mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 21:04:40 +01:00
Merge pull request #1077 from pypeclub/feature/igniter-improvements
Igniter and Pype bootstrap improvements
This commit is contained in:
commit
68be90e31d
15 changed files with 1134 additions and 665 deletions
|
|
@ -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"
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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."""
|
||||
|
|
|
|||
|
|
@ -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__":
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
109
igniter/tools.py
109
igniter/tools.py
|
|
@ -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
4
igniter/version.py
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Definition of Igniter version."""
|
||||
|
||||
__version__ = "1.0.0"
|
||||
336
poetry.lock
generated
336
poetry.lock
generated
|
|
@ -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"},
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
1
repos/maya-look-assigner
Submodule
1
repos/maya-look-assigner
Submodule
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 7adabe8f0e6858bfe5b6bf0b39bd428ed72d0452
|
||||
98
start.py
98
start.py
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue