Merge pull request #1077 from pypeclub/feature/igniter-improvements

Igniter and Pype bootstrap improvements
This commit is contained in:
Milan Kolar 2021-03-10 17:53:20 +01:00 committed by GitHub
commit 68be90e31d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 1134 additions and 665 deletions

View file

@ -2,23 +2,36 @@
"""Open install dialog."""
import sys
from Qt import QtWidgets
from Qt import QtWidgets # noqa
from Qt.QtCore import Signal # noqa
from .install_dialog import InstallDialog
from .bootstrap_repos import BootstrapRepos
from .version import __version__ as version
def run():
RESULT = 0
def get_result(res: int):
"""Sets result returned from dialog."""
global RESULT
RESULT = res
def open_dialog():
"""Show Igniter dialog."""
app = QtWidgets.QApplication(sys.argv)
d = InstallDialog()
d.exec_()
d.show()
sys.exit(app.exec_())
d.finished.connect(get_result)
d.open()
app.exec()
return RESULT
__all__ = [
"InstallDialog",
"BootstrapRepos",
"run"
"open_dialog",
"version"
]

View file

@ -2,11 +2,25 @@
"""Open install dialog."""
import sys
from Qt import QtWidgets
from Qt import QtWidgets # noqa
from Qt.QtCore import Signal # noqa
from .install_dialog import InstallDialog
RESULT = 0
def get_result(res: int):
"""Sets result returned from dialog."""
global RESULT
RESULT = res
app = QtWidgets.QApplication(sys.argv)
d = InstallDialog()
d.show()
sys.exit(app.exec_())
d.finished.connect(get_result)
d.open()
app.exec()
sys.exit(RESULT)

View file

@ -15,7 +15,12 @@ from appdirs import user_data_dir
from speedcopy import copyfile
from .user_settings import PypeSettingsRegistry
from .tools import load_environments
from .tools import get_pype_path_from_db
LOG_INFO = 0
LOG_WARNING = 1
LOG_ERROR = 3
@functools.total_ordering
@ -285,6 +290,9 @@ class BootstrapRepos:
def get_version(repo_dir: Path) -> Union[str, None]:
"""Get version of Pype in given directory.
Note: in frozen Pype installed in user data dir, this must point
one level deeper as it is `pype-version-v3.0.0/pype/pype/version.py`
Args:
repo_dir (Path): Path to Pype repo.
@ -304,7 +312,8 @@ class BootstrapRepos:
return version['__version__']
def install_live_repos(self, repo_dir: Path = None) -> Union[Path, None]:
def create_version_from_live_code(
self, repo_dir: Path = None) -> Union[PypeVersion, None]:
"""Copy zip created from Pype repositories to user data dir.
This detect Pype version either in local "live" Pype repository
@ -328,6 +337,10 @@ class BootstrapRepos:
else:
version = self.get_version(repo_dir)
if not version:
self._print("Pype not found.", LOG_ERROR)
return
# create destination directory
if not self.data_dir.exists():
self.data_dir.mkdir(parents=True)
@ -336,30 +349,127 @@ class BootstrapRepos:
with tempfile.TemporaryDirectory() as temp_dir:
temp_zip = \
Path(temp_dir) / f"pype-v{version}.zip"
self._log.info(f"creating zip: {temp_zip}")
self._print(f"creating zip: {temp_zip}")
self._create_pype_zip(temp_zip, repo_dir)
if not os.path.exists(temp_zip):
self._log.error("make archive failed.")
self._print("make archive failed.", LOG_ERROR)
return None
destination = self.data_dir / temp_zip.name
destination = self._move_zip_to_data_dir(temp_zip)
if destination.exists():
self._log.warning(
f"Destination file {destination} exists, removing.")
try:
destination.unlink()
except Exception as e:
self._log.error(e)
return None
return PypeVersion(version=version, path=destination)
def _move_zip_to_data_dir(self, zip_file) -> Union[None, Path]:
"""Move zip with Pype version to user data directory.
Args:
zip_file (Path): Path to zip file.
Returns:
None if move fails.
Path to moved zip on success.
"""
destination = self.data_dir / zip_file.name
if destination.exists():
self._print(
f"Destination file {destination} exists, removing.",
LOG_WARNING)
try:
shutil.move(temp_zip.as_posix(), self.data_dir.as_posix())
except shutil.Error as e:
self._log.error(e)
destination.unlink()
except Exception as e:
self._print(str(e), LOG_ERROR, exc_info=True)
return None
try:
shutil.move(zip_file.as_posix(), self.data_dir.as_posix())
except shutil.Error as e:
self._print(str(e), LOG_ERROR, exc_info=True)
return None
return destination
def _filter_dir(self, path: Path, path_filter: List) -> List[Path]:
"""Recursively crawl over path and filter."""
result = []
for item in path.iterdir():
if item.name in path_filter:
continue
if item.name.startswith('.'):
continue
if item.is_dir():
result.extend(self._filter_dir(item, path_filter))
else:
result.append(item)
return result
def create_version_from_frozen_code(self) -> Union[None, PypeVersion]:
"""Create Pype version from *frozen* code distributed by installer.
This should be real edge case for those wanting to try out Pype
without setting up whole infrastructure but is strongly discouraged
in studio setup as this use local version independent of others
that can be out of date.
Returns:
:class:`PypeVersion` zip file to be installed.
"""
frozen_root = Path(sys.executable).parent
repo_dir = frozen_root / "repos"
repo_list = self._filter_dir(
repo_dir, self.zip_filter)
# from frozen code we need igniter, pype, schema vendor
pype_list = self._filter_dir(
frozen_root / "pype", self.zip_filter)
pype_list += self._filter_dir(
frozen_root / "igniter", self.zip_filter)
pype_list += self._filter_dir(
frozen_root / "schema", self.zip_filter)
pype_list += self._filter_dir(
frozen_root / "vendor", self.zip_filter)
pype_list.append(frozen_root / "README.md")
pype_list.append(frozen_root / "LICENSE")
version = self.get_version(frozen_root)
# create zip inside temporary directory.
with tempfile.TemporaryDirectory() as temp_dir:
temp_zip = \
Path(temp_dir) / f"pype-v{version}.zip"
self._print(f"creating zip: {temp_zip}")
with ZipFile(temp_zip, "w") as zip_file:
progress = 0
repo_inc = 48.0 / float(len(repo_list))
file: Path
for file in repo_list:
progress += repo_inc
self._progress_callback(int(progress))
# archive name is relative to repos dir
arc_name = file.relative_to(repo_dir)
zip_file.write(file, arc_name)
pype_inc = 48.0 / float(len(pype_list))
file: Path
for file in pype_list:
progress += pype_inc
self._progress_callback(int(progress))
arc_name = file.relative_to(frozen_root.parent)
# we need to replace first part of path which starts with
# something like `exe.win/linux....` with `pype` as this
# is expected by Pype in zip archive.
arc_name = Path("pype").joinpath(*arc_name.parts[1:])
zip_file.write(file, arc_name)
destination = self._move_zip_to_data_dir(temp_zip)
return PypeVersion(version=version, path=destination)
def _create_pype_zip(
self,
zip_path: Path, include_dir: Path,
@ -379,23 +489,9 @@ class BootstrapRepos:
"""
include_dir = include_dir.resolve()
def _filter_dir(path: Path, path_filter: List) -> List[Path]:
"""Recursively crawl over path and filter."""
result = []
for item in path.iterdir():
if item.name in path_filter:
continue
if item.name.startswith('.'):
continue
if item.is_dir():
result.extend(_filter_dir(item, path_filter))
else:
result.append(item)
return result
pype_list = []
# get filtered list of files in repositories (repos directory)
repo_list = _filter_dir(include_dir, self.zip_filter)
repo_list = self._filter_dir(include_dir, self.zip_filter)
# count them
repo_files = len(repo_list)
@ -404,15 +500,15 @@ class BootstrapRepos:
pype_inc = 0
if include_pype:
# get filtered list of file in Pype repository
pype_list = _filter_dir(include_dir.parent, self.zip_filter)
pype_list = self._filter_dir(include_dir.parent, self.zip_filter)
pype_files = len(pype_list)
repo_inc = 48.0 / float(repo_files)
pype_inc = 48.0 / float(pype_files)
else:
repo_inc = 98.0 / float(repo_files)
progress = 0
with ZipFile(zip_path, "w") as zip_file:
progress = 0
file: Path
for file in repo_list:
progress += repo_inc
@ -446,8 +542,7 @@ class BootstrapRepos:
continue
processed_path = file
self._log.debug(f"processing {processed_path}")
self._print(f"- processing {processed_path}", False)
self._print(f"- processing {processed_path}")
zip_file.write(file,
"pype" / file.relative_to(pype_root))
@ -468,6 +563,9 @@ class BootstrapRepos:
Args:
archive (Path): path to archive.
.. deprecated:: 3.0
we don't use zip archives directly
"""
if not archive.is_file() and not archive.exists():
raise ValueError("Archive is not file.")
@ -520,7 +618,7 @@ class BootstrapRepos:
def find_pype(
self,
pype_path: Path = None,
pype_path: Union[Path, str] = None,
staging: bool = False,
include_zips: bool = False) -> Union[List[PypeVersion], None]:
"""Get ordered dict of detected Pype version.
@ -532,7 +630,8 @@ class BootstrapRepos:
3) We use user data directory
Args:
pype_path (Path, optional): Try to find Pype on the given path.
pype_path (Path or str, optional): Try to find Pype on the given
path or url.
staging (bool, optional): Filter only staging version, skip them
otherwise.
include_zips (bool, optional): If set True it will try to find
@ -544,7 +643,17 @@ class BootstrapRepos:
None: if Pype is not found.
Todo:
implement git/url support as Pype location, so it would be
possible to enter git url, Pype would check it out and if it is
ok install it as normal version.
"""
if pype_path and not isinstance(pype_path, Path):
raise NotImplementedError(
("Finding Pype in non-filesystem locations is"
" not implemented yet."))
dir_to_search = self.data_dir
# if we have pype_path specified, search only there.
@ -565,116 +674,15 @@ class BootstrapRepos:
# nothing found in registry, we'll use data dir
pass
# pype installation dir doesn't exists
if not dir_to_search.exists():
return None
pype_versions = self.get_pype_versions(dir_to_search, staging)
_pype_versions = []
# iterate over directory in first level and find all that might
# contain Pype.
for file in dir_to_search.iterdir():
# remove zip file version if needed.
if not include_zips:
pype_versions = [
v for v in pype_versions if v.path.suffix != ".zip"
]
# if file, strip extension, in case of dir not.
name = file.name if file.is_dir() else file.stem
result = PypeVersion.version_in_str(name)
if result[0]:
detected_version: PypeVersion
detected_version = result[1]
if file.is_dir():
# if item is directory that might (based on it's name)
# contain Pype version, check if it really does contain
# Pype and that their versions matches.
try:
# add one 'pype' level as inside dir there should
# be many other repositories.
version_str = BootstrapRepos.get_version(
file / "pype")
version_check = PypeVersion(version=version_str)
except ValueError:
self._log.error(
f"cannot determine version from {file}")
continue
version_main = version_check.get_main_version()
detected_main = detected_version.get_main_version()
if version_main != detected_main:
self._log.error(
(f"dir version ({detected_version}) and "
f"its content version ({version_check}) "
"doesn't match. Skipping."))
continue
if file.is_file():
if not include_zips:
continue
# skip non-zip files
if file.suffix.lower() != ".zip":
continue
# open zip file, look inside and parse version from Pype
# inside it. If there is none, or it is different from
# version specified in file name, skip it.
try:
with ZipFile(file, "r") as zip_file:
with zip_file.open(
"pype/pype/version.py") as version_file:
zip_version = {}
exec(version_file.read(), zip_version)
version_check = PypeVersion(
version=zip_version["__version__"])
version_main = version_check.get_main_version() # noqa: E501
detected_main = detected_version.get_main_version() # noqa: E501
if version_main != detected_main:
self._log.error(
(f"zip version ({detected_version}) "
f"and its content version "
f"({version_check}) "
"doesn't match. Skipping."))
continue
except BadZipFile:
self._log.error(f"{file} is not zip file")
continue
except KeyError:
self._log.error("Zip not containing Pype")
continue
detected_version.path = file
if staging and detected_version.is_staging():
_pype_versions.append(detected_version)
if not staging and not detected_version.is_staging():
_pype_versions.append(detected_version)
return sorted(_pype_versions)
@staticmethod
def _get_pype_from_mongo(mongo_url: str) -> Union[Path, None]:
"""Get path from Mongo database.
This sets environment variable ``PYPE_MONGO`` for
:mod:`pype.settings` to be able to read data from database.
It will then retrieve environment variables and among them
must be ``PYPE_PATH``.
Args:
mongo_url (str): mongodb connection url
Returns:
Path: if path from ``PYPE_PATH`` is found.
None: if not.
"""
os.environ["PYPE_MONGO"] = mongo_url
env = load_environments()
if not env.get("PYPE_PATH"):
return None
return Path(env.get("PYPE_PATH"))
return pype_versions
def process_entered_location(self, location: str) -> Union[Path, None]:
"""Process user entered location string.
@ -683,7 +691,7 @@ class BootstrapRepos:
If it is mongodb url, it will connect and load ``PYPE_PATH`` from
there and use it as path to Pype. In it is _not_ mongodb url, it
is assumed we have a path, this is tested and zip file is
produced and installed using :meth:`install_live_repos`.
produced and installed using :meth:`create_version_from_live_code`.
Args:
location (str): User entered location.
@ -696,9 +704,9 @@ class BootstrapRepos:
pype_path = None
# try to get pype path from mongo.
if location.startswith("mongodb"):
pype_path = self._get_pype_from_mongo(location)
pype_path = get_pype_path_from_db(location)
if not pype_path:
self._log.error("cannot find PYPE_PATH in settings.")
self._print("cannot find PYPE_PATH in settings.")
return None
# if not successful, consider location to be fs path.
@ -707,108 +715,59 @@ class BootstrapRepos:
# test if this path does exist.
if not pype_path.exists():
self._log.error(f"{pype_path} doesn't exists.")
self._print(f"{pype_path} doesn't exists.")
return None
# test if entered path isn't user data dir
if self.data_dir == pype_path:
self._log.error("cannot point to user data dir")
self._print("cannot point to user data dir", LOG_ERROR)
return None
# find pype zip files in location. There can be
# either "live" Pype repository, or multiple zip files or even
# multiple pype version directories. This process looks into zip
# files and directories and tries to parse `version.py` file.
versions = self.find_pype(pype_path)
versions = self.find_pype(pype_path, include_zips=True)
if versions:
self._log.info(f"found Pype in [ {pype_path} ]")
self._log.info(f"latest version found is [ {versions[-1]} ]")
self._print(f"found Pype in [ {pype_path} ]")
self._print(f"latest version found is [ {versions[-1]} ]")
destination = self.data_dir / versions[-1].path.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)
# latest version found is directory
if versions[-1].path.is_dir():
# zip it, copy it and extract it
# create zip inside temporary directory.
self._log.info("Creating zip from directory ...")
with tempfile.TemporaryDirectory() as temp_dir:
temp_zip = \
Path(temp_dir) / f"pype-v{versions[-1]}.zip"
self._log.info(f"creating zip: {temp_zip}")
self._create_pype_zip(temp_zip, versions[-1].path)
if not os.path.exists(temp_zip):
self._log.error("make archive failed.")
return None
destination = self.data_dir / temp_zip.name
elif versions[-1].path.is_file():
# in this place, it must be zip file as `find_pype()` is
# checking just that.
assert versions[-1].path.suffix.lower() == ".zip", (
"Invalid file format"
)
try:
self._log.info("Copying zip to destination ...")
copyfile(versions[-1].path.as_posix(), destination.as_posix())
except OSError:
self._log.error(
"cannot copy detected version to user data directory",
exc_info=True)
return None
# extract zip there
self._log.info("extracting zip to destination ...")
with ZipFile(versions[-1].path, "r") as zip_ref:
zip_ref.extractall(destination)
return destination
return self.install_version(versions[-1])
# 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.")
live_pype = self.create_version_from_live_code(pype_path)
if not live_pype.path.exists():
self._print(f"installing zip {live_pype} failed.", LOG_ERROR)
return None
# install it
return self.install_version(live_pype)
destination = self.data_dir / repo_file.stem
if destination.exists():
try:
destination.unlink()
except OSError:
self._log.error(
f"cannot remove already existing {destination}",
exc_info=True)
return None
def _print(self,
message: str,
level: int = LOG_INFO,
exc_info: bool = False):
"""Helper function passing logs to UI and to logger.
destination.mkdir(parents=True)
Supporting 3 levels of logs defined with `LOG_INFO`, `LOG_WARNING` and
`LOG_ERROR` constants.
# extract zip there
self._log.info("extracting zip to destination ...")
with ZipFile(versions[-1].path, "r") as zip_ref:
zip_ref.extractall(destination)
Args:
message (str): Message to log.
level (int, optional): Log level to use.
exc_info (bool, optional): Exception info object to pass to logger.
return destination
def _print(self, message, error=False):
"""
if self._message:
self._message.emit(message, error)
self._message.emit(message, level == LOG_ERROR)
if level == LOG_WARNING:
self._log.warning(message, exc_info=exc_info)
return
if level == LOG_ERROR:
self._log.error(message, exc_info=exc_info)
return
self._log.info(message, exc_info=exc_info)
def extract_pype(self, version: PypeVersion) -> Union[Path, None]:
"""Extract zipped Pype version to user data directory.
@ -829,12 +788,9 @@ class BootstrapRepos:
if destination.exists():
try:
destination.unlink()
except OSError as e:
except OSError:
msg = f"!!! Cannot remove already existing {destination}"
self._log.error(msg)
self._log.error(e.strerror)
self._print(msg, True)
self._print(e.strerror, True)
self._print(msg, LOG_ERROR, exc_info=True)
return None
destination.mkdir(parents=True)
@ -848,7 +804,29 @@ class BootstrapRepos:
return destination
def install_version(self, pype_version: PypeVersion, force: bool = False):
def is_inside_user_data(self, path: Path) -> bool:
"""Test if version is located in user data dir.
Args:
path (Path) Path to test.
Returns:
True if path is inside user data dir.
"""
is_inside = False
try:
is_inside = path.resolve().relative_to(
self.data_dir)
except ValueError:
# if relative path cannot be calculated, Pype version is not
# inside user data dir
pass
return is_inside
def install_version(self,
pype_version: PypeVersion,
force: bool = False) -> Path:
"""Install Pype version to user data directory.
Args:
@ -866,52 +844,46 @@ class BootstrapRepos:
"""
# test if version is located (in user data dir)
is_inside = False
try:
is_inside = pype_version.path.resolve().relative_to(
self.data_dir)
except ValueError:
# if relative path cannot be calculated, Pype version is not
# inside user data dir
pass
if is_inside:
if self.is_inside_user_data(pype_version.path) and not pype_version.path.is_file(): # noqa
raise PypeVersionExists("Pype already inside user data dir")
# determine destination directory name
# for zip file strip suffix
destination = self.data_dir / pype_version.path.stem
# for zip file strip suffix, in case of dir use whole dir name
if pype_version.path.is_dir():
dir_name = pype_version.path.name
else:
dir_name = pype_version.path.stem
# test if destination file already exist, if so lets delete it.
# we consider path on location as authoritative place.
destination = self.data_dir / dir_name
# test if destination directory already exist, if so lets delete it.
if destination.exists() and force:
try:
destination.unlink()
except OSError:
self._log.error(
shutil.rmtree(destination)
except OSError as e:
self._print(
f"cannot remove already existing {destination}",
exc_info=True)
return None
else:
LOG_ERROR, exc_info=True)
raise PypeVersionIOError(
f"cannot remove existing {destination}") from e
elif destination.exists() and not force:
raise PypeVersionExists(f"{destination} already exist.")
# create destination parent directories even if they don't exist.
if not destination.exists():
else:
# create destination parent directories even if they don't exist.
destination.mkdir(parents=True)
# version is directory
if pype_version.path.is_dir():
# create zip inside temporary directory.
self._log.info("Creating zip from directory ...")
self._print("Creating zip from directory ...")
with tempfile.TemporaryDirectory() as temp_dir:
temp_zip = \
Path(temp_dir) / f"pype-v{pype_version}.zip"
self._log.info(f"creating zip: {temp_zip}")
self._print(f"creating zip: {temp_zip}")
self._create_pype_zip(temp_zip, pype_version.path)
if not os.path.exists(temp_zip):
self._log.error("make archive failed.")
self._print("make archive failed.", LOG_ERROR)
raise PypeVersionIOError("Zip creation failed.")
# set zip as version source
@ -922,24 +894,164 @@ class BootstrapRepos:
if pype_version.path.suffix.lower() != ".zip":
raise PypeVersionInvalid("Invalid file format")
try:
# copy file to destination
self._log.info("Copying zip to destination ...")
copyfile(pype_version.path.as_posix(), destination.as_posix())
except OSError as e:
self._log.error(
"cannot copy version to user data directory",
exc_info=True)
raise PypeVersionIOError(
"can't copy version to destination") from e
if not self.is_inside_user_data(pype_version.path):
try:
# copy file to destination
self._print("Copying zip to destination ...")
_destination_zip = destination.parent / pype_version.path.name
copyfile(
pype_version.path.as_posix(),
_destination_zip.as_posix())
except OSError as e:
self._print(
"cannot copy version to user data directory", LOG_ERROR,
exc_info=True)
raise PypeVersionIOError((
f"can't copy version {pype_version.path.as_posix()} "
f"to destination {destination.parent.as_posix()}")) from e
# extract zip there
self._log.info("extracting zip to destination ...")
self._print("extracting zip to destination ...")
with ZipFile(pype_version.path, "r") as zip_ref:
zip_ref.extractall(destination)
return destination
def _is_pype_in_dir(self,
dir_item: Path,
detected_version: PypeVersion) -> bool:
"""Test if path item is Pype version matching detected version.
If item is directory that might (based on it's name)
contain Pype version, check if it really does contain
Pype and that their versions matches.
Args:
dir_item (Path): Directory to test.
detected_version (PypeVersion): Pype version detected from name.
Returns:
True if it is valid Pype version, False otherwise.
"""
try:
# add one 'pype' level as inside dir there should
# be many other repositories.
version_str = BootstrapRepos.get_version(
dir_item / "pype")
version_check = PypeVersion(version=version_str)
except ValueError:
self._print(
f"cannot determine version from {dir_item}", True)
return False
version_main = version_check.get_main_version()
detected_main = detected_version.get_main_version()
if version_main != detected_main:
self._print(
(f"dir version ({detected_version}) and "
f"its content version ({version_check}) "
"doesn't match. Skipping."))
return False
return True
def _is_pype_in_zip(self,
zip_item: Path,
detected_version: PypeVersion) -> bool:
"""Test if zip path is Pype version matching detected version.
Open zip file, look inside and parse version from Pype
inside it. If there is none, or it is different from
version specified in file name, skip it.
Args:
zip_item (Path): Zip file to test.
detected_version (PypeVersion): Pype version detected from name.
Returns:
True if it is valid Pype version, False otherwise.
"""
# skip non-zip files
if zip_item.suffix.lower() != ".zip":
return False
try:
with ZipFile(zip_item, "r") as zip_file:
with zip_file.open(
"pype/pype/version.py") as version_file:
zip_version = {}
exec(version_file.read(), zip_version)
version_check = PypeVersion(
version=zip_version["__version__"])
version_main = version_check.get_main_version() # noqa: E501
detected_main = detected_version.get_main_version() # noqa: E501
if version_main != detected_main:
self._print(
(f"zip version ({detected_version}) "
f"and its content version "
f"({version_check}) "
"doesn't match. Skipping."), True)
return False
except BadZipFile:
self._print(f"{zip_item} is not a zip file", True)
return False
except KeyError:
self._print("Zip does not contain Pype", True)
return False
return True
def get_pype_versions(self, pype_dir: Path, staging: bool = False) -> list:
"""Get all detected Pype versions in directory.
Args:
pype_dir (Path): Directory to scan.
staging (bool, optional): Find staging versions if True.
Returns:
list of PypeVersion
Throws:
ValueError: if invalid path is specified.
"""
if not pype_dir.exists() and not pype_dir.is_dir():
raise ValueError("specified directory is invalid")
_pype_versions = []
# iterate over directory in first level and find all that might
# contain Pype.
for item in pype_dir.iterdir():
# if file, strip extension, in case of dir not.
name = item.name if item.is_dir() else item.stem
result = PypeVersion.version_in_str(name)
if result[0]:
detected_version: PypeVersion
detected_version = result[1]
if item.is_dir() and not self._is_pype_in_dir(
item, detected_version
):
continue
if item.is_file() and not self._is_pype_in_zip(
item, detected_version
):
continue
detected_version.path = item
if staging and detected_version.is_staging():
_pype_versions.append(detected_version)
if not staging and not detected_version.is_staging():
_pype_versions.append(detected_version)
return sorted(_pype_versions)
class PypeVersionExists(Exception):
"""Exception for handling existing Pype version."""

View file

@ -3,25 +3,54 @@
import os
import sys
from Qt import QtCore, QtGui, QtWidgets
from Qt.QtGui import QValidator
from Qt import QtCore, QtGui, QtWidgets # noqa
from Qt.QtGui import QValidator # noqa
from Qt.QtCore import QTimer # noqa
from .install_thread import InstallThread
from .tools import validate_path_string, validate_mongo_connection
from .install_thread import InstallThread, InstallResult
from .tools import (
validate_path_string,
validate_mongo_connection,
get_pype_path_from_db
)
from .user_settings import PypeSettingsRegistry
from .version import __version__
class FocusHandlingLineEdit(QtWidgets.QLineEdit):
"""Handling focus in/out on QLineEdit."""
focusIn = QtCore.Signal()
focusOut = QtCore.Signal()
def focusOutEvent(self, event): # noqa
"""For emitting signal on focus out."""
self.focusOut.emit()
super().focusOutEvent(event)
def focusInEvent(self, event): # noqa
"""For emitting signal on focus in."""
self.focusIn.emit()
super().focusInEvent(event)
class InstallDialog(QtWidgets.QDialog):
"""Main Igniter dialog window."""
_size_w = 400
_size_h = 400
_path = None
_size_h = 600
path = ""
_controls_disabled = False
def __init__(self, parent=None):
super(InstallDialog, self).__init__(parent)
self.registry = PypeSettingsRegistry()
self._mongo_url = os.getenv("PYPE_MONGO", "")
self.mongo_url = ""
try:
self.mongo_url = os.getenv("PYPE_MONGO", "") or self.registry.get_secure_item("pypeMongo") # noqa: E501
except ValueError:
pass
self.setWindowTitle("Pype - Configure Pype repository path")
self.setWindowTitle(f"Pype Igniter {__version__} - Pype installation")
self._icon_path = os.path.join(
os.path.dirname(__file__), 'pype_icon.png')
icon = QtGui.QIcon(self._icon_path)
@ -34,7 +63,7 @@ class InstallDialog(QtWidgets.QDialog):
self.setMinimumSize(
QtCore.QSize(self._size_w, self._size_h))
self.setMaximumSize(
QtCore.QSize(self._size_w + 100, self._size_h + 100))
QtCore.QSize(self._size_w + 100, self._size_h + 500))
# style for normal console text
self.default_console_style = QtGui.QTextCharFormat()
@ -52,6 +81,7 @@ class InstallDialog(QtWidgets.QDialog):
os.path.join(
os.path.dirname(__file__), 'RobotoMono-Regular.ttf')
)
self._pype_run_ready = False
self._init_ui()
@ -70,7 +100,7 @@ class InstallDialog(QtWidgets.QDialog):
"""Welcome to <b>Pype</b>
<p>
We've detected <b>Pype</b> is not configured yet. But don't worry,
this is as easy as setting one thing.
this is as easy as setting one or two things.
<p>
""")
self.main_label.setWordWrap(True)
@ -80,11 +110,16 @@ class InstallDialog(QtWidgets.QDialog):
# --------------------------------------------------------------------
self.pype_path_label = QtWidgets.QLabel(
"""This can be either <b>Path to studio location</b>
or <b>Database connection string</b> or <b>Pype Token</b>.
"""This is <b>Path to studio location</b> where Pype versions
are stored. It will be pre-filled if your MongoDB connection is
already set and your studio defined this location.
<p>
Leave it empty if you want to use Pype version that come with this
installation.
Leave it empty if you want to install Pype version that comes with
this installation.
</p>
<p>
If you want to just try Pype without installing, hit the middle
button that states "run without installation".
</p>
"""
)
@ -98,9 +133,9 @@ class InstallDialog(QtWidgets.QDialog):
input_layout = QtWidgets.QHBoxLayout()
input_layout.setContentsMargins(0, 10, 0, 10)
self.user_input = QtWidgets.QLineEdit()
self.user_input = FocusHandlingLineEdit()
self.user_input.setPlaceholderText("Pype repository path or url")
self.user_input.setPlaceholderText("Path to Pype versions")
self.user_input.textChanged.connect(self._path_changed)
self.user_input.setStyleSheet(
("color: rgb(233, 233, 233);"
@ -129,15 +164,28 @@ class InstallDialog(QtWidgets.QDialog):
# Mongo box | OK button
# --------------------------------------------------------------------
self.mongo_label = QtWidgets.QLabel(
"""Enter URL for running MongoDB instance:"""
)
self.mongo_label.setWordWrap(True)
self.mongo_label.setStyleSheet("color: rgb(150, 150, 150);")
class MongoWidget(QtWidgets.QWidget):
"""Widget to input mongodb URL."""
def __init__(self, parent=None):
self._btn_mongo = None
super(MongoWidget, self).__init__(parent)
mongo_layout = QtWidgets.QHBoxLayout()
mongo_layout.setContentsMargins(0, 0, 0, 0)
self._mongo_input = QtWidgets.QLineEdit()
self._mongo_input = FocusHandlingLineEdit()
self._mongo_input.setPlaceholderText("Mongo URL")
self._mongo_input.textChanged.connect(self._mongo_changed)
self._mongo_input.focusIn.connect(self._focus_in)
self._mongo_input.focusOut.connect(self._focus_out)
self._mongo_input.setValidator(
MongoValidator(self._mongo_input))
self._mongo_input.setStyleSheet(
("color: rgb(233, 233, 233);"
"background-color: rgb(64, 64, 64);"
@ -148,16 +196,37 @@ class InstallDialog(QtWidgets.QDialog):
mongo_layout.addWidget(self._mongo_input)
self.setLayout(mongo_layout)
def _focus_out(self):
self.validate_url()
def _focus_in(self):
self._mongo_input.setStyleSheet(
"""
background-color: rgb(32, 32, 19);
color: rgb(255, 190, 15);
padding: 0.5em;
border: 1px solid rgb(64, 64, 32);
"""
)
def _mongo_changed(self, mongo: str):
self.parent().mongo_url = mongo
def get_mongo_url(self):
def get_mongo_url(self) -> str:
"""Helper to get url from parent."""
return self.parent().mongo_url
def set_mongo_url(self, mongo: str):
"""Helper to set url to parent.
Args:
mongo (str): mongodb url string.
"""
self._mongo_input.setText(mongo)
def set_valid(self):
"""Set valid state on mongo url input."""
self._mongo_input.setStyleSheet(
"""
background-color: rgb(19, 19, 19);
@ -166,20 +235,48 @@ class InstallDialog(QtWidgets.QDialog):
border: 1px solid rgb(32, 64, 32);
"""
)
self.parent().install_button.setEnabled(True)
def set_invalid(self):
"""Set invalid state on mongo url input."""
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);
border: 1px solid rgb(64, 32, 32);
"""
)
self.parent().install_button.setEnabled(False)
def set_read_only(self, state: bool):
"""Set input read-only."""
self._mongo_input.setReadOnly(state)
def validate_url(self) -> bool:
"""Validate if entered url is ok.
Returns:
True if url is valid monogo string.
"""
if self.parent().mongo_url == "":
return False
is_valid, reason_str = validate_mongo_connection(
self.parent().mongo_url
)
if not is_valid:
self.set_invalid()
self.parent().update_console(f"!!! {reason_str}", True)
return False
else:
self.set_valid()
return True
self._mongo = MongoWidget(self)
if self._mongo_url:
self._mongo.set_mongo_url(self._mongo_url)
if self.mongo_url:
self._mongo.set_mongo_url(self.mongo_url)
# Bottom button bar
# --------------------------------------------------------------------
@ -193,16 +290,29 @@ class InstallDialog(QtWidgets.QDialog):
pype_logo_label.setPixmap(pype_logo)
pype_logo_label.setContentsMargins(10, 0, 0, 10)
self._ok_button = QtWidgets.QPushButton("OK")
self._ok_button.setStyleSheet(
# install button - - - - - - - - - - - - - - - - - - - - - - - - - - -
self.install_button = QtWidgets.QPushButton("Install")
self.install_button.setStyleSheet(
("color: rgb(64, 64, 64);"
"background-color: rgb(72, 200, 150);"
"padding: 0.5em;")
)
self._ok_button.setMinimumSize(64, 24)
self._ok_button.setToolTip("Save and continue")
self._ok_button.clicked.connect(self._on_ok_clicked)
self.install_button.setMinimumSize(64, 24)
self.install_button.setToolTip("Install Pype")
self.install_button.clicked.connect(self._on_ok_clicked)
# run from current button - - - - - - - - - - - - - - - - - - - - - -
self.run_button = QtWidgets.QPushButton("Run without installation")
self.run_button.setStyleSheet(
("color: rgb(64, 64, 64);"
"background-color: rgb(200, 164, 64);"
"padding: 0.5em;")
)
self.run_button.setMinimumSize(64, 24)
self.run_button.setToolTip("Run without installing Pype")
self.run_button.clicked.connect(self._on_run_clicked)
# install button - - - - - - - - - - - - - - - - - - - - - - - - - - -
self._exit_button = QtWidgets.QPushButton("Exit")
self._exit_button.setStyleSheet(
("color: rgb(64, 64, 64);"
@ -210,14 +320,16 @@ class InstallDialog(QtWidgets.QDialog):
"padding: 0.5em;")
)
self._exit_button.setMinimumSize(64, 24)
self._exit_button.setToolTip("Exit without saving")
self._exit_button.setToolTip("Exit")
self._exit_button.clicked.connect(self._on_exit_clicked)
bottom_layout.setContentsMargins(0, 10, 0, 0)
bottom_layout.addWidget(pype_logo_label)
bottom_layout.setContentsMargins(0, 10, 10, 0)
bottom_layout.setAlignment(QtCore.Qt.AlignVCenter)
bottom_layout.addWidget(pype_logo_label, 0, QtCore.Qt.AlignVCenter)
bottom_layout.addStretch(1)
bottom_layout.addWidget(self._ok_button)
bottom_layout.addWidget(self._exit_button)
bottom_layout.addWidget(self.install_button, 0, QtCore.Qt.AlignVCenter)
bottom_layout.addWidget(self.run_button, 0, QtCore.Qt.AlignVCenter)
bottom_layout.addWidget(self._exit_button, 0, QtCore.Qt.AlignVCenter)
bottom_widget.setLayout(bottom_layout)
bottom_widget.setStyleSheet("background-color: rgb(32, 32, 32);")
@ -239,6 +351,7 @@ class InstallDialog(QtWidgets.QDialog):
color: rgb(72, 200, 150);
font-family: "Roboto Mono";
font-size: 0.5em;
border: 1px solid rgb(48, 48, 48);
}
QScrollBar:vertical {
border: 1px solid rgb(61, 115, 97);
@ -291,17 +404,25 @@ class InstallDialog(QtWidgets.QDialog):
"""
)
# add all to main
main.addWidget(self.main_label)
main.addWidget(self.pype_path_label)
main.addLayout(input_layout)
main.addWidget(self._mongo)
main.addStretch(1)
main.addWidget(self._status_label)
main.addWidget(self._status_box)
main.addWidget(self._progress_bar)
main.addWidget(bottom_widget)
main.addWidget(self.main_label, 0)
main.addWidget(self.pype_path_label, 0)
main.addLayout(input_layout, 0)
main.addWidget(self.mongo_label, 0)
main.addWidget(self._mongo, 0)
main.addWidget(self._status_label, 0)
main.addWidget(self._status_box, 1)
main.addWidget(self._progress_bar, 0)
main.addWidget(bottom_widget, 0)
self.setLayout(main)
# if mongo url is ok, try to get pype path from there
if self._mongo.validate_url() and len(self.path) == 0:
self.path = get_pype_path_from_db(self.mongo_url)
self.user_input.setText(self.path)
def _on_select_clicked(self):
"""Show directory dialog."""
options = QtWidgets.QFileDialog.Options()
@ -317,78 +438,81 @@ class InstallDialog(QtWidgets.QDialog):
if not result:
return
filename = result[0]
filename = QtCore.QDir.toNativeSeparators(filename)
filename = QtCore.QDir.toNativeSeparators(result)
if os.path.isdir(filename):
self.path = filename
self.user_input.setText(filename)
def _on_run_clicked(self):
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.done(2)
def _on_ok_clicked(self):
"""Start install process.
This will once again validate entered path and if ok, start
This will once again validate entered path and mongo if ok, start
working thread that will do actual job.
"""
valid, reason = validate_path_string(self._path)
valid, reason = validate_mongo_connection(
self._mongo.get_mongo_url()
)
if not valid:
self.user_input.setStyleSheet(
"""
background-color: rgb(32, 19, 19);
color: rgb(255, 69, 0);
padding: 0.5em;
border: 1px solid rgb(32, 64, 32);
"""
)
self._update_console(reason, True)
self._mongo.set_invalid()
self.update_console(f"!!! {reason}", True)
return
else:
self.user_input.setStyleSheet(
"""
background-color: rgb(19, 19, 19);
color: rgb(64, 230, 132);
padding: 0.5em;
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._mongo.set_valid()
if self._pype_run_ready:
self.done(3)
return
if self.path and len(self.path) > 0:
valid, reason = validate_path_string(self.path)
if not valid:
self.update_console(f"!!! {reason}", True)
return
self._disable_buttons()
self._install_thread = InstallThread(self)
self._install_thread.message.connect(self._update_console)
self._install_thread = InstallThread(
self.install_result_callback_handler, self)
self._install_thread.message.connect(self.update_console)
self._install_thread.progress.connect(self._update_progress)
self._install_thread.finished.connect(self._enable_buttons)
self._install_thread.set_path(self._path)
self._install_thread.set_path(self.path)
self._install_thread.set_mongo(self._mongo.get_mongo_url())
self._install_thread.start()
def install_result_callback_handler(self, result: InstallResult):
"""Change button behaviour based on installation outcome."""
status = result.status
if status >= 0:
self.install_button.setText("Run installed Pype")
self._pype_run_ready = True
def _update_progress(self, progress: int):
self._progress_bar.setValue(progress)
def _on_exit_clicked(self):
self.close()
self.reject()
def _path_changed(self, path: str) -> str:
"""Set path."""
self._path = path
if not self._path.startswith("mongodb"):
self._mongo.setVisible(True)
else:
self._mongo.setVisible(False)
if len(self._path) < 1:
self._mongo.setVisible(False)
self.path = path
return path
def _update_console(self, msg: str, error: bool = False) -> None:
def update_console(self, msg: str, error: bool = False) -> None:
"""Display message in console.
Args:
@ -404,36 +528,46 @@ class InstallDialog(QtWidgets.QDialog):
def _disable_buttons(self):
"""Disable buttons so user interaction doesn't interfere."""
self._btn_select.setEnabled(False)
self.run_button.setEnabled(False)
self._exit_button.setEnabled(False)
self._ok_button.setEnabled(False)
self.install_button.setEnabled(False)
self._controls_disabled = True
def _enable_buttons(self):
"""Enable buttons after operation is complete."""
self._btn_select.setEnabled(True)
self.run_button.setEnabled(True)
self._exit_button.setEnabled(True)
self._ok_button.setEnabled(True)
self.install_button.setEnabled(True)
self._controls_disabled = False
def closeEvent(self, event):
def closeEvent(self, event): # noqa
"""Prevent closing if window when controls are disabled."""
if self._controls_disabled:
return event.ignore()
return super(InstallDialog, self).closeEvent(event)
class PathValidator(QValidator):
class MongoValidator(QValidator):
"""Validate mongodb url for Qt widgets."""
def __init__(self, parent=None):
def __init__(self, parent=None, intermediate=False):
self.parent = parent
super(PathValidator, self).__init__(parent)
self.intermediate = intermediate
self._validate_lock = False
self.timer = QTimer()
self.timer.timeout.connect(self._unlock_validator)
super().__init__(parent)
def _unlock_validator(self):
self._validate_lock = False
def _return_state(
self, state: QValidator.State, reason: str, path: str, pos: int):
self, state: QValidator.State, reason: str, mongo: str):
"""Set stylesheets and actions on parent based on state.
Warning:
This will always return `QFileDialog.State.Acceptable` as
This will always return `QValidator.State.Acceptable` as
anything different will stop input to `QLineEdit`
"""
@ -445,10 +579,10 @@ class PathValidator(QValidator):
background-color: rgb(32, 19, 19);
color: rgb(255, 69, 0);
padding: 0.5em;
border: 1px solid rgb(32, 64, 32);
border: 1px solid rgb(64, 32, 32);
"""
)
elif state == QValidator.State.Intermediate:
elif state == QValidator.State.Intermediate and self.intermediate:
self.parent.setToolTip(reason)
self.parent.setStyleSheet(
"""
@ -469,42 +603,66 @@ class PathValidator(QValidator):
"""
)
return QValidator.State.Acceptable, path, len(path)
return QValidator.State.Acceptable, mongo, len(mongo)
def validate(self, path: str, pos: int) -> (QValidator.State, str, int):
"""Validate entered path.
def validate(self, mongo: str, pos: int) -> (QValidator.State, str, int): # noqa
"""Validate entered mongodb connection string.
It can be regular path - in that case we test if it does exist.
It can also be mongodb connection string. In that case we parse it
as url (it should start with `mongodb://` url schema.
As url (it should start with `mongodb://` or
`mongodb+srv:// url schema.
Args:
path (str): path, connection string url or pype token.
mongo (str): connection string url.
pos (int): current position.
Todo:
It can also be Pype token, binding it to Pype user account.
Returns:
(QValidator.State.Acceptable, str, int):
Indicate input state with color and always return
Acceptable state as we need to be able to edit input further.
"""
if path.startswith("mongodb"):
pos = len(path)
if not mongo.startswith("mongodb"):
return self._return_state(
QValidator.State.Intermediate, "", path, pos)
QValidator.State.Invalid, "need mongodb schema", mongo)
if len(path) < 6:
return self._return_state(
QValidator.State.Intermediate, "", path, pos)
return self._return_state(
QValidator.State.Intermediate, "", mongo)
valid, reason = validate_path_string(path)
if not valid:
class PathValidator(MongoValidator):
"""Validate mongodb url for Qt widgets."""
def validate(self, path: str, pos: int) -> (QValidator.State, str, int): # noqa
"""Validate path to be accepted by Igniter.
Args:
path (str): path to Pype.
pos (int): current position.
Returns:
(QValidator.State.Acceptable, str, int):
Indicate input state with color and always return
Acceptable state as we need to be able to edit input further.
"""
# allow empty path as that will use current version coming with
# Pype Igniter
if len(path) == 0:
return self._return_state(
QValidator.State.Invalid, reason, path, pos)
else:
return self._return_state(
QValidator.State.Acceptable, reason, path, pos)
QValidator.State.Acceptable, "Use version with Igniter", path)
if len(path) > 3:
valid, reason = validate_path_string(path)
if not valid:
return self._return_state(
QValidator.State.Invalid, reason, path)
else:
return self._return_state(
QValidator.State.Acceptable, reason, path)
class CollapsibleWidget(QtWidgets.QWidget):
"""Collapsible widget to hide mongo url in necessary."""
def __init__(self, parent=None, title: str = "", animation: int = 300):
self._mainLayout = QtWidgets.QGridLayout(parent)
@ -515,9 +673,9 @@ class CollapsibleWidget(QtWidgets.QWidget):
self._animation = animation
self._title = title
super(CollapsibleWidget, self).__init__(parent)
self._initUi()
self._init_ui()
def _initUi(self):
def _init_ui(self):
self._toggleButton.setStyleSheet(
"""QToolButton {
border: none;
@ -576,13 +734,13 @@ class CollapsibleWidget(QtWidgets.QWidget):
self._toggleAnimation.setDirection(direction)
self._toggleAnimation.start()
def setContentLayout(self, content_layout: QtWidgets.QLayout):
def setContentLayout(self, content_layout: QtWidgets.QLayout): # noqa
self._contentArea.setLayout(content_layout)
collapsed_height = \
self.sizeHint().height() - self._contentArea.maximumHeight()
content_height = self._contentArea.sizeHint().height()
for i in range(0, self._toggleAnimation.animationCount() - 1):
for i in range(self._toggleAnimation.animationCount() - 1):
sec_anim = self._toggleAnimation.animationAt(i)
sec_anim.setDuration(self._animation)
sec_anim.setStartValue(collapsed_height)
@ -593,7 +751,7 @@ class CollapsibleWidget(QtWidgets.QWidget):
con_anim.setDuration(self._animation)
con_anim.setStartValue(0)
con_anim.setEndValue(32)
con_anim.setEndValue(collapsed_height + content_height)
if __name__ == "__main__":

View file

@ -2,15 +2,27 @@
"""Working thread for installer."""
import os
import sys
from zipfile import ZipFile
from pathlib import Path
from Qt.QtCore import QThread, Signal
from Qt.QtCore import QThread, Signal, QObject # noqa
from .bootstrap_repos import (
BootstrapRepos,
PypeVersionInvalid,
PypeVersionIOError,
PypeVersionExists,
PypeVersion
)
from .bootstrap_repos import BootstrapRepos
from .bootstrap_repos import PypeVersion
from .tools import validate_mongo_connection
class InstallResult(QObject):
"""Used to pass results back."""
def __init__(self, value):
self.status = value
class InstallThread(QThread):
"""Install Worker thread.
@ -21,19 +33,18 @@ class InstallThread(QThread):
If path contains plain repositories, they are zipped and installed to
user data dir.
Attributes:
progress (Signal): signal reporting progress back o UI.
message (Signal): message displaying in UI console.
"""
progress = Signal(int)
message = Signal((str, bool))
finished = Signal(object)
def __init__(self, parent=None):
def __init__(self, callback, parent=None,):
self._mongo = None
self._path = None
self.result_callback = callback
QThread.__init__(self, parent)
self.finished.connect(callback)
def run(self):
"""Thread entry point.
@ -64,10 +75,12 @@ class InstallThread(QThread):
except ValueError:
self.message.emit(
"!!! We need MongoDB URL to proceed.", True)
self.finished.emit(InstallResult(-1))
return
else:
self._mongo = os.getenv("PYPE_MONGO")
else:
self.message.emit("Saving mongo connection string ...", False)
bs.registry.set_secure_item("pypeMongo", self._mongo)
os.environ["PYPE_MONGO"] = self._mongo
@ -77,7 +90,8 @@ class InstallThread(QThread):
detected = bs.find_pype(include_zips=True)
if detected:
if PypeVersion(version=local_version) < detected[-1]:
if PypeVersion(
version=local_version, path=Path()) < detected[-1]:
self.message.emit((
f"Latest installed version {detected[-1]} is newer "
f"then currently running {local_version}"
@ -85,14 +99,16 @@ class InstallThread(QThread):
self.message.emit("Skipping Pype install ...", False)
if detected[-1].path.suffix.lower() == ".zip":
bs.extract_pype(detected[-1])
self.finished.emit(InstallResult(0))
return
if PypeVersion(version=local_version) == detected[-1]:
if PypeVersion(version=local_version).get_main_version() == detected[-1].get_main_version(): # noqa
self.message.emit((
f"Latest installed version is the same as "
f"currently running {local_version}"
), False)
self.message.emit("Skipping Pype install ...", False)
self.finished.emit(InstallResult(0))
return
self.message.emit((
@ -100,11 +116,21 @@ class InstallThread(QThread):
f"currently running one {local_version}"
), False)
else:
# we cannot build install package from frozen code.
if getattr(sys, 'frozen', False):
self.message.emit("None detected.", True)
self.message.emit(("Please set path to Pype sources to "
"build installation."), False)
self.message.emit(("We will use Pype coming with "
"installer."), False)
pype_version = bs.create_version_from_frozen_code()
if not pype_version:
self.message.emit(
f"!!! Install failed - {pype_version}", True)
self.finished.emit(InstallResult(-1))
return
self.message.emit(f"Using: {pype_version}", False)
bs.install_version(pype_version)
self.message.emit(f"Installed as {pype_version}", False)
self.progress.emit(100)
self.finished.emit(InstallResult(1))
return
else:
self.message.emit("None detected.", False)
@ -112,31 +138,26 @@ class InstallThread(QThread):
self.message.emit(
f"We will use local Pype version {local_version}", False)
repo_file = bs.install_live_repos()
if not repo_file:
local_pype = bs.create_version_from_live_code()
if not local_pype:
self.message.emit(
f"!!! Install failed - {repo_file}", True)
f"!!! Install failed - {local_pype}", True)
self.finished.emit(InstallResult(-1))
return
destination = bs.data_dir / repo_file.stem
if destination.exists():
try:
destination.unlink()
except OSError as e:
self.message.emit(
f"!!! Cannot remove already existing {destination}",
True)
self.message.emit(e.strerror, True)
return
try:
bs.install_version(local_pype)
except (PypeVersionExists,
PypeVersionInvalid,
PypeVersionIOError) as e:
self.message.emit(f"Installed failed: ", True)
self.message.emit(str(e), True)
self.finished.emit(InstallResult(-1))
return
destination.mkdir(parents=True)
# extract zip there
self.message.emit("Extracting zip to destination ...", False)
with ZipFile(repo_file, "r") as zip_ref:
zip_ref.extractall(destination)
self.message.emit(f"Installed as {repo_file}", False)
self.message.emit(f"Installed as {local_pype}", False)
self.progress.emit(100)
return
else:
# if we have mongo connection string, validate it, set it to
# user settings and get PYPE_PATH from there.
@ -144,25 +165,46 @@ class InstallThread(QThread):
if not validate_mongo_connection(self._mongo):
self.message.emit(
f"!!! invalid mongo url {self._mongo}", True)
self.finished.emit(InstallResult(-1))
return
bs.registry.set_secure_item("pypeMongo", self._mongo)
os.environ["PYPE_MONGO"] = self._mongo
if os.getenv("PYPE_PATH") == self._path:
...
self.message.emit(f"processing {self._path}", True)
repo_file = bs.process_entered_location(self._path)
if not repo_file:
self.message.emit("!!! Cannot install", True)
self.finished.emit(InstallResult(-1))
return
self.progress.emit(100)
self.finished.emit(InstallResult(1))
return
def set_path(self, path: str) -> None:
"""Helper to set path.
Args:
path (str): Path to set.
"""
self._path = path
def set_mongo(self, mongo: str) -> None:
"""Helper to set mongo url.
Args:
mongo (str): Mongodb url.
"""
self._mongo = mongo
def set_progress(self, progress: int) -> None:
"""Helper to set progress bar.
Args:
progress (int): Progress in percents.
"""
self.progress.emit(progress)

View file

@ -6,11 +6,10 @@ Functions ``compose_url()`` and ``decompose_url()`` are the same as in
version is decided.
"""
import os
import uuid
from typing import Dict
from typing import Dict, Union
from urllib.parse import urlparse, parse_qs
from pathlib import Path
import platform
from pymongo import MongoClient
from pymongo.errors import ServerSelectionTimeoutError, InvalidURI
@ -115,10 +114,14 @@ def validate_mongo_connection(cnx: str) -> (bool, str):
if parsed.scheme not in ["mongodb", "mongodb+srv"]:
return False, "Not mongodb schema"
# we have mongo connection string. Let's try if we can connect.
components = decompose_url(cnx)
try:
components = decompose_url(cnx)
except RuntimeError:
return False, f"Invalid port specified."
mongo_args = {
"host": compose_url(**components),
"serverSelectionTimeoutMS": 1000
"serverSelectionTimeoutMS": 2000
}
port = components.get("port")
if port is not None:
@ -137,13 +140,32 @@ def validate_mongo_connection(cnx: str) -> (bool, str):
return True, "Connection is successful"
def validate_path_string(path: str) -> (bool, str):
"""Validate string if it is acceptable by **Igniter**.
`path` string can be either regular path, or mongodb url or Pype token.
def validate_mongo_string(mongo: str) -> (bool, str):
"""Validate string if it is mongo url acceptable by **Igniter**..
Args:
path (str): String to validate.
mongo (str): String to validate.
Returns:
(bool, str):
True if valid, False if not and in second part of tuple
the reason why it failed.
"""
if not mongo:
return True, "empty string"
parsed = urlparse(mongo)
if parsed.scheme in ["mongodb", "mongodb+srv"]:
return validate_mongo_connection(mongo)
return False, "not valid mongodb schema"
def validate_path_string(path: str) -> (bool, str):
"""Validate string if it is path to Pype repository.
Args:
path (str): Path to validate.
Returns:
(bool, str):
@ -152,22 +174,15 @@ def validate_path_string(path: str) -> (bool, str):
"""
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, TypeError):
# not uuid
if not os.path.exists(path):
return False, "Path doesn't exist or invalid token"
return True, "Path exists"
else:
# we have pype token
# todo: implement
return False, "Not implemented yet"
return False, "empty string"
if not Path(path).exists():
return False, "path doesn't exists"
if not Path(path).is_dir():
return False, "path is not directory"
return True, "valid path"
def load_environments(sections: list = None) -> dict:
@ -200,3 +215,43 @@ def load_environments(sections: list = None) -> dict:
merged_env = acre.append(merged_env, parsed_env)
return acre.compute(merged_env, cleanup=True)
def get_pype_path_from_db(url: str) -> Union[str, None]:
"""Get Pype path from database.
We are loading data from database `pype` and collection `settings`.
There we expect document type `global_settings`.
Args:
url (str): mongodb url.
Returns:
path to Pype or None if not found
"""
try:
components = decompose_url(url)
except RuntimeError:
return None
mongo_args = {
"host": compose_url(**components),
"serverSelectionTimeoutMS": 2000
}
port = components.get("port")
if port is not None:
mongo_args["port"] = int(port)
try:
client = MongoClient(**mongo_args)
except Exception:
return None
db = client.pype
col = db.settings
global_settings = col.find_one({"type": "global_settings"}, {"data": 1})
if not global_settings:
return None
global_settings.get("data", {})
return global_settings.get("pype_path", {}).get(platform.system().lower())

4
igniter/version.py Normal file
View file

@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
"""Definition of Igniter version."""
__version__ = "1.0.0"

336
poetry.lock generated
View file

@ -80,7 +80,7 @@ python-dateutil = ">=2.7.0"
[[package]]
name = "astroid"
version = "2.5"
version = "2.5.1"
description = "An abstract syntax tree for Python with inference support."
category = "dev"
optional = false
@ -234,7 +234,7 @@ test = ["flake8 (==3.7.8)", "hypothesis (==3.55.3)"]
[[package]]
name = "coverage"
version = "5.4"
version = "5.5"
description = "Code coverage measurement for Python"
category = "dev"
optional = false
@ -273,6 +273,21 @@ python-versions = ">=3.6"
[package.dependencies]
importlib-metadata = ">=3.1.1"
[[package]]
name = "dnspython"
version = "2.1.0"
description = "DNS toolkit"
category = "main"
optional = false
python-versions = ">=3.6"
[package.extras]
dnssec = ["cryptography (>=2.6)"]
doh = ["requests", "requests-toolbelt"]
idna = ["idna (>=2.1)"]
curio = ["curio (>=1.2)", "sniffio (>=1.1)"]
trio = ["trio (>=0.14.0)", "sniffio (>=1.1)"]
[[package]]
name = "docutils"
version = "0.16"
@ -331,7 +346,7 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
[[package]]
name = "google-api-core"
version = "1.26.0"
version = "1.26.1"
description = "Google API client core library"
category = "main"
optional = false
@ -369,7 +384,7 @@ uritemplate = ">=3.0.0,<4dev"
[[package]]
name = "google-auth"
version = "1.27.0"
version = "1.27.1"
description = "Google Authentication Library"
category = "main"
optional = false
@ -387,7 +402,7 @@ pyopenssl = ["pyopenssl (>=20.0.0)"]
[[package]]
name = "google-auth-httplib2"
version = "0.0.4"
version = "0.1.0"
description = "Google Authentication Library: httplib2 transport"
category = "main"
optional = false
@ -395,7 +410,7 @@ python-versions = "*"
[package.dependencies]
google-auth = "*"
httplib2 = ">=0.9.1"
httplib2 = ">=0.15.0"
six = "*"
[[package]]
@ -455,6 +470,14 @@ zipp = ">=0.5"
docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"]
testing = ["pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "pytest-enabler", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"]
[[package]]
name = "iniconfig"
version = "1.1.1"
description = "iniconfig: brain-dead simple config-ini parsing"
category = "dev"
optional = false
python-versions = "*"
[[package]]
name = "isort"
version = "5.7.0"
@ -538,7 +561,7 @@ format_nongpl = ["idna", "jsonpointer (>1.13)", "webcolors", "rfc3986-validator
[[package]]
name = "keyring"
version = "22.2.0"
version = "22.3.0"
description = "Store and access your passwords safely."
category = "main"
optional = false
@ -589,14 +612,6 @@ category = "dev"
optional = false
python-versions = "*"
[[package]]
name = "more-itertools"
version = "8.7.0"
description = "More routines for operating on iterables, beyond itertools"
category = "dev"
optional = false
python-versions = ">=3.5"
[[package]]
name = "multidict"
version = "5.1.0"
@ -661,7 +676,7 @@ six = "*"
[[package]]
name = "pillow"
version = "8.1.0"
version = "8.1.1"
description = "Python Imaging Library (Fork)"
category = "main"
optional = false
@ -683,7 +698,7 @@ dev = ["pre-commit", "tox"]
[[package]]
name = "protobuf"
version = "3.15.3"
version = "3.15.5"
description = "Protocol Buffers"
category = "main"
optional = false
@ -781,21 +796,21 @@ python-versions = ">=3.5"
[[package]]
name = "pylint"
version = "2.7.1"
version = "2.7.2"
description = "python code static checker"
category = "dev"
optional = false
python-versions = "~=3.6"
[package.dependencies]
astroid = "2.5.0"
astroid = ">=2.5.1,<2.6"
colorama = {version = "*", markers = "sys_platform == \"win32\""}
isort = ">=4.2.5,<6"
mccabe = ">=0.6,<0.7"
toml = ">=0.7.1"
[package.extras]
docs = ["sphinx (>=3.2,<4.0)", "python-docs-theme"]
docs = ["sphinx (==3.5.1)", "python-docs-theme (==2020.12)"]
[[package]]
name = "pymongo"
@ -906,25 +921,24 @@ python-versions = ">=3.5"
[[package]]
name = "pytest"
version = "5.4.3"
version = "6.2.2"
description = "pytest: simple powerful testing with Python"
category = "dev"
optional = false
python-versions = ">=3.5"
python-versions = ">=3.6"
[package.dependencies]
atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""}
attrs = ">=17.4.0"
attrs = ">=19.2.0"
colorama = {version = "*", markers = "sys_platform == \"win32\""}
importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""}
more-itertools = ">=4.0.0"
iniconfig = "*"
packaging = "*"
pluggy = ">=0.12,<1.0"
py = ">=1.5.0"
wcwidth = "*"
pluggy = ">=0.12,<1.0.0a1"
py = ">=1.8.2"
toml = "*"
[package.extras]
checkqa-mypy = ["mypy (==v0.761)"]
testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"]
[[package]]
@ -1314,8 +1328,8 @@ python-versions = "*"
[[package]]
name = "websocket-client"
version = "0.57.0"
description = "WebSocket client for Python. hybi13 is supported."
version = "0.58.0"
description = "WebSocket client for Python with low level API options"
category = "main"
optional = false
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
@ -1363,20 +1377,20 @@ typing-extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""}
[[package]]
name = "zipp"
version = "3.4.0"
version = "3.4.1"
description = "Backport of pathlib-compatible object wrapper for zip files"
category = "main"
optional = false
python-versions = ">=3.6"
[package.extras]
docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"]
testing = ["pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "jaraco.test (>=3.2.0)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"]
docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"]
testing = ["pytest (>=4.6)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "pytest-enabler", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"]
[metadata]
lock-version = "1.1"
python-versions = "3.7.*"
content-hash = "22fb546eefa7ced2769c1dcb3220fd2ef4b22f0dc8436e023774e50e2ee6bde1"
content-hash = "4905515073ad2bf2a8517d513d68e48669b6a829f24e540b2dd60bc70cbea26b"
[metadata.files]
acre = []
@ -1440,8 +1454,8 @@ arrow = [
{file = "arrow-0.17.0.tar.gz", hash = "sha256:ff08d10cda1d36c68657d6ad20d74fbea493d980f8b2d45344e00d6ed2bf6ed4"},
]
astroid = [
{file = "astroid-2.5-py3-none-any.whl", hash = "sha256:87ae7f2398b8a0ae5638ddecf9987f081b756e0e9fc071aeebdca525671fc4dc"},
{file = "astroid-2.5.tar.gz", hash = "sha256:b31c92f545517dcc452f284bc9c044050862fbe6d93d2b3de4a215a6b384bf0d"},
{file = "astroid-2.5.1-py3-none-any.whl", hash = "sha256:21d735aab248253531bb0f1e1e6d068f0ee23533e18ae8a6171ff892b98297cf"},
{file = "astroid-2.5.1.tar.gz", hash = "sha256:cfc35498ee64017be059ceffab0a25bedf7548ab76f2bea691c5565896e7128d"},
]
async-timeout = [
{file = "async-timeout-3.0.1.tar.gz", hash = "sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f"},
@ -1535,55 +1549,58 @@ commonmark = [
{file = "commonmark-0.9.1.tar.gz", hash = "sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60"},
]
coverage = [
{file = "coverage-5.4-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:6d9c88b787638a451f41f97446a1c9fd416e669b4d9717ae4615bd29de1ac135"},
{file = "coverage-5.4-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:66a5aae8233d766a877c5ef293ec5ab9520929c2578fd2069308a98b7374ea8c"},
{file = "coverage-5.4-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9754a5c265f991317de2bac0c70a746efc2b695cf4d49f5d2cddeac36544fb44"},
{file = "coverage-5.4-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:fbb17c0d0822684b7d6c09915677a32319f16ff1115df5ec05bdcaaee40b35f3"},
{file = "coverage-5.4-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:b7f7421841f8db443855d2854e25914a79a1ff48ae92f70d0a5c2f8907ab98c9"},
{file = "coverage-5.4-cp27-cp27m-win32.whl", hash = "sha256:4a780807e80479f281d47ee4af2eb2df3e4ccf4723484f77da0bb49d027e40a1"},
{file = "coverage-5.4-cp27-cp27m-win_amd64.whl", hash = "sha256:87c4b38288f71acd2106f5d94f575bc2136ea2887fdb5dfe18003c881fa6b370"},
{file = "coverage-5.4-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:c6809ebcbf6c1049002b9ac09c127ae43929042ec1f1dbd8bb1615f7cd9f70a0"},
{file = "coverage-5.4-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ba7ca81b6d60a9f7a0b4b4e175dcc38e8fef4992673d9d6e6879fd6de00dd9b8"},
{file = "coverage-5.4-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:89fc12c6371bf963809abc46cced4a01ca4f99cba17be5e7d416ed7ef1245d19"},
{file = "coverage-5.4-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:4a8eb7785bd23565b542b01fb39115a975fefb4a82f23d407503eee2c0106247"},
{file = "coverage-5.4-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:7e40d3f8eb472c1509b12ac2a7e24158ec352fc8567b77ab02c0db053927e339"},
{file = "coverage-5.4-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:1ccae21a076d3d5f471700f6d30eb486da1626c380b23c70ae32ab823e453337"},
{file = "coverage-5.4-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:755c56beeacac6a24c8e1074f89f34f4373abce8b662470d3aa719ae304931f3"},
{file = "coverage-5.4-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:322549b880b2d746a7672bf6ff9ed3f895e9c9f108b714e7360292aa5c5d7cf4"},
{file = "coverage-5.4-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:60a3307a84ec60578accd35d7f0c71a3a971430ed7eca6567399d2b50ef37b8c"},
{file = "coverage-5.4-cp35-cp35m-win32.whl", hash = "sha256:1375bb8b88cb050a2d4e0da901001347a44302aeadb8ceb4b6e5aa373b8ea68f"},
{file = "coverage-5.4-cp35-cp35m-win_amd64.whl", hash = "sha256:16baa799ec09cc0dcb43a10680573269d407c159325972dd7114ee7649e56c66"},
{file = "coverage-5.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:2f2cf7a42d4b7654c9a67b9d091ec24374f7c58794858bff632a2039cb15984d"},
{file = "coverage-5.4-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:b62046592b44263fa7570f1117d372ae3f310222af1fc1407416f037fb3af21b"},
{file = "coverage-5.4-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:812eaf4939ef2284d29653bcfee9665f11f013724f07258928f849a2306ea9f9"},
{file = "coverage-5.4-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:859f0add98707b182b4867359e12bde806b82483fb12a9ae868a77880fc3b7af"},
{file = "coverage-5.4-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:04b14e45d6a8e159c9767ae57ecb34563ad93440fc1b26516a89ceb5b33c1ad5"},
{file = "coverage-5.4-cp36-cp36m-win32.whl", hash = "sha256:ebfa374067af240d079ef97b8064478f3bf71038b78b017eb6ec93ede1b6bcec"},
{file = "coverage-5.4-cp36-cp36m-win_amd64.whl", hash = "sha256:84df004223fd0550d0ea7a37882e5c889f3c6d45535c639ce9802293b39cd5c9"},
{file = "coverage-5.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:1b811662ecf72eb2d08872731636aee6559cae21862c36f74703be727b45df90"},
{file = "coverage-5.4-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:6b588b5cf51dc0fd1c9e19f622457cc74b7d26fe295432e434525f1c0fae02bc"},
{file = "coverage-5.4-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:3fe50f1cac369b02d34ad904dfe0771acc483f82a1b54c5e93632916ba847b37"},
{file = "coverage-5.4-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:32ab83016c24c5cf3db2943286b85b0a172dae08c58d0f53875235219b676409"},
{file = "coverage-5.4-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:68fb816a5dd901c6aff352ce49e2a0ffadacdf9b6fae282a69e7a16a02dad5fb"},
{file = "coverage-5.4-cp37-cp37m-win32.whl", hash = "sha256:a636160680c6e526b84f85d304e2f0bb4e94f8284dd765a1911de9a40450b10a"},
{file = "coverage-5.4-cp37-cp37m-win_amd64.whl", hash = "sha256:bb32ca14b4d04e172c541c69eec5f385f9a075b38fb22d765d8b0ce3af3a0c22"},
{file = "coverage-5.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4d7165a4e8f41eca6b990c12ee7f44fef3932fac48ca32cecb3a1b2223c21f"},
{file = "coverage-5.4-cp38-cp38-manylinux1_i686.whl", hash = "sha256:a565f48c4aae72d1d3d3f8e8fb7218f5609c964e9c6f68604608e5958b9c60c3"},
{file = "coverage-5.4-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:fff1f3a586246110f34dc762098b5afd2de88de507559e63553d7da643053786"},
{file = "coverage-5.4-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:a839e25f07e428a87d17d857d9935dd743130e77ff46524abb992b962eb2076c"},
{file = "coverage-5.4-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:6625e52b6f346a283c3d563d1fd8bae8956daafc64bb5bbd2b8f8a07608e3994"},
{file = "coverage-5.4-cp38-cp38-win32.whl", hash = "sha256:5bee3970617b3d74759b2d2df2f6a327d372f9732f9ccbf03fa591b5f7581e39"},
{file = "coverage-5.4-cp38-cp38-win_amd64.whl", hash = "sha256:03ed2a641e412e42cc35c244508cf186015c217f0e4d496bf6d7078ebe837ae7"},
{file = "coverage-5.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:14a9f1887591684fb59fdba8feef7123a0da2424b0652e1b58dd5b9a7bb1188c"},
{file = "coverage-5.4-cp39-cp39-manylinux1_i686.whl", hash = "sha256:9564ac7eb1652c3701ac691ca72934dd3009997c81266807aef924012df2f4b3"},
{file = "coverage-5.4-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:0f48fc7dc82ee14aeaedb986e175a429d24129b7eada1b7e94a864e4f0644dde"},
{file = "coverage-5.4-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:107d327071061fd4f4a2587d14c389a27e4e5c93c7cba5f1f59987181903902f"},
{file = "coverage-5.4-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:0cdde51bfcf6b6bd862ee9be324521ec619b20590787d1655d005c3fb175005f"},
{file = "coverage-5.4-cp39-cp39-win32.whl", hash = "sha256:c67734cff78383a1f23ceba3b3239c7deefc62ac2b05fa6a47bcd565771e5880"},
{file = "coverage-5.4-cp39-cp39-win_amd64.whl", hash = "sha256:c669b440ce46ae3abe9b2d44a913b5fd86bb19eb14a8701e88e3918902ecd345"},
{file = "coverage-5.4-pp36-none-any.whl", hash = "sha256:c0ff1c1b4d13e2240821ef23c1efb1f009207cb3f56e16986f713c2b0e7cd37f"},
{file = "coverage-5.4-pp37-none-any.whl", hash = "sha256:cd601187476c6bed26a0398353212684c427e10a903aeafa6da40c63309d438b"},
{file = "coverage-5.4.tar.gz", hash = "sha256:6d2e262e5e8da6fa56e774fb8e2643417351427604c2b177f8e8c5f75fc928ca"},
{file = "coverage-5.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:b6d534e4b2ab35c9f93f46229363e17f63c53ad01330df9f2d6bd1187e5eaacf"},
{file = "coverage-5.5-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:b7895207b4c843c76a25ab8c1e866261bcfe27bfaa20c192de5190121770672b"},
{file = "coverage-5.5-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:c2723d347ab06e7ddad1a58b2a821218239249a9e4365eaff6649d31180c1669"},
{file = "coverage-5.5-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:900fbf7759501bc7807fd6638c947d7a831fc9fdf742dc10f02956ff7220fa90"},
{file = "coverage-5.5-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:004d1880bed2d97151facef49f08e255a20ceb6f9432df75f4eef018fdd5a78c"},
{file = "coverage-5.5-cp27-cp27m-win32.whl", hash = "sha256:06191eb60f8d8a5bc046f3799f8a07a2d7aefb9504b0209aff0b47298333302a"},
{file = "coverage-5.5-cp27-cp27m-win_amd64.whl", hash = "sha256:7501140f755b725495941b43347ba8a2777407fc7f250d4f5a7d2a1050ba8e82"},
{file = "coverage-5.5-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:372da284cfd642d8e08ef606917846fa2ee350f64994bebfbd3afb0040436905"},
{file = "coverage-5.5-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:8963a499849a1fc54b35b1c9f162f4108017b2e6db2c46c1bed93a72262ed083"},
{file = "coverage-5.5-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:869a64f53488f40fa5b5b9dcb9e9b2962a66a87dab37790f3fcfb5144b996ef5"},
{file = "coverage-5.5-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:4a7697d8cb0f27399b0e393c0b90f0f1e40c82023ea4d45d22bce7032a5d7b81"},
{file = "coverage-5.5-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:8d0a0725ad7c1a0bcd8d1b437e191107d457e2ec1084b9f190630a4fb1af78e6"},
{file = "coverage-5.5-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:51cb9476a3987c8967ebab3f0fe144819781fca264f57f89760037a2ea191cb0"},
{file = "coverage-5.5-cp310-cp310-win_amd64.whl", hash = "sha256:c0891a6a97b09c1f3e073a890514d5012eb256845c451bd48f7968ef939bf4ae"},
{file = "coverage-5.5-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:3487286bc29a5aa4b93a072e9592f22254291ce96a9fbc5251f566b6b7343cdb"},
{file = "coverage-5.5-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:deee1077aae10d8fa88cb02c845cfba9b62c55e1183f52f6ae6a2df6a2187160"},
{file = "coverage-5.5-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:f11642dddbb0253cc8853254301b51390ba0081750a8ac03f20ea8103f0c56b6"},
{file = "coverage-5.5-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:6c90e11318f0d3c436a42409f2749ee1a115cd8b067d7f14c148f1ce5574d701"},
{file = "coverage-5.5-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:30c77c1dc9f253283e34c27935fded5015f7d1abe83bc7821680ac444eaf7793"},
{file = "coverage-5.5-cp35-cp35m-win32.whl", hash = "sha256:9a1ef3b66e38ef8618ce5fdc7bea3d9f45f3624e2a66295eea5e57966c85909e"},
{file = "coverage-5.5-cp35-cp35m-win_amd64.whl", hash = "sha256:972c85d205b51e30e59525694670de6a8a89691186012535f9d7dbaa230e42c3"},
{file = "coverage-5.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:af0e781009aaf59e25c5a678122391cb0f345ac0ec272c7961dc5455e1c40066"},
{file = "coverage-5.5-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:74d881fc777ebb11c63736622b60cb9e4aee5cace591ce274fb69e582a12a61a"},
{file = "coverage-5.5-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:92b017ce34b68a7d67bd6d117e6d443a9bf63a2ecf8567bb3d8c6c7bc5014465"},
{file = "coverage-5.5-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:d636598c8305e1f90b439dbf4f66437de4a5e3c31fdf47ad29542478c8508bbb"},
{file = "coverage-5.5-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:41179b8a845742d1eb60449bdb2992196e211341818565abded11cfa90efb821"},
{file = "coverage-5.5-cp36-cp36m-win32.whl", hash = "sha256:040af6c32813fa3eae5305d53f18875bedd079960822ef8ec067a66dd8afcd45"},
{file = "coverage-5.5-cp36-cp36m-win_amd64.whl", hash = "sha256:5fec2d43a2cc6965edc0bb9e83e1e4b557f76f843a77a2496cbe719583ce8184"},
{file = "coverage-5.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:18ba8bbede96a2c3dde7b868de9dcbd55670690af0988713f0603f037848418a"},
{file = "coverage-5.5-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:2910f4d36a6a9b4214bb7038d537f015346f413a975d57ca6b43bf23d6563b53"},
{file = "coverage-5.5-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:f0b278ce10936db1a37e6954e15a3730bea96a0997c26d7fee88e6c396c2086d"},
{file = "coverage-5.5-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:796c9c3c79747146ebd278dbe1e5c5c05dd6b10cc3bcb8389dfdf844f3ead638"},
{file = "coverage-5.5-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:53194af30d5bad77fcba80e23a1441c71abfb3e01192034f8246e0d8f99528f3"},
{file = "coverage-5.5-cp37-cp37m-win32.whl", hash = "sha256:184a47bbe0aa6400ed2d41d8e9ed868b8205046518c52464fde713ea06e3a74a"},
{file = "coverage-5.5-cp37-cp37m-win_amd64.whl", hash = "sha256:2949cad1c5208b8298d5686d5a85b66aae46d73eec2c3e08c817dd3513e5848a"},
{file = "coverage-5.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:217658ec7187497e3f3ebd901afdca1af062b42cfe3e0dafea4cced3983739f6"},
{file = "coverage-5.5-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1aa846f56c3d49205c952d8318e76ccc2ae23303351d9270ab220004c580cfe2"},
{file = "coverage-5.5-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:24d4a7de75446be83244eabbff746d66b9240ae020ced65d060815fac3423759"},
{file = "coverage-5.5-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:d1f8bf7b90ba55699b3a5e44930e93ff0189aa27186e96071fac7dd0d06a1873"},
{file = "coverage-5.5-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:970284a88b99673ccb2e4e334cfb38a10aab7cd44f7457564d11898a74b62d0a"},
{file = "coverage-5.5-cp38-cp38-win32.whl", hash = "sha256:01d84219b5cdbfc8122223b39a954820929497a1cb1422824bb86b07b74594b6"},
{file = "coverage-5.5-cp38-cp38-win_amd64.whl", hash = "sha256:2e0d881ad471768bf6e6c2bf905d183543f10098e3b3640fc029509530091502"},
{file = "coverage-5.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d1f9ce122f83b2305592c11d64f181b87153fc2c2bbd3bb4a3dde8303cfb1a6b"},
{file = "coverage-5.5-cp39-cp39-manylinux1_i686.whl", hash = "sha256:13c4ee887eca0f4c5a247b75398d4114c37882658300e153113dafb1d76de529"},
{file = "coverage-5.5-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:52596d3d0e8bdf3af43db3e9ba8dcdaac724ba7b5ca3f6358529d56f7a166f8b"},
{file = "coverage-5.5-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:2cafbbb3af0733db200c9b5f798d18953b1a304d3f86a938367de1567f4b5bff"},
{file = "coverage-5.5-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:44d654437b8ddd9eee7d1eaee28b7219bec228520ff809af170488fd2fed3e2b"},
{file = "coverage-5.5-cp39-cp39-win32.whl", hash = "sha256:d314ed732c25d29775e84a960c3c60808b682c08d86602ec2c3008e1202e3bb6"},
{file = "coverage-5.5-cp39-cp39-win_amd64.whl", hash = "sha256:13034c4409db851670bc9acd836243aeee299949bd5673e11844befcb0149f03"},
{file = "coverage-5.5-pp36-none-any.whl", hash = "sha256:f030f8873312a16414c0d8e1a1ddff2d3235655a2174e3648b4fa66b3f2f1079"},
{file = "coverage-5.5-pp37-none-any.whl", hash = "sha256:2a3859cb82dcbda1cfd3e6f71c27081d18aa251d20a17d87d26d4cd216fb0af4"},
{file = "coverage-5.5.tar.gz", hash = "sha256:ebe78fe9a0e874362175b02371bdfbee64d8edc42a044253ddf4ee7d3c15212c"},
]
cryptography = [
{file = "cryptography-3.4.6-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:57ad77d32917bc55299b16d3b996ffa42a1c73c6cfa829b14043c561288d2799"},
@ -1610,6 +1627,10 @@ cx-freeze = [
{file = "cx_Freeze-6.5.3-cp39-cp39-win_amd64.whl", hash = "sha256:507bbaace2fd27edb0e6b024898ab2e4831d45d7238264f578a5e4fa70f065e5"},
{file = "cx_Freeze-6.5.3.tar.gz", hash = "sha256:e0d03cabcdf9b9c21354807ed9f06fa9481a8fd5a0838968a830f01a70820ff1"},
]
dnspython = [
{file = "dnspython-2.1.0-py3-none-any.whl", hash = "sha256:95d12f6ef0317118d2a1a6fc49aac65ffec7eb8087474158f42f26a639135216"},
{file = "dnspython-2.1.0.zip", hash = "sha256:e4a87f0b573201a0f3727fa18a516b055fd1107e0e5477cded4a2de497df1dd4"},
]
docutils = [
{file = "docutils-0.16-py2.py3-none-any.whl", hash = "sha256:0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af"},
{file = "docutils-0.16.tar.gz", hash = "sha256:c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc"},
@ -1629,20 +1650,20 @@ future = [
{file = "future-0.18.2.tar.gz", hash = "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"},
]
google-api-core = [
{file = "google-api-core-1.26.0.tar.gz", hash = "sha256:4230ec764d48ca934fe69b85cc217e31e844e176f68df93e252acd55350e730b"},
{file = "google_api_core-1.26.0-py2.py3-none-any.whl", hash = "sha256:002e44c533299aecd9dd265d200f9eacd9957cddd2c72e2cd1cb5cea127e972d"},
{file = "google-api-core-1.26.1.tar.gz", hash = "sha256:23b0df512c4cc8729793f8992edb350e3211f5fd0ec007afb1599864b421beef"},
{file = "google_api_core-1.26.1-py2.py3-none-any.whl", hash = "sha256:c383206f0f87545d3e658c4f8dc3b18a8457610fdbd791a15757c5b42d1e0e7f"},
]
google-api-python-client = [
{file = "google-api-python-client-1.12.8.tar.gz", hash = "sha256:f3b9684442eec2cfe9f9bb48e796ef919456b82142c7528c5fd527e5224f08bb"},
{file = "google_api_python_client-1.12.8-py2.py3-none-any.whl", hash = "sha256:3c4c4ca46b5c21196bec7ee93453443e477d82cbfa79234d1ce0645f81170eaf"},
]
google-auth = [
{file = "google-auth-1.27.0.tar.gz", hash = "sha256:da5218cbf33b8461d7661d6b4ad91c12c0107e2767904d5e3ae6408031d5463e"},
{file = "google_auth-1.27.0-py2.py3-none-any.whl", hash = "sha256:d3640ea61ee025d5af00e3ffd82ba0a06dd99724adaf50bdd52f49daf29f3f65"},
{file = "google-auth-1.27.1.tar.gz", hash = "sha256:d8958af6968e4ecd599f82357ebcfeb126f826ed0656126ad68416f810f7531e"},
{file = "google_auth-1.27.1-py2.py3-none-any.whl", hash = "sha256:63a5636d7eacfe6ef5b7e36e112b3149fa1c5b5ad77dd6df54910459bcd6b89f"},
]
google-auth-httplib2 = [
{file = "google-auth-httplib2-0.0.4.tar.gz", hash = "sha256:8d092cc60fb16517b12057ec0bba9185a96e3b7169d86ae12eae98e645b7bc39"},
{file = "google_auth_httplib2-0.0.4-py2.py3-none-any.whl", hash = "sha256:aeaff501738b289717fac1980db9711d77908a6c227f60e4aa1923410b43e2ee"},
{file = "google-auth-httplib2-0.1.0.tar.gz", hash = "sha256:a07c39fd632becacd3f07718dfd6021bf396978f03ad3ce4321d060015cc30ac"},
{file = "google_auth_httplib2-0.1.0-py2.py3-none-any.whl", hash = "sha256:31e49c36c6b5643b57e82617cb3e021e3e1d2df9da63af67252c02fa9c1f4a10"},
]
googleapis-common-protos = [
{file = "googleapis-common-protos-1.53.0.tar.gz", hash = "sha256:a88ee8903aa0a81f6c3cec2d5cf62d3c8aa67c06439b0496b49048fb1854ebf4"},
@ -1664,6 +1685,10 @@ importlib-metadata = [
{file = "importlib_metadata-3.7.0-py3-none-any.whl", hash = "sha256:c6af5dbf1126cd959c4a8d8efd61d4d3c83bddb0459a17e554284a077574b614"},
{file = "importlib_metadata-3.7.0.tar.gz", hash = "sha256:24499ffde1b80be08284100393955842be4a59c7c16bbf2738aad0e464a8e0aa"},
]
iniconfig = [
{file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"},
{file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"},
]
isort = [
{file = "isort-5.7.0-py3-none-any.whl", hash = "sha256:fff4f0c04e1825522ce6949973e83110a6e907750cd92d128b0d14aaaadbffdc"},
{file = "isort-5.7.0.tar.gz", hash = "sha256:c729845434366216d320e936b8ad6f9d681aab72dc7cbc2d51bedc3582f3ad1e"},
@ -1689,8 +1714,8 @@ jsonschema = [
{file = "jsonschema-3.2.0.tar.gz", hash = "sha256:c8a85b28d377cc7737e46e2d9f2b4f44ee3c0e1deac6bf46ddefc7187d30797a"},
]
keyring = [
{file = "keyring-22.2.0-py3-none-any.whl", hash = "sha256:8ae8e53d744e3e395e7402fd04c7474d46c8ad2d65e095bcde8a622dc643f7cd"},
{file = "keyring-22.2.0.tar.gz", hash = "sha256:c73c66c4ca89bee6a233b1638e1d2f5bcba4da35f8713ad4f98decc46e64cccd"},
{file = "keyring-22.3.0-py3-none-any.whl", hash = "sha256:2bc8363ebdd63886126a012057a85c8cb6e143877afa02619ac7dbc9f38a207b"},
{file = "keyring-22.3.0.tar.gz", hash = "sha256:16927a444b2c73f983520a48dec79ddab49fe76429ea05b8d528d778c8339522"},
]
lazy-object-proxy = [
{file = "lazy-object-proxy-1.5.2.tar.gz", hash = "sha256:5944a9b95e97de1980c65f03b79b356f30a43de48682b8bdd90aa5089f0ec1f4"},
@ -1779,10 +1804,6 @@ mccabe = [
{file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"},
{file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"},
]
more-itertools = [
{file = "more-itertools-8.7.0.tar.gz", hash = "sha256:c5d6da9ca3ff65220c3bfd2a8db06d698f05d4d2b9be57e1deb2be5a45019713"},
{file = "more_itertools-8.7.0-py3-none-any.whl", hash = "sha256:5652a9ac72209ed7df8d9c15daf4e1aa0e3d2ccd3c87f8265a0673cd9cbc9ced"},
]
multidict = [
{file = "multidict-5.1.0-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:b7993704f1a4b204e71debe6095150d43b2ee6150fa4f44d6d966ec356a8d61f"},
{file = "multidict-5.1.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:9dd6e9b1a913d096ac95d0399bd737e00f2af1e1594a787e00f7975778c8b2bf"},
@ -1836,64 +1857,65 @@ pathlib2 = [
{file = "pathlib2-2.3.5.tar.gz", hash = "sha256:6cd9a47b597b37cc57de1c05e56fb1a1c9cc9fab04fe78c29acd090418529868"},
]
pillow = [
{file = "Pillow-8.1.0-cp36-cp36m-macosx_10_10_x86_64.whl", hash = "sha256:d355502dce85ade85a2511b40b4c61a128902f246504f7de29bbeec1ae27933a"},
{file = "Pillow-8.1.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:93a473b53cc6e0b3ce6bf51b1b95b7b1e7e6084be3a07e40f79b42e83503fbf2"},
{file = "Pillow-8.1.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:2353834b2c49b95e1313fb34edf18fca4d57446675d05298bb694bca4b194174"},
{file = "Pillow-8.1.0-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:1d208e670abfeb41b6143537a681299ef86e92d2a3dac299d3cd6830d5c7bded"},
{file = "Pillow-8.1.0-cp36-cp36m-win32.whl", hash = "sha256:dd9eef866c70d2cbbea1ae58134eaffda0d4bfea403025f4db6859724b18ab3d"},
{file = "Pillow-8.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:b09e10ec453de97f9a23a5aa5e30b334195e8d2ddd1ce76cc32e52ba63c8b31d"},
{file = "Pillow-8.1.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:b02a0b9f332086657852b1f7cb380f6a42403a6d9c42a4c34a561aa4530d5234"},
{file = "Pillow-8.1.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:ca20739e303254287138234485579b28cb0d524401f83d5129b5ff9d606cb0a8"},
{file = "Pillow-8.1.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:604815c55fd92e735f9738f65dabf4edc3e79f88541c221d292faec1904a4b17"},
{file = "Pillow-8.1.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:cf6e33d92b1526190a1de904df21663c46a456758c0424e4f947ae9aa6088bf7"},
{file = "Pillow-8.1.0-cp37-cp37m-win32.whl", hash = "sha256:47c0d93ee9c8b181f353dbead6530b26980fe4f5485aa18be8f1fd3c3cbc685e"},
{file = "Pillow-8.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:96d4dc103d1a0fa6d47c6c55a47de5f5dafd5ef0114fa10c85a1fd8e0216284b"},
{file = "Pillow-8.1.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:7916cbc94f1c6b1301ac04510d0881b9e9feb20ae34094d3615a8a7c3db0dcc0"},
{file = "Pillow-8.1.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:3de6b2ee4f78c6b3d89d184ade5d8fa68af0848f9b6b6da2b9ab7943ec46971a"},
{file = "Pillow-8.1.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:cdbbe7dff4a677fb555a54f9bc0450f2a21a93c5ba2b44e09e54fcb72d2bd13d"},
{file = "Pillow-8.1.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:f50e7a98b0453f39000619d845be8b06e611e56ee6e8186f7f60c3b1e2f0feae"},
{file = "Pillow-8.1.0-cp38-cp38-win32.whl", hash = "sha256:cb192176b477d49b0a327b2a5a4979552b7a58cd42037034316b8018ac3ebb59"},
{file = "Pillow-8.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:6c5275bd82711cd3dcd0af8ce0bb99113ae8911fc2952805f1d012de7d600a4c"},
{file = "Pillow-8.1.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:165c88bc9d8dba670110c689e3cc5c71dbe4bfb984ffa7cbebf1fac9554071d6"},
{file = "Pillow-8.1.0-cp39-cp39-manylinux1_i686.whl", hash = "sha256:5e2fe3bb2363b862671eba632537cd3a823847db4d98be95690b7e382f3d6378"},
{file = "Pillow-8.1.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:7612520e5e1a371d77e1d1ca3a3ee6227eef00d0a9cddb4ef7ecb0b7396eddf7"},
{file = "Pillow-8.1.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:d673c4990acd016229a5c1c4ee8a9e6d8f481b27ade5fc3d95938697fa443ce0"},
{file = "Pillow-8.1.0-cp39-cp39-win32.whl", hash = "sha256:dc577f4cfdda354db3ae37a572428a90ffdbe4e51eda7849bf442fb803f09c9b"},
{file = "Pillow-8.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:22d070ca2e60c99929ef274cfced04294d2368193e935c5d6febfd8b601bf865"},
{file = "Pillow-8.1.0-pp36-pypy36_pp73-macosx_10_10_x86_64.whl", hash = "sha256:a3d3e086474ef12ef13d42e5f9b7bbf09d39cf6bd4940f982263d6954b13f6a9"},
{file = "Pillow-8.1.0-pp36-pypy36_pp73-manylinux2010_i686.whl", hash = "sha256:731ca5aabe9085160cf68b2dbef95fc1991015bc0a3a6ea46a371ab88f3d0913"},
{file = "Pillow-8.1.0-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:bba80df38cfc17f490ec651c73bb37cd896bc2400cfba27d078c2135223c1206"},
{file = "Pillow-8.1.0-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:c3d911614b008e8a576b8e5303e3db29224b455d3d66d1b2848ba6ca83f9ece9"},
{file = "Pillow-8.1.0-pp37-pypy37_pp73-manylinux2010_i686.whl", hash = "sha256:39725acf2d2e9c17356e6835dccebe7a697db55f25a09207e38b835d5e1bc032"},
{file = "Pillow-8.1.0-pp37-pypy37_pp73-manylinux2010_x86_64.whl", hash = "sha256:81c3fa9a75d9f1afafdb916d5995633f319db09bd773cb56b8e39f1e98d90820"},
{file = "Pillow-8.1.0-pp37-pypy37_pp73-win32.whl", hash = "sha256:b6f00ad5ebe846cc91763b1d0c6d30a8042e02b2316e27b05de04fa6ec831ec5"},
{file = "Pillow-8.1.0.tar.gz", hash = "sha256:887668e792b7edbfb1d3c9d8b5d8c859269a0f0eba4dda562adb95500f60dbba"},
{file = "Pillow-8.1.1-cp36-cp36m-macosx_10_10_x86_64.whl", hash = "sha256:14415e9e28410232370615dbde0cf0a00e526f522f665460344a5b96973a3086"},
{file = "Pillow-8.1.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:924fc33cb4acaf6267b8ca3b8f1922620d57a28470d5e4f49672cea9a841eb08"},
{file = "Pillow-8.1.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:df534e64d4f3e84e8f1e1a37da3f541555d947c1c1c09b32178537f0f243f69d"},
{file = "Pillow-8.1.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:4fe74636ee71c57a7f65d7b21a9f127d842b4fb75511e5d256ace258826eb352"},
{file = "Pillow-8.1.1-cp36-cp36m-win32.whl", hash = "sha256:3e759bcc03d6f39bc751e56d86bc87252b9a21c689a27c5ed753717a87d53a5b"},
{file = "Pillow-8.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:292f2aa1ae5c5c1451cb4b558addb88c257411d3fd71c6cf45562911baffc979"},
{file = "Pillow-8.1.1-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:8211cac9bf10461f9e33fe9a3af6c5131f3fdd0d10672afc2abb2c70cf95c5ca"},
{file = "Pillow-8.1.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:d30f30c044bdc0ab8f3924e1eeaac87e0ff8a27e87369c5cac4064b6ec78fd83"},
{file = "Pillow-8.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:7094bbdecb95ebe53166e4c12cf5e28310c2b550b08c07c5dc15433898e2238e"},
{file = "Pillow-8.1.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:1022f8f6dc3c5b0dcf928f1c49ba2ac73051f576af100d57776e2b65c1f76a8d"},
{file = "Pillow-8.1.1-cp37-cp37m-win32.whl", hash = "sha256:a7d690b2c5f7e4a932374615fedceb1e305d2dd5363c1de15961725fe10e7d16"},
{file = "Pillow-8.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:436b0a2dd9fe3f7aa6a444af6bdf53c1eb8f5ced9ea3ef104daa83f0ea18e7bc"},
{file = "Pillow-8.1.1-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:c448d2b335e21951416a30cd48d35588d122a912d5fe9e41900afacecc7d21a1"},
{file = "Pillow-8.1.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:bb18422ad00c1fecc731d06592e99c3be2c634da19e26942ba2f13d805005cf2"},
{file = "Pillow-8.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:3ec87bd1248b23a2e4e19e774367fbe30fddc73913edc5f9b37470624f55dc1f"},
{file = "Pillow-8.1.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:99ce3333b40b7a4435e0a18baad468d44ab118a4b1da0af0a888893d03253f1d"},
{file = "Pillow-8.1.1-cp38-cp38-win32.whl", hash = "sha256:2f0d7034d5faae9a8d1019d152ede924f653df2ce77d3bba4ce62cd21b5f94ae"},
{file = "Pillow-8.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:07872f1d8421db5a3fe770f7480835e5e90fddb58f36c216d4a2ac0d594de474"},
{file = "Pillow-8.1.1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:69da5b1d7102a61ce9b45deb2920a2012d52fd8f4201495ea9411d0071b0ec22"},
{file = "Pillow-8.1.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2a40d7d4b17db87f5b9a1efc0aff56000e1d0d5ece415090c102aafa0ccbe858"},
{file = "Pillow-8.1.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:01bb0a34f1a6689b138c0089d670ae2e8f886d2666a9b2f2019031abdea673c4"},
{file = "Pillow-8.1.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:43b3c859912e8bf754b3c5142df624794b18eb7ae07cfeddc917e1a9406a3ef2"},
{file = "Pillow-8.1.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:3b13d89d97b551e02549d1f0edf22bed6acfd6fd2e888cd1e9a953bf215f0e81"},
{file = "Pillow-8.1.1-cp39-cp39-win32.whl", hash = "sha256:c143c409e7bc1db784471fe9d0bf95f37c4458e879ad84cfae640cb74ee11a26"},
{file = "Pillow-8.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:1c5e3c36f02c815766ae9dd91899b1c5b4652f2a37b7a51609f3bd467c0f11fb"},
{file = "Pillow-8.1.1-pp36-pypy36_pp73-macosx_10_10_x86_64.whl", hash = "sha256:8cf77e458bd996dc85455f10fe443c0c946f5b13253773439bcbec08aa1aebc2"},
{file = "Pillow-8.1.1-pp36-pypy36_pp73-manylinux2010_i686.whl", hash = "sha256:c10af40ee2f1a99e1ae755ab1f773916e8bca3364029a042cd9161c400416bd8"},
{file = "Pillow-8.1.1-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:ff83dfeb04c98bb3e7948f876c17513a34e9a19fd92e292288649164924c1b39"},
{file = "Pillow-8.1.1-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:b9af590adc1e46898a1276527f3cfe2da8048ae43fbbf9b1bf9395f6c99d9b47"},
{file = "Pillow-8.1.1-pp37-pypy37_pp73-manylinux2010_i686.whl", hash = "sha256:172acfaf00434a28dddfe592d83f2980e22e63c769ff4a448ddf7b7a38ffd165"},
{file = "Pillow-8.1.1-pp37-pypy37_pp73-manylinux2010_x86_64.whl", hash = "sha256:33fdbd4f5608c852d97264f9d2e3b54e9e9959083d008145175b86100b275e5b"},
{file = "Pillow-8.1.1-pp37-pypy37_pp73-win32.whl", hash = "sha256:59445af66b59cc39530b4f810776928d75e95f41e945f0c32a3de4aceb93c15d"},
{file = "Pillow-8.1.1.tar.gz", hash = "sha256:f6fc18f9c9c7959bf58e6faf801d14fafb6d4717faaf6f79a68c8bb2a13dcf20"},
]
pluggy = [
{file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"},
{file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"},
]
protobuf = [
{file = "protobuf-3.15.3-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:822341974a28d895f0b39df13b3e2f27577498c1d85b5e876ff1d53fbdf2ef97"},
{file = "protobuf-3.15.3-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:31460706eb7c3bcc3c153877e580b78efa624b9626bd084fb882f20681ffa81a"},
{file = "protobuf-3.15.3-cp35-cp35m-macosx_10_9_intel.whl", hash = "sha256:405fbe27eeccd90b07e7cc20f2bcce477a86027435016aef71f15473dede92b5"},
{file = "protobuf-3.15.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:a98195be29b2622961896893a6e92a4e0812e4e367078ac20f0c7982e51ff7ea"},
{file = "protobuf-3.15.3-cp35-cp35m-win32.whl", hash = "sha256:7141c37a5af565908c3da10575c517c59a8e67591c507cf36f2655590200ddfc"},
{file = "protobuf-3.15.3-cp35-cp35m-win_amd64.whl", hash = "sha256:ffc556af23c7e1278b43719999dd215619f73f8d42f40275c55a1de09938214f"},
{file = "protobuf-3.15.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f9aa4c216e48236c6af4e71f64afc0c13c12401d3067a323b9fe543bb676bac3"},
{file = "protobuf-3.15.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0c678e11b7b6d6e5baa9710f44de01d48bc81b7db617ad5283a76f1f4c73df99"},
{file = "protobuf-3.15.3-cp36-cp36m-win32.whl", hash = "sha256:1fe832e1a5c51c71c2d6e949e597f3c47ef39c817264086293e4037941ab9bd7"},
{file = "protobuf-3.15.3-cp36-cp36m-win_amd64.whl", hash = "sha256:8a8157ff82760105cf435dbb8f4e7042a39c6d92f673fba8c2c815432b3f1063"},
{file = "protobuf-3.15.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:970b9f648fc12d28ce6f1f10575bbf063e828e1fd8d95339602cad2312a4fefa"},
{file = "protobuf-3.15.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:492beef9386706f84f683489fe18d1a7d8be5f4ab050782b3a484de1d1b01a69"},
{file = "protobuf-3.15.3-cp37-cp37m-win32.whl", hash = "sha256:af760e4fe6f30e1af3d5dac6767444ff61ef621ac857b3405b8f3cd29f16ac55"},
{file = "protobuf-3.15.3-cp37-cp37m-win_amd64.whl", hash = "sha256:21911af1fc692e7ca6a73c0fab3912a5d792ed7603350dbabd34a9722cbfe4d5"},
{file = "protobuf-3.15.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a3e18453d91040dad1985d1ea8a237fb7522a84fcefc17b452f756833b066d71"},
{file = "protobuf-3.15.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:d7761bd18fc3d197e50459c37abb95b64cd614e7b9014239a1e7c952433e380b"},
{file = "protobuf-3.15.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6f52c66f361de32096ba88c73ad0ff53585dafc569d8bf11968412175ddf297c"},
{file = "protobuf-3.15.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:3bbaed8e63cd62533a80adfa51f91b34bf7da29ac0335412dbebd21dac2d68b9"},
{file = "protobuf-3.15.3-py2.py3-none-any.whl", hash = "sha256:ad8e808b572e6ee38131e7b58d94aa5c438e3a3469d055e8989ea73a8e2308c0"},
{file = "protobuf-3.15.3.tar.gz", hash = "sha256:f3348af83391cdb842030e774d9bb01565ed4c62c93554cd1c69723411ec5e9d"},
{file = "protobuf-3.15.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:d26ed8dbdbe6b62cd24173c9ceb7588ae7831eec172ac002b095af091db01196"},
{file = "protobuf-3.15.5-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:9133b39924485ae43c02fc8274e57e5aa1706ad0970de49c72cfb8c0854d5f89"},
{file = "protobuf-3.15.5-cp35-cp35m-macosx_10_9_intel.whl", hash = "sha256:6bb44c15c98091e926a98362bff7fb24338bdf4001a6614834b8414c3b8593ee"},
{file = "protobuf-3.15.5-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:2d4cede5f2f2514df4a1eda1424a14d46daa5ea57963a1ea0fdab8d74ca2f9cd"},
{file = "protobuf-3.15.5-cp35-cp35m-win32.whl", hash = "sha256:ab735b3a4342004afa60ff580ce2be0f2aa784f1f69ee7f08a23ef26d22d811d"},
{file = "protobuf-3.15.5-cp35-cp35m-win_amd64.whl", hash = "sha256:a390e4bbb8232945fc8e4493c8b70949423a6dacee6f0353021b59c40b039e25"},
{file = "protobuf-3.15.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:dc7191b2e3361fdf2979e78a120a3a40e9d811318f6b2629036f53d9cb041c09"},
{file = "protobuf-3.15.5-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:762f6b9fb8025db34f762a860fd2b1473dfc84bcd0c3e4f396a695c83d733729"},
{file = "protobuf-3.15.5-cp36-cp36m-win32.whl", hash = "sha256:d1aab4d0aed36f7873734a243b46786d407cfa1010fae886249db56a1493a057"},
{file = "protobuf-3.15.5-cp36-cp36m-win_amd64.whl", hash = "sha256:119b4d308c87e833b6265b3922d5f5927e9d804605fcb1c1f771aa4d17e03591"},
{file = "protobuf-3.15.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c5b37b117ef89431149883d9b867c341a01f835142864722534885dcc1db6b1b"},
{file = "protobuf-3.15.5-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:f75aa0483fec2e4208bd4be18da0e3d7161dc74c65b6d6108f5968a8fe53a8ce"},
{file = "protobuf-3.15.5-cp37-cp37m-win32.whl", hash = "sha256:5d52d89e26adf0ba65193b6be39025c7766740ccc57fe9d10ddb709220b360d9"},
{file = "protobuf-3.15.5-cp37-cp37m-win_amd64.whl", hash = "sha256:87b5bc2ff944810a918628fc1f45f766acab23e1fecb0634fcf86cda554b30c4"},
{file = "protobuf-3.15.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:282385b8dd168b0f71f2ffca74c1fb39377f42217830ab492a0b64cbe14f86c1"},
{file = "protobuf-3.15.5-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:9f4445f197f779cd5b37c9d5d4aeb0d1999c1df7d143a9bce21d03dac8dba205"},
{file = "protobuf-3.15.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ac7c7a2b271307787ccdc0a45278827f36f72aba5040eadefff129b869068797"},
{file = "protobuf-3.15.5-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:8090b77f0791560b3c01263f6222006fe4c1d1d526539344afc4ecd9bd3e56f2"},
{file = "protobuf-3.15.5-py2.py3-none-any.whl", hash = "sha256:dbb98adb4281684eb54ce1f003b574bbc5768b9f614d7faa2c56f30e18519ec7"},
{file = "protobuf-3.15.5.tar.gz", hash = "sha256:be8a929c6178bb6cbe9e2c858be62fa08966a39ae758a8493a88f0ed1efb6097"},
]
py = [
{file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"},
@ -1958,8 +1980,8 @@ pygments = [
{file = "Pygments-2.8.0.tar.gz", hash = "sha256:37a13ba168a02ac54cc5891a42b1caec333e59b66addb7fa633ea8a6d73445c0"},
]
pylint = [
{file = "pylint-2.7.1-py3-none-any.whl", hash = "sha256:a251b238db462b71d25948f940568bb5b3ae0e37dbaa05e10523f54f83e6cc7e"},
{file = "pylint-2.7.1.tar.gz", hash = "sha256:81ce108f6342421169ea039ff1f528208c99d2e5a9c4ca95cfc5291be6dfd982"},
{file = "pylint-2.7.2-py3-none-any.whl", hash = "sha256:d09b0b07ba06bcdff463958f53f23df25e740ecd81895f7d2699ec04bbd8dc3b"},
{file = "pylint-2.7.2.tar.gz", hash = "sha256:0e21d3b80b96740909d77206d741aa3ce0b06b41be375d92e1f3244a274c1f8a"},
]
pymongo = [
{file = "pymongo-3.11.3-cp27-cp27m-macosx_10_14_intel.whl", hash = "sha256:4d959e929cec805c2bf391418b1121590b4e7d5cb00af7b1ba521443d45a0918"},
@ -2100,8 +2122,8 @@ pyrsistent = [
{file = "pyrsistent-0.17.3.tar.gz", hash = "sha256:2e636185d9eb976a18a8a8e96efce62f2905fea90041958d8cc2a189756ebf3e"},
]
pytest = [
{file = "pytest-5.4.3-py3-none-any.whl", hash = "sha256:5c0db86b698e8f170ba4582a492248919255fcd4c79b1ee64ace34301fb589a1"},
{file = "pytest-5.4.3.tar.gz", hash = "sha256:7979331bfcba207414f5e1263b5a0f8f521d0f457318836a7355531ed1a4c7d8"},
{file = "pytest-6.2.2-py3-none-any.whl", hash = "sha256:b574b57423e818210672e07ca1fa90aaf194a4f63f3ab909a2c67ebb22913839"},
{file = "pytest-6.2.2.tar.gz", hash = "sha256:9d1edf9e7d0b84d72ea3dbcdfd22b35fb543a5e8f2a60092dd578936bf63d7f9"},
]
pytest-cov = [
{file = "pytest-cov-2.11.1.tar.gz", hash = "sha256:359952d9d39b9f822d9d29324483e7ba04a3a17dd7d05aa6beb7ea01e359e5f7"},
@ -2275,8 +2297,8 @@ wcwidth = [
{file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"},
]
websocket-client = [
{file = "websocket_client-0.57.0-py2.py3-none-any.whl", hash = "sha256:0fc45c961324d79c781bab301359d5a1b00b13ad1b10415a4780229ef71a5549"},
{file = "websocket_client-0.57.0.tar.gz", hash = "sha256:d735b91d6d1692a6a181f2a8c9e0238e5f6373356f561bb9dc4c7af36f452010"},
{file = "websocket_client-0.58.0-py2.py3-none-any.whl", hash = "sha256:44b5df8f08c74c3d82d28100fdc81f4536809ce98a17f0757557813275fbb663"},
{file = "websocket_client-0.58.0.tar.gz", hash = "sha256:63509b41d158ae5b7f67eb4ad20fecbb4eee99434e73e140354dc3ff8e09716f"},
]
wrapt = [
{file = "wrapt-1.12.1.tar.gz", hash = "sha256:b62ffa81fb85f4332a4f609cab4ac40709470da05643a082ec1eb88e6d9b97d7"},
@ -2325,6 +2347,6 @@ yarl = [
{file = "yarl-1.6.3.tar.gz", hash = "sha256:8a9066529240171b68893d60dca86a763eae2139dd42f42106b03cf4b426bf10"},
]
zipp = [
{file = "zipp-3.4.0-py3-none-any.whl", hash = "sha256:102c24ef8f171fd729d46599845e95c7ab894a4cf45f5de11a44cc7444fb1108"},
{file = "zipp-3.4.0.tar.gz", hash = "sha256:ed5eee1974372595f9e416cc7bbeeb12335201d8081ca8a0743c954d4446e5cb"},
{file = "zipp-3.4.1-py3-none-any.whl", hash = "sha256:51cb66cc54621609dd593d1787f286ee42a5c0adbb4b29abea5a63edc3e03098"},
{file = "zipp-3.4.1.tar.gz", hash = "sha256:3607921face881ba3e026887d8150cca609d517579abe052ac81fc5aeffdbd76"},
]

View file

@ -104,15 +104,6 @@ class PypeCommands:
def texture_copy(self, project, asset, path):
pass
def run_pype_tests(self, keyword, id):
pass
def make_docs(self):
pass
def pype_setup_coverage(self):
pass
def run_application(self, app, project, asset, task, tools, arguments):
pass
@ -136,7 +127,7 @@ class PypeCommands:
bs.data_dir = out_path.parent
print(f">>> Creating zip in {bs.data_dir} ...")
repo_file = bs.install_live_repos()
repo_file = bs.create_version_from_live_code()
if not repo_file:
print("!!! Error while creating zip file.")
exit(1)

View file

@ -20,6 +20,7 @@ appdirs = "^1.4.3"
blessed = "^1.17" # pype terminal formatting
clique = "1.5.*"
Click = "^7"
dnspython = "^2.1.0"
ftrack-python-api = "2.0.*"
google-api-python-client = "^1.12.8" # sync server google support (should be separate?)
jsonschema = "^3.2.0"
@ -52,7 +53,7 @@ Jinja2 = "^2.11"
pycodestyle = "^2.5.0"
pydocstyle = "^3.0.0"
pylint = "^2.4.4"
pytest = "^5.3.2"
pytest = "^6.1"
pytest-cov = "*"
pytest-print = "*"
Sphinx = "*"

@ -1 +1 @@
Subproject commit 8d3364dc8ae73a33726ba3279ff75adff73c6239
Subproject commit 9e6b0d02e5a147cbafdcaeee7d786d4767e14c94

@ -0,0 +1 @@
Subproject commit 7adabe8f0e6858bfe5b6bf0b39bd428ed72d0452

View file

@ -112,7 +112,7 @@ if getattr(sys, 'frozen', False):
os.environ["PYTHONPATH"] = os.pathsep.join(paths)
from igniter import BootstrapRepos # noqa: E402
from igniter.tools import load_environments # noqa: E402
from igniter.tools import load_environments, get_pype_path_from_db # noqa
from igniter.bootstrap_repos import PypeVersion # noqa: E402
bootstrap = BootstrapRepos()
@ -134,14 +134,21 @@ def set_environments() -> None:
os.path.dirname(sys.executable),
"dependencies"
))
import acre
try:
import acre
except ImportError as e:
# giving up
print("!!! cannot import acre")
print(f"{e}")
sys.exit(1)
try:
env = load_environments(["global"])
except OSError as e:
print(f"!!! {e}")
sys.exit(1)
env = acre.merge(env, dict(os.environ))
# acre must be available here
env = acre.merge(env, dict(os.environ)) # noqa
os.environ.clear()
os.environ.update(env)
@ -269,7 +276,12 @@ def _process_arguments() -> tuple:
# this is helper to run igniter before anything else
if "igniter" in sys.argv:
import igniter
igniter.run()
return_code = igniter.open_dialog()
# this is when we want to run Pype without installing anything.
# or we are ready to run.
if return_code not in [2, 3]:
sys.exit(return_code)
return use_version, use_staging
@ -296,7 +308,9 @@ def _determine_mongodb() -> str:
except ValueError:
print("*** No DB connection string specified.")
print("--- launching setup UI ...")
run(["igniter"])
import igniter
igniter.open_dialog()
try:
pype_mongo = bootstrap.registry.get_secure_item("pypeMongo")
except ValueError:
@ -362,19 +376,41 @@ def _find_frozen_pype(use_version: str = None,
pype_version = None
pype_versions = bootstrap.find_pype(include_zips=True,
staging=use_staging)
try:
# use latest one found (last in the list is latest)
pype_version = pype_versions[-1]
except IndexError:
# no pype version found, run Igniter and ask for them.
print('*** No Pype versions found.')
print("--- launching setup UI ...")
run(["igniter"])
pype_versions = bootstrap.find_pype()
if not os.getenv("PYPE_TRYOUT"):
try:
# use latest one found (last in the list is latest)
pype_version = pype_versions[-1]
except IndexError:
# no pype version found, run Igniter and ask for them.
print('*** No Pype versions found.')
print("--- launching setup UI ...")
import igniter
return_code = igniter.open_dialog()
if return_code == 2:
os.environ["PYPE_TRYOUT"] = "1"
if return_code == 3:
# run Pype after installation
print('>>> Finding Pype again ...')
pype_versions = bootstrap.find_pype(staging=use_staging)
try:
pype_version = pype_versions[-1]
except IndexError:
print(("!!! Something is wrong and we didn't "
"found it again."))
pype_versions = None
sys.exit(1)
elif return_code != 2:
print(f" . finished ({return_code})")
sys.exit(return_code)
if not pype_versions:
# no Pype versions found anyway, lets use then the one
# shipped with frozen Pype
if not os.getenv("PYPE_TRYOUT"):
print("*** Still no luck finding Pype.")
print(("*** We'll try to use the one coming "
"with Pype installation."))
version_path = _bootstrap_from_code(use_version)
pype_version = PypeVersion(
version=BootstrapRepos.get_version(version_path),
@ -438,6 +474,8 @@ def _bootstrap_from_code(use_version):
pype_root = os.path.normpath(
os.path.dirname(sys.executable))
local_version = bootstrap.get_version(Path(pype_root))
print(f" - running version: {local_version}")
assert local_version
else:
pype_root = os.path.normpath(
os.path.dirname(os.path.realpath(__file__)))
@ -495,7 +533,6 @@ def _bootstrap_from_code(use_version):
def boot():
"""Bootstrap Pype."""
version_path = None
# ------------------------------------------------------------------------
# Play animation
@ -527,7 +564,7 @@ def boot():
os.environ["PYPE_MONGO"] = pype_mongo
# ------------------------------------------------------------------------
# Load environments from database
# Set environments - load Pype path from database (if set)
# ------------------------------------------------------------------------
# set PYPE_ROOT to running location until proper version can be
# determined.
@ -535,7 +572,15 @@ def boot():
os.environ["PYPE_ROOT"] = os.path.dirname(sys.executable)
else:
os.environ["PYPE_ROOT"] = os.path.dirname(__file__)
set_environments()
# No environment loading from settings until Pype version is established.
# set_environments()
# Get Pype path from database and set it to environment so Pype can
# find its versions there and bootstrap them.
pype_path = get_pype_path_from_db(pype_mongo)
if not os.getenv("PYPE_PATH") and pype_path:
os.environ["PYPE_PATH"] = pype_path
# ------------------------------------------------------------------------
# Find Pype versions
@ -564,10 +609,12 @@ def boot():
# delete Pype module and it's submodules from cache so it is used from
# specific version
modules_to_del = []
for module_name in tuple(sys.modules):
if module_name == "pype" or module_name.startswith("pype."):
modules_to_del.append(sys.modules.pop(module_name))
modules_to_del = [
sys.modules.pop(module_name)
for module_name in tuple(sys.modules)
if module_name == "pype" or module_name.startswith("pype.")
]
try:
for module_name in modules_to_del:
del sys.modules[module_name]
@ -581,7 +628,9 @@ def boot():
from pype.version import __version__
print(">>> loading environments ...")
# Must happen before `set_modules_environments`
print(" - for Avalon ...")
set_avalon_environments()
print(" - for modules ...")
set_modules_environments()
assert version_path, "Version path not defined."
@ -591,10 +640,7 @@ def boot():
t_width = 20
try:
t_width = os.get_terminal_size().columns - 2
except ValueError:
# running without terminal
pass
except OSError:
except (ValueError, OSError):
# running without terminal
pass
@ -608,7 +654,7 @@ def boot():
try:
cli.main(obj={}, prog_name="pype")
except Exception:
except Exception: # noqa
exc_info = sys.exc_info()
print("!!! Pype crashed:")
traceback.print_exception(*exc_info)

View file

@ -28,6 +28,7 @@ def test_pype_version():
v2 = PypeVersion(1, 2, 3, client="x")
assert str(v2) == "1.2.3-x"
assert v1 < v2
v3 = PypeVersion(1, 2, 3, variant="staging")
assert str(v3) == "1.2.3-staging"
@ -35,6 +36,7 @@ def test_pype_version():
v4 = PypeVersion(1, 2, 3, variant="staging", client="client")
assert str(v4) == "1.2.3-client-staging"
assert v3 < v4
assert v1 < v4
v5 = PypeVersion(1, 2, 3, variant="foo", client="x")
assert str(v5) == "1.2.3-x"
@ -55,6 +57,9 @@ def test_pype_version():
v10 = PypeVersion(1, 2, 2)
assert v10 < v1
v11 = PypeVersion(1, 2, 3, path=Path("/foo/bar"))
assert v10 < v11
assert v5 == v2
sort_versions = [
@ -141,7 +146,7 @@ def test_search_string_for_pype_version(printer):
def test_install_live_repos(fix_bootstrap, printer):
rf = fix_bootstrap.install_live_repos()
rf = fix_bootstrap.create_version_from_live_code()
sep = os.path.sep
expected_paths = [
f"{rf}{sep}avalon-core",

View file

@ -95,11 +95,16 @@ libs_dir = build_dir / "lib"
to_delete = []
_print("Finding duplicates ...")
deps_items = list(deps_dir.iterdir())
for d in libs_dir.iterdir():
if (deps_dir / d.name) in deps_dir.iterdir():
if (deps_dir / d.name) in deps_items:
to_delete.append(d)
_print(f"found {d}", 3)
# add pype and igniter in libs too
to_delete.append(libs_dir / "pype")
to_delete.append(libs_dir / "igniter")
# delete duplicates
_print(f"Deleting {len(to_delete)} duplicates ...")
for d in to_delete: