bootstrapping

This commit is contained in:
Ondrej Samohel 2020-10-13 16:59:30 +02:00
parent be308a48ba
commit 01b3b96983
No known key found for this signature in database
GPG key ID: 8A29C663C672C2B7
8 changed files with 197 additions and 25 deletions

View file

@ -9,6 +9,7 @@ import tempfile
from typing import Union, Callable, Dict
from zipfile import ZipFile
from pathlib import Path
from speedcopy import copyfile
from appdirs import user_data_dir
from pype.version import __version__
@ -85,6 +86,11 @@ class BootstrapRepos:
def install_live_repos(self, repo_dir: Path = None) -> Union[Path, None]:
"""Copy zip created from Pype repositories to user data dir.
This detect Pype version either in local "live" Pype repository
or in user provided path. Then it will zip in in temporary directory
and finally it will move it to destination which is user data
directory. Existing files will be replaced.
Args:
repo_dir (Path, optional): Path to Pype repository.
@ -92,6 +98,9 @@ class BootstrapRepos:
Path: path of installed repository file.
"""
# if repo dir is not set, we detect local "live" Pype repository
# version and use it as a source. Otherwise repo_dir is user
# entered location.
if not repo_dir:
version = self.get_local_version()
repo_dir = self.live_repo_dir
@ -99,10 +108,10 @@ class BootstrapRepos:
version = self.get_version(repo_dir)
# create destination directory
try:
os.makedirs(self.data_dir)
except OSError:
self._log.error("directory already exists")
if not self.data_dir.exists():
self.data_dir.mkdir(parents=True)
# create zip inside temporary directory.
with tempfile.TemporaryDirectory() as temp_dir:
temp_zip = \
Path(temp_dir) / f"pype-repositories-v{version}.zip"
@ -214,7 +223,8 @@ class BootstrapRepos:
os.environ["PYTHONPATH"] = os.pathsep.join(paths)
def find_pype(self) -> Union[Dict[str, Path], None]:
def find_pype(
self, pype_path: Path = None) -> Union[Dict[str, Path], None]:
"""Get ordered dict of detected Pype version.
Resolution order for Pype is following:
@ -223,6 +233,9 @@ class BootstrapRepos:
2) We try to find ``pypePath`` in registry setting
3) We use user data directory
Args:
pype_path (Path, optional): Try to find Pype on the given path.
Returns:
dict of Path: Dictionary of detected Pype version.
Key is version, value is path to zip file.
@ -244,6 +257,10 @@ class BootstrapRepos:
# nothing found in registry, we'll use data dir
pass
# if we have pyp_path specified, search only there.
if pype_path:
dir_to_search = pype_path
# pype installation dir doesn't exists
if not dir_to_search.exists():
return None
@ -275,7 +292,7 @@ class BootstrapRepos:
None: if not.
"""
os.environ["AVALON_MONGO"] = mongo_url
os.environ["PYPE_MONGO"] = mongo_url
env = load_environments()
if not env.get("PYPE_PATH"):
return None
@ -299,19 +316,59 @@ class BootstrapRepos:
"""
pype_path = None
# try to get pype path from mongo.
if location.startswith("mongodb"):
pype_path = self._get_pype_from_mongo(location)
if not pype_path:
self._log.error("cannot find PYPE_PATH in settings.")
return None
# if not successful, consider location to be fs path.
if not pype_path:
pype_path = Path(location)
# test if this path does exist.
if not pype_path.exists():
self._log.error(f"{pype_path} doesn't exists.")
return None
# find pype zip files in location. In that location, there can be
# either "live" Pype repository, or multiple zip files.
versions = self.find_pype(pype_path)
if versions:
latest_version = (
list(versions.keys())[-1], list(versions.values())[-1])
self._log.info(f"found Pype zips in [ {pype_path} ].")
self._log.info(f"latest version found is [ {latest_version[0]} ]")
destination = self.data_dir / latest_version[1].name
# test if destination file already exist, if so lets delete it.
# we consider path on location as authoritative place.
if destination.exists():
try:
destination.unlink()
except OSError:
self._log.error(
f"cannot remove already existing {destination}",
exc_info=True)
return None
# create destination parent directories even if they don't exist.
if not destination.parent.exists():
destination.parent.mkdir(parents=True)
try:
copyfile(latest_version[1].as_posix(), destination.as_posix())
except OSError:
self._log.error(
"cannot copy detected version to user data directory",
exc_info=True)
return None
return destination
# if we got here, it means that location is "live" Pype repository.
# we'll create zip from it and move it to user data dir.
repo_file = self.install_live_repos(pype_path)
if not repo_file.exists():
self._log.error(f"installing zip {repo_file} failed.")

View file

@ -7,7 +7,7 @@ from Qt import QtCore, QtGui, QtWidgets
from Qt.QtGui import QValidator
from .install_thread import InstallThread
from .tools import validate_path_string
from .tools import validate_path_string, validate_mongo_connection
class InstallDialog(QtWidgets.QDialog):
@ -152,8 +152,27 @@ class InstallDialog(QtWidgets.QDialog):
def get_mongo_url(self):
return self._mongo_url
def set_valid(self):
self._mongo_input.setStyleSheet(
"""
background-color: rgb(19, 19, 19);
color: rgb(64, 230, 132);
padding: 0.5em;
border: 1px solid rgb(32, 64, 32);
"""
)
def set_invalid(self):
self._mongo_input.setStyleSheet(
"""
background-color: rgb(32, 19, 19);
color: rgb(255, 69, 0);
padding: 0.5em;
border: 1px solid rgb(32, 64, 32);
"""
)
self._mongo = MongoWidget(self)
self._mongo.hide()
# Bottom button bar
# --------------------------------------------------------------------
@ -321,10 +340,19 @@ class InstallDialog(QtWidgets.QDialog):
border: 1px solid rgb(32, 64, 32);
"""
)
if not self._path or not self._path.startswith("mongodb"):
valid, reason = validate_mongo_connection(
self._mongo.get_mongo_url()
)
if not valid:
self._mongo.set_invalid()
self._update_console(f"!!! {reason}", True)
return
else:
self._mongo.set_valid()
self._disable_buttons()
self._install_thread = InstallThread(self)
self._install_thread.ask_for_mongo.connect(self._show_mongo)
self._install_thread.message.connect(self._update_console)
self._install_thread.progress.connect(self._update_progress)
self._install_thread.finished.connect(self._enable_buttons)
@ -338,9 +366,6 @@ class InstallDialog(QtWidgets.QDialog):
def _on_exit_clicked(self):
self.close()
def _show_mongo(self):
self._update_console("mongo showed")
def _path_changed(self, path: str) -> str:
"""Set path."""
self._path = path

View file

@ -2,7 +2,8 @@
"""Working thread for installer."""
import os
from Qt.QtCore import QThread, Signal
from igniter.tools import load_environments
from speedcopy import copyfile
from .bootstrap_repos import BootstrapRepos
from .tools import validate_mongo_connection
@ -33,7 +34,15 @@ class InstallThread(QThread):
QThread.__init__(self, parent)
def run(self):
"""Thread entry point.
Using :class:`BootstrapRepos` to either install Pype as zip files
or copy them from location specified by user or retrieved from
database.
"""
self.message.emit("Installing Pype ...", False)
# find local version of Pype
bs = BootstrapRepos(progress_callback=self.set_progress)
local_version = bs.get_local_version()
@ -42,7 +51,24 @@ class InstallThread(QThread):
# zip content of `repos`, copy it to user data dir and append
# version to it.
if not self._path:
self.ask_for_mongo.emit(True)
# user did not entered url
if not self._mongo:
# it not set in environment
if not os.getenv("PYPE_MONGO"):
# try to get it from settings registry
try:
self._mongo = bs.registry.get_secure_item("pypeMongo")
except ValueError:
self.message.emit(
"!!! We need MongoDB URL to proceed.", True)
return
else:
self._mongo = os.getenv("PYPE_MONGO")
else:
bs.registry.set_secure_item("pypeMongo", self._mongo)
os.environ["PYPE_MONGO"] = self._mongo
self.message.emit(
f"We will use local Pype version {local_version}", False)
repo_file = bs.install_live_repos()
@ -52,13 +78,15 @@ class InstallThread(QThread):
return
self.message.emit(f"installed as {repo_file}", False)
else:
# if we have mongo connection string, validate it, set it to
# user settings and get PYPE_PATH from there.
if self._mongo:
if not validate_mongo_connection(self._mongo):
self.message.emit(
f"!!! invalid mongo url {self._mongo}", True)
return
bs.registry.set_secure_item("avalonMongo", self._mongo)
os.environ["AVALON_MONGO"] = self._mongo
bs.registry.set_secure_item("pypeMongo", self._mongo)
os.environ["PYPE_MONGO"] = self._mongo
repo_file = bs.process_entered_location(self._path)
@ -72,5 +100,5 @@ class InstallThread(QThread):
def set_mongo(self, mongo: str) -> None:
self._mongo = mongo
def set_progress(self, progress: int):
def set_progress(self, progress: int) -> None:
self.progress.emit(progress)

View file

@ -61,13 +61,15 @@ def validate_path_string(path: str) -> (bool, str):
the reason why it failed.
"""
if not path:
return True, "Empty string"
parsed = urlparse(path)
if parsed.scheme == "mongodb":
return validate_mongo_connection(path)
# test for uuid
try:
uuid.UUID(path)
except ValueError:
except (ValueError, TypeError):
# not uuid
if not os.path.exists(path):
return False, "Path doesn't exist or invalid token"

20
pype.py
View file

@ -86,16 +86,21 @@ def boot():
use_version = m.group('version')
break
if not os.getenv("AVALON_MONGO"):
if not os.getenv("PYPE_MONGO"):
try:
avalon_mongo = bootstrap.registry.get_secure_item("avalonMongo")
pype_mongo = bootstrap.registry.get_secure_item("pypeMongo")
except ValueError:
print("*** No DB connection string specified.")
print("--- launching setup UI ...")
import igniter
igniter.run()
set_environments()
return
else:
os.environ["AVALON_MONGO"] = avalon_mongo
os.environ["PYPE_MONGO"] = pype_mongo
# FIXME (antirotor): we need to set those in different way
if not os.getenv("AVALON_MONGO"):
os.environ["AVALON_MONGO"] = os.environ["PYPE_MONGO"]
if getattr(sys, 'frozen', False):
if not pype_versions:
@ -118,6 +123,13 @@ def boot():
else:
# run through repos and add them to sys.path and PYTHONPATH
pype_root = os.path.dirname(os.path.realpath(__file__))
local_version = bootstrap.get_local_version()
if use_version and use_version != local_version:
if use_version in pype_versions.keys():
# use specified
bootstrap.add_paths_from_archive(pype_versions[use_version])
use_version = pype_versions[use_version]
os.environ["PYPE_ROOT"] = pype_root
repos = os.listdir(os.path.join(pype_root, "repos"))
repos = [os.path.join(pype_root, "repos", repo) for repo in repos]

View file

@ -321,7 +321,8 @@ class JSONSettingRegistry(ASettingRegistry):
"registry": {}
}
self._registry_file.parent.mkdir(parents=True)
if not self._registry_file.parent.exists():
self._registry_file.parent.mkdir(parents=True)
if not os.path.exists(self._registry_file):
with open(self._registry_file, mode="w") as cfg:
json.dump(header, cfg, indent=4)

View file

@ -1,5 +1,12 @@
# -*- coding: utf-8 -*-
"""Implementation of Pype commands."""
import os
import sys
import subprocess
from pathlib import Path
from pype.lib import execute
from pype.lib import PypeLogger as Logger
class PypeCommands:
@ -7,9 +14,43 @@ class PypeCommands:
Most of its methods are called by :mod:`cli` module.
"""
@staticmethod
def launch_tray(debug):
if debug:
execute([
sys.executable,
"-m",
"pype.tools.tray"
])
return
detached_process = 0x00000008 # noqa: N806
args = [sys.executable, "-m", "pype.tools.tray"]
if sys.platform.startswith('linux'):
subprocess.Popen(
args,
universal_newlines=True,
bufsize=1,
env=os.environ,
stdout=None,
stderr=None,
preexec_fn=os.setpgrp
)
if sys.platform == 'win32':
args = ["pythonw", "-m", "pype.tools.tray"]
subprocess.Popen(
args,
universal_newlines=True,
bufsize=1,
cwd=None,
env=os.environ,
stdout=open(Logger.get_file_path(), 'w+'),
stderr=subprocess.STDOUT,
creationflags=detached_process
)
def launch_tray(self, debug):
pass
def launch_eventservercli(self, args):
pass

View file

@ -118,3 +118,9 @@ def test_find_pype(fix_bootstrap, tmp_path_factory, monkeypatch, printer):
assert list(result.values())[-1] == Path(
r_path / test_versions_2[0]
), "not a latest version of Pype 2"
result = fix_bootstrap.find_pype(e_path)
assert result is not None, "no Pype version found"
assert list(result.values())[-1] == Path(
e_path / test_versions_1[1]
), "not a latest version of Pype 1"