Merge branch 'develop' into feature/1004-deadline-better-handling-of-pype

This commit is contained in:
Milan Kolar 2021-03-11 12:45:51 +01:00
commit 7c4aa85de0
43 changed files with 2737 additions and 1660 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

4
igniter/version.py Normal file
View file

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

336
poetry.lock generated
View file

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

View file

@ -7,7 +7,7 @@ from maya import utils, cmds
from avalon import api as avalon
from avalon import pipeline
from avalon.maya import suspended_refresh
from avalon.maya.pipeline import IS_HEADLESS, _on_task_changed
from avalon.maya.pipeline import IS_HEADLESS
from avalon.tools import workfiles
from pyblish import api as pyblish
from pype.lib import any_outdated
@ -45,9 +45,7 @@ def install():
avalon.on("open", on_open)
avalon.on("new", on_new)
avalon.before("save", on_before_save)
log.info("Overriding existing event 'taskChanged'")
override_event("taskChanged", on_task_changed)
avalon.on("taskChanged", on_task_changed)
log.info("Setting default family states for loader..")
avalon.data["familiesStateToggled"] = ["imagesequence"]
@ -61,24 +59,6 @@ def uninstall():
menu.uninstall()
def override_event(event, callback):
"""
Override existing event callback
Args:
event (str): name of the event
callback (function): callback to be triggered
Returns:
None
"""
ref = weakref.WeakSet()
ref.add(callback)
pipeline._registered_event_handlers[event] = ref
def on_init(_):
avalon.logger.info("Running callback on init..")
@ -215,7 +195,6 @@ def on_new(_):
def on_task_changed(*args):
"""Wrapped function of app initialize and maya's on task changed"""
# Run
_on_task_changed()
with suspended_refresh():
lib.set_context_settings()
lib.update_content_on_context_change()

View file

@ -72,7 +72,7 @@ class GenerateUUIDsOnInvalidAction(pyblish.api.Action):
nodes (list): all nodes to regenerate ids on
"""
from pype.hosts.maya import lib
from pype.hosts.maya.api import lib
import avalon.io as io
asset = instance.data['asset']

View file

@ -2127,15 +2127,9 @@ def bake_to_world_space(nodes,
def load_capture_preset(path=None, data=None):
import capture_gui
import capture
if data:
preset = data
else:
path = path
preset = capture_gui.lib.load_json(path)
print(preset)
preset = data
options = dict()
@ -2177,29 +2171,27 @@ def load_capture_preset(path=None, data=None):
temp_options2 = {}
id = 'Viewport Options'
light_options = {
0: "default",
1: 'all',
2: 'selected',
3: 'flat',
4: 'nolights'}
for key in preset[id]:
if key == 'high_quality':
if preset[id][key] == True:
temp_options2['multiSampleEnable'] = True
temp_options2['multiSampleCount'] = 4
temp_options2['textureMaxResolution'] = 1024
if key == 'textureMaxResolution':
if preset[id][key] > 0:
temp_options2['textureMaxResolution'] = preset[id][key]
temp_options2['enableTextureMaxRes'] = True
temp_options2['textureMaxResMode'] = 1
else:
temp_options2['multiSampleEnable'] = False
temp_options2['multiSampleCount'] = 4
temp_options2['textureMaxResolution'] = 512
temp_options2['enableTextureMaxRes'] = True
temp_options2['textureMaxResolution'] = preset[id][key]
temp_options2['enableTextureMaxRes'] = False
temp_options2['textureMaxResMode'] = 0
if key == 'multiSample':
if preset[id][key] > 0:
temp_options2['multiSampleEnable'] = True
temp_options2['multiSampleCount'] = preset[id][key]
else:
temp_options2['multiSampleEnable'] = False
temp_options2['multiSampleCount'] = preset[id][key]
if key == 'ssaoEnable':
if preset[id][key] == True:
if preset[id][key] is True:
temp_options2['ssaoEnable'] = True
else:
temp_options2['ssaoEnable'] = False
@ -2211,18 +2203,17 @@ def load_capture_preset(path=None, data=None):
if key == 'headsUpDisplay':
temp_options['headsUpDisplay'] = True
if key == 'displayLights':
temp_options[str(key)] = light_options[preset[id][key]]
else:
temp_options[str(key)] = preset[id][key]
for key in ['override_viewport_options',
'high_quality',
'alphaCut',
'gpuCacheDisplayFilter']:
temp_options.pop(key, None)
for key in ['ssaoEnable']:
'gpuCacheDisplayFilter',
'multiSample',
'ssaoEnable',
'textureMaxResolution'
]:
temp_options.pop(key, None)
options['viewport_options'] = temp_options
@ -2686,7 +2677,7 @@ def update_content_on_context_change():
def show_message(title, msg):
from avalon.vendor.Qt import QtWidgets
from ...widgets import message_window
from pype.widgets import message_window
# Find maya main window
top_level_widgets = {w.objectName(): w for w in

View file

@ -23,6 +23,7 @@ class ExtractPlayblast(pype.api.Extractor):
hosts = ["maya"]
families = ["review"]
optional = True
capture_preset = {}
def process(self, instance):
self.log.info("Extracting capture..")
@ -43,15 +44,9 @@ class ExtractPlayblast(pype.api.Extractor):
# get cameras
camera = instance.data['review_camera']
capture_preset = (
instance.context.data['project_settings']['maya']['capture']
)
try:
preset = lib.load_capture_preset(data=capture_preset)
except Exception:
preset = {}
self.log.info('using viewport preset: {}'.format(preset))
preset = lib.load_capture_preset(data=self.capture_preset)
preset['camera'] = camera
preset['format'] = "image"
@ -101,6 +96,9 @@ class ExtractPlayblast(pype.api.Extractor):
# Remove panel key since it's internal value to capture_gui
preset.pop("panel", None)
self.log.info('using viewport preset: {}'.format(preset))
path = capture.capture(**preset)
playblast = self._fix_playblast_output_path(path)

View file

@ -34,7 +34,7 @@ class ExtractThumbnail(pype.api.Extractor):
capture_preset = ""
capture_preset = (
instance.context.data["project_settings"]['maya']['capture']
instance.context.data["project_settings"]['maya']['publish']['ExtractPlayblast']
)
try:

View file

@ -95,6 +95,7 @@ class LoadMov(api.Loader):
containerise,
viewer_update_and_undo_stop
)
version = context['version']
version_data = version.get("data", {})
repr_id = context["representation"]["_id"]

View file

@ -73,5 +73,17 @@ class CreateImage(pype.api.Creator):
groups.append(group)
for group in groups:
long_names = []
if group.long_name:
for directory in group.long_name[::-1]:
name = directory.replace(stub.PUBLISH_ICON, '').\
replace(stub.LOADED_ICON, '')
long_names.append(name)
self.data.update({"subset": "image" + group.name})
self.data.update({"uuid": str(group.id)})
self.data.update({"long_name": "_".join(long_names)})
stub.imprint(group, self.data)
# reusing existing group, need to rename afterwards
if not create_group:
stub.rename_layer(group.id, stub.PUBLISH_ICON + group.name)

View file

@ -24,6 +24,7 @@ class CollectInstances(pyblish.api.ContextPlugin):
stub = photoshop.stub()
layers = stub.get_layers()
layers_meta = stub.get_layers_metadata()
instance_names = []
for layer in layers:
layer_data = stub.read(layer, layers_meta)
@ -41,14 +42,20 @@ class CollectInstances(pyblish.api.ContextPlugin):
# self.log.info("%s skipped, it was empty." % layer.Name)
# continue
instance = context.create_instance(layer.name)
instance = context.create_instance(layer_data["subset"])
instance.append(layer)
instance.data.update(layer_data)
instance.data["families"] = self.families_mapping[
layer_data["family"]
]
instance.data["publish"] = layer.visible
instance_names.append(layer_data["subset"])
# Produce diagnostic message for any graphical
# user interface interested in visualising it.
self.log.info("Found: \"%s\" " % instance.data["name"])
self.log.info("instance: {} ".format(instance.data))
if len(instance_names) != len(set(instance_names)):
self.log.warning("Duplicate instances found. " +
"Remove unwanted via SubsetManager")

View file

@ -25,11 +25,15 @@ class ValidateNamingRepair(pyblish.api.Action):
for instance in instances:
self.log.info("validate_naming instance {}".format(instance))
name = instance.data["name"].replace(" ", "_")
name = name.replace(instance.data["family"], '')
instance[0].Name = name
data = stub.read(instance[0])
data["subset"] = "image" + name
stub.imprint(instance[0], data)
name = stub.PUBLISH_ICON + name
stub.rename_layer(instance.data["uuid"], name)
return True
@ -46,8 +50,11 @@ class ValidateNaming(pyblish.api.InstancePlugin):
actions = [ValidateNamingRepair]
def process(self, instance):
msg = "Name \"{}\" is not allowed.".format(instance.data["name"])
help_msg = ' Use Repair action (A) in Pyblish to fix it.'
msg = "Name \"{}\" is not allowed.{}".format(instance.data["name"],
help_msg)
assert " " not in instance.data["name"], msg
msg = "Subset \"{}\" is not allowed.".format(instance.data["subset"])
msg = "Subset \"{}\" is not allowed.{}".format(instance.data["subset"],
help_msg)
assert " " not in instance.data["subset"], msg

View file

@ -0,0 +1,26 @@
import pyblish.api
import pype.api
class ValidateSubsetUniqueness(pyblish.api.ContextPlugin):
"""
Validate that all subset's names are unique.
"""
label = "Validate Subset Uniqueness"
hosts = ["photoshop"]
order = pype.api.ValidateContentsOrder
families = ["image"]
def process(self, context):
subset_names = []
for instance in context:
if instance.data.get('publish'):
subset_names.append(instance.data.get('subset'))
msg = (
"Instance subset names are not unique. " +
"Remove duplicates via SubsetManager."
)
assert len(subset_names) == len(set(subset_names)), msg

View file

@ -2,7 +2,10 @@ import os
import shutil
from pype.hosts import tvpaint
from pype.lib import PreLaunchHook
from pype.lib import (
PreLaunchHook,
get_pype_execute_args
)
import avalon
@ -20,18 +23,16 @@ class TvpaintPrelaunchHook(PreLaunchHook):
def execute(self):
# Pop tvpaint executable
tvpaint_executable = self.launch_context.launch_args.pop(0)
executable_path = self.launch_context.launch_args.pop(0)
# Pop rest of launch arguments - There should not be other arguments!
remainders = []
while self.launch_context.launch_args:
remainders.append(self.launch_context.launch_args.pop(0))
new_launch_args = [
self.main_executable(),
self.launch_script_path(),
tvpaint_executable
]
new_launch_args = get_pype_execute_args(
"run", self.launch_script_path(), executable_path
)
# Add workfile to launch arguments
workfile_path = self.workfile_path()
@ -56,11 +57,6 @@ class TvpaintPrelaunchHook(PreLaunchHook):
).format(str(remainders)))
self.launch_context.launch_args.extend(remainders)
def main_executable(self):
"""Should lead to python executable."""
# TODO change in Pype 3
return os.path.normpath(os.environ["PYPE_PYTHON_EXE"])
def launch_script_path(self):
avalon_dir = os.path.dirname(os.path.abspath(avalon.__file__))
script_path = os.path.join(

87
pype/lib/pype_info.py Normal file
View file

@ -0,0 +1,87 @@
import os
import json
import datetime
import platform
import getpass
import socket
import pype.version
from pype.settings.lib import get_local_settings
from .execute import get_pype_execute_args
from .local_settings import get_local_site_id
def get_pype_version():
"""Version of pype that is currently used."""
return pype.version.__version__
def get_pype_info():
"""Information about currently used Pype process."""
executable_args = get_pype_execute_args()
if len(executable_args) == 1:
version_type = "build"
else:
version_type = "code"
return {
"version": get_pype_version(),
"version_type": version_type,
"executable": executable_args[-1],
"pype_root": os.environ["PYPE_ROOT"],
"mongo_url": os.environ["PYPE_MONGO"]
}
def get_workstation_info():
"""Basic information about workstation."""
host_name = socket.gethostname()
try:
host_ip = socket.gethostbyname(host_name)
except socket.gaierror:
host_ip = "127.0.0.1"
return {
"hostname": host_name,
"hostip": host_ip,
"username": getpass.getuser(),
"system_name": platform.system(),
"local_id": get_local_site_id()
}
def get_all_current_info():
"""All information about current process in one dictionary."""
return {
"pype": get_pype_info(),
"workstation": get_workstation_info(),
"env": os.environ.copy(),
"local_settings": get_local_settings()
}
def extract_pype_info_to_file(dirpath):
"""Extract all current info to a file.
It is possible to define onpy directory path. Filename is concatenated with
pype version, workstation site id and timestamp.
Args:
dirpath (str): Path to directory where file will be stored.
Returns:
filepath (str): Full path to file where data were extracted.
"""
filename = "{}_{}_{}.json".format(
get_pype_version(),
get_local_site_id(),
datetime.datetime.now().strftime("%y%m%d%H%M%S")
)
filepath = os.path.join(dirpath, filename)
data = get_all_current_info()
if not os.path.exists(dirpath):
os.makedirs(dirpath)
with open(filepath, "w") as file_stream:
json.dump(data, file_stream, indent=4)
return filepath

View file

@ -108,6 +108,7 @@ class ITrayModule:
would do nothing.
"""
tray_initialized = False
_tray_manager = None
@abstractmethod
def tray_init(self):
@ -138,6 +139,20 @@ class ITrayModule:
"""
pass
def show_tray_message(self, title, message, icon=None, msecs=None):
"""Show tray message.
Args:
title (str): Title of message.
message (str): Content of message.
icon (QSystemTrayIcon.MessageIcon): Message's icon. Default is
Information icon, may differ by Qt version.
msecs (int): Duration of message visibility in miliseconds.
Default is 10000 msecs, may differ by Qt version.
"""
if self._tray_manager:
self._tray_manager.show_tray_message(title, message, icon, msecs)
class ITrayAction(ITrayModule):
"""Implementation of Tray action.
@ -638,8 +653,10 @@ class TrayModulesManager(ModulesManager):
self.modules_by_id = {}
self.modules_by_name = {}
self._report = {}
self.tray_manager = None
def initialize(self, tray_menu):
def initialize(self, tray_manager, tray_menu):
self.tray_manager = tray_manager
self.initialize_modules()
self.tray_init()
self.connect_modules()
@ -658,6 +675,7 @@ class TrayModulesManager(ModulesManager):
prev_start_time = time_start
for module in self.get_enabled_tray_modules():
try:
module._tray_manager = self.tray_manager
module.tray_init()
module.tray_initialized = True
except Exception:

View file

@ -54,6 +54,9 @@ class Photoshop(WebSocketRoute):
async def projectmanager_route(self):
self._tool_route("projectmanager")
async def subsetmanager_route(self):
self._tool_route("subsetmanager")
def _tool_route(self, tool_name):
"""The address accessed when clicking on the buttons."""
partial_method = functools.partial(photoshop.show, tool_name)

View file

@ -4,16 +4,37 @@ from pype.modules.websocket_server import WebSocketServer
Used anywhere solution is calling client methods.
"""
import json
from collections import namedtuple
import attr
class PhotoshopServerStub():
@attr.s
class PSItem(object):
"""
Object denoting layer or group item in PS. Each item is created in
PS by any Loader, but contains same fields, which are being used
in later processing.
"""
# metadata
id = attr.ib() # id created by AE, could be used for querying
name = attr.ib() # name of item
group = attr.ib(default=None) # item type (footage, folder, comp)
parents = attr.ib(factory=list)
visible = attr.ib(default=True)
type = attr.ib(default=None)
# all imported elements, single for
members = attr.ib(factory=list)
long_name = attr.ib(default=None)
class PhotoshopServerStub:
"""
Stub for calling function on client (Photoshop js) side.
Expects that client is already connected (started when avalon menu
is opened).
'self.websocketserver.call' is used as async wrapper
"""
PUBLISH_ICON = '\u2117 '
LOADED_ICON = '\u25bc'
def __init__(self):
self.websocketserver = WebSocketServer.get_instance()
@ -34,7 +55,7 @@ class PhotoshopServerStub():
"""
Parses layer metadata from Headline field of active document
Args:
layer: <namedTuple Layer("id":XX, "name":"YYY")
layer: (PSItem)
layers_meta: full list from Headline (for performance in loops)
Returns:
"""
@ -46,10 +67,33 @@ class PhotoshopServerStub():
def imprint(self, layer, data, all_layers=None, layers_meta=None):
"""
Save layer metadata to Headline field of active document
Stores metadata in format:
[{
"active":true,
"subset":"imageBG",
"family":"image",
"id":"pyblish.avalon.instance",
"asset":"Town",
"uuid": "8"
}] - for created instances
OR
[{
"schema": "avalon-core:container-2.0",
"id": "pyblish.avalon.instance",
"name": "imageMG",
"namespace": "Jungle_imageMG_001",
"loader": "ImageLoader",
"representation": "5fbfc0ee30a946093c6ff18a",
"members": [
"40"
]
}] - for loaded instances
Args:
layer (namedtuple): Layer("id": XXX, "name":'YYY')
layer (PSItem):
data(string): json representation for single layer
all_layers (list of namedtuples): for performance, could be
all_layers (list of PSItem): for performance, could be
injected for usage in loop, if not, single call will be
triggered
layers_meta(string): json representation from Headline
@ -59,6 +103,7 @@ class PhotoshopServerStub():
"""
if not layers_meta:
layers_meta = self.get_layers_metadata()
# json.dumps writes integer values in a dictionary to string, so
# anticipating it here.
if str(layer.id) in layers_meta and layers_meta[str(layer.id)]:
@ -73,11 +118,11 @@ class PhotoshopServerStub():
if not all_layers:
all_layers = self.get_layers()
layer_ids = [layer.id for layer in all_layers]
cleaned_data = {}
cleaned_data = []
for id in layers_meta:
if int(id) in layer_ids:
cleaned_data[id] = layers_meta[id]
cleaned_data.append(layers_meta[id])
payload = json.dumps(cleaned_data, indent=4)
@ -89,7 +134,7 @@ class PhotoshopServerStub():
"""
Returns JSON document with all(?) layers in active document.
Returns: <list of namedtuples>
Returns: <list of PSItem>
Format of tuple: { 'id':'123',
'name': 'My Layer 1',
'type': 'GUIDE'|'FG'|'BG'|'OBJ'
@ -100,12 +145,26 @@ class PhotoshopServerStub():
return self._to_records(res)
def get_layer(self, layer_id):
"""
Returns PSItem for specific 'layer_id' or None if not found
Args:
layer_id (string): unique layer id, stored in 'uuid' field
Returns:
(PSItem) or None
"""
layers = self.get_layers()
for layer in layers:
if str(layer.id) == str(layer_id):
return layer
def get_layers_in_layers(self, layers):
"""
Return all layers that belong to layers (might be groups).
Args:
layers <list of namedTuples>:
Returns: <list of namedTuples>
layers <list of PSItem>:
Returns: <list of PSItem>
"""
all_layers = self.get_layers()
ret = []
@ -123,28 +182,30 @@ class PhotoshopServerStub():
def create_group(self, name):
"""
Create new group (eg. LayerSet)
Returns: <namedTuple Layer("id":XX, "name":"YYY")>
Returns: <PSItem>
"""
enhanced_name = self.PUBLISH_ICON + name
ret = self.websocketserver.call(self.client.call
('Photoshop.create_group',
name=name))
name=enhanced_name))
# create group on PS is asynchronous, returns only id
layer = {"id": ret, "name": name, "group": True}
return namedtuple('Layer', layer.keys())(*layer.values())
return PSItem(id=ret, name=name, group=True)
def group_selected_layers(self, name):
"""
Group selected layers into new LayerSet (eg. group)
Returns: (Layer)
"""
enhanced_name = self.PUBLISH_ICON + name
res = self.websocketserver.call(self.client.call
('Photoshop.group_selected_layers',
name=name)
name=enhanced_name)
)
res = self._to_records(res)
if res:
return res.pop()
rec = res.pop()
rec.name = rec.name.replace(self.PUBLISH_ICON, '')
return rec
raise ValueError("No group record returned")
def get_selected_layers(self):
@ -163,11 +224,10 @@ class PhotoshopServerStub():
layers: <list of Layer('id':XX, 'name':"YYY")>
Returns: None
"""
layer_ids = [layer.id for layer in layers]
layers_id = [str(lay.id) for lay in layers]
self.websocketserver.call(self.client.call
('Photoshop.get_layers',
layers=layer_ids)
('Photoshop.select_layers',
layers=json.dumps(layers_id))
)
def get_active_document_full_name(self):
@ -238,7 +298,14 @@ class PhotoshopServerStub():
"""
Reads layers metadata from Headline from active document in PS.
(Headline accessible by File > File Info)
Returns(string): - json documents
Returns:
(string): - json documents
example:
{"8":{"active":true,"subset":"imageBG",
"family":"image","id":"pyblish.avalon.instance",
"asset":"Town"}}
8 is layer(group) id - used for deletion, update etc.
"""
layers_data = {}
res = self.websocketserver.call(self.client.call('Photoshop.read'))
@ -246,6 +313,23 @@ class PhotoshopServerStub():
layers_data = json.loads(res)
except json.decoder.JSONDecodeError:
pass
# format of metadata changed from {} to [] because of standardization
# keep current implementation logic as its working
if not isinstance(layers_data, dict):
temp_layers_meta = {}
for layer_meta in layers_data:
layer_id = layer_meta.get("uuid") or \
(layer_meta.get("members")[0])
temp_layers_meta[layer_id] = layer_meta
layers_data = temp_layers_meta
else:
# legacy version of metadata
for layer_id, layer_meta in layers_data.items():
if layer_meta.get("schema") != "avalon-core:container-2.0":
layer_meta["uuid"] = str(layer_id)
else:
layer_meta["members"] = [str(layer_id)]
return layers_data
def import_smart_object(self, path, layer_name):
@ -257,11 +341,14 @@ class PhotoshopServerStub():
layer_name (str): Unique layer name to differentiate how many times
same smart object was loaded
"""
enhanced_name = self.LOADED_ICON + layer_name
res = self.websocketserver.call(self.client.call
('Photoshop.import_smart_object',
path=path, name=layer_name))
return self._to_records(res).pop()
path=path, name=enhanced_name))
rec = self._to_records(res).pop()
if rec:
rec.name = rec.name.replace(self.LOADED_ICON, '')
return rec
def replace_smart_object(self, layer, path, layer_name):
"""
@ -270,13 +357,14 @@ class PhotoshopServerStub():
same smart object was loaded
Args:
layer (namedTuple): Layer("id":XX, "name":"YY"..).
layer (PSItem):
path (str): File to import.
"""
enhanced_name = self.LOADED_ICON + layer_name
self.websocketserver.call(self.client.call
('Photoshop.replace_smart_object',
layer_id=layer.id,
path=path, name=layer_name))
path=path, name=enhanced_name))
def delete_layer(self, layer_id):
"""
@ -288,24 +376,62 @@ class PhotoshopServerStub():
('Photoshop.delete_layer',
layer_id=layer_id))
def rename_layer(self, layer_id, name):
"""
Renames specific layer by it's id.
Args:
layer_id (int): id of layer to delete
name (str): new name
"""
self.websocketserver.call(self.client.call
('Photoshop.rename_layer',
layer_id=layer_id,
name=name))
def remove_instance(self, instance_id):
cleaned_data = {}
for key, instance in self.get_layers_metadata().items():
if key != instance_id:
cleaned_data[key] = instance
payload = json.dumps(cleaned_data, indent=4)
self.websocketserver.call(self.client.call
('Photoshop.imprint', payload=payload)
)
def close(self):
self.client.close()
def _to_records(self, res):
"""
Converts string json representation into list of named tuples for
Converts string json representation into list of PSItem for
dot notation access to work.
Returns: <list of named tuples>
res(string): - json representation
Args:
res (string): valid json
Returns:
<list of PSItem>
"""
try:
layers_data = json.loads(res)
except json.decoder.JSONDecodeError:
raise ValueError("Received broken JSON {}".format(res))
ret = []
# convert to namedtuple to use dot donation
if isinstance(layers_data, dict): # TODO refactore
# convert to AEItem to use dot donation
if isinstance(layers_data, dict):
layers_data = [layers_data]
for d in layers_data:
ret.append(namedtuple('Layer', d.keys())(*d.values()))
# currently implemented and expected fields
item = PSItem(d.get('id'),
d.get('name'),
d.get('group'),
d.get('parents'),
d.get('visible'),
d.get('type'),
d.get('members'),
d.get('long_name'))
ret.append(item)
return ret

View file

@ -113,15 +113,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
@ -145,7 +136,7 @@ class PypeCommands:
bs.data_dir = out_path.parent
print(f">>> Creating zip in {bs.data_dir} ...")
repo_file = bs.install_live_repos()
repo_file = bs.create_version_from_live_code()
if not repo_file:
print("!!! Error while creating zip file.")
exit(1)

View file

@ -1,17 +0,0 @@
<svg version="1.1" id="loader-1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="312px" height="312px" viewBox="0 0 40 40" xml:space="preserve">
<path opacity="0.2" fill="#ffa500" d="M20.201,5.169c-8.254,0-14.946,6.692-14.946,14.946c0,8.255,6.692,14.946,14.946,14.946
s14.946-6.691,14.946-14.946C35.146,11.861,28.455,5.169,20.201,5.169z M20.201,31.749c-6.425,0-11.634-5.208-11.634-11.634
c0-6.425,5.209-11.634,11.634-11.634c6.425,0,11.633,5.209,11.633,11.634C31.834,26.541,26.626,31.749,20.201,31.749z"/>
<path fill="#ffa500" d="M26.013,10.047l1.654-2.866c-2.198-1.272-4.743-2.012-7.466-2.012h0v3.312h0
C22.32,8.481,24.301,9.057,26.013,10.047z">
<animateTransform attributeType="xml"
attributeName="transform"
type="rotate"
from="00 20.2 20.1"
to="360 20.2 20.1"
dur="0.5s"
repeatCount="indefinite"/>
</path>
<text x="3" y="23" fill="#ffa500" font-style="bold" font-size="7px" font-family="sans-serif">Working...</text>
</svg>

Before

Width:  |  Height:  |  Size: 1 KiB

View file

@ -1,111 +1,11 @@
{
"capture": {
"Codec": {
"compression": "jpg",
"format": "image",
"quality": 95
},
"Display Options": {
"background": [
0.7,
0.7,
0.7
],
"backgroundBottom": [
0.7,
0.7,
0.7
],
"backgroundTop": [
0.7,
0.7,
0.7
],
"override_display": true
},
"Generic": {
"isolate_view": true,
"off_screen": true
},
"IO": {
"name": "",
"open_finished": true,
"raw_frame_numbers": true,
"recent_playblasts": [],
"save_file": true
},
"PanZoom": {
"pan_zoom": true
},
"Renderer": {
"rendererName": "vp2Renderer"
},
"Resolution": {
"width": 1080,
"height": 1920,
"percent": 1.0,
"mode": "Custom"
},
"Time Range": {
"start_frame": 0,
"end_frame": 0,
"frame": "",
"time": "Time Slider"
},
"Viewport Options": {
"cameras": false,
"clipGhosts": false,
"controlVertices": false,
"deformers": false,
"dimensions": false,
"displayLights": 0,
"dynamicConstraints": false,
"dynamics": false,
"fluids": false,
"follicles": false,
"gpuCacheDisplayFilter": false,
"greasePencils": false,
"grid": false,
"hairSystems": true,
"handles": false,
"high_quality": true,
"hud": false,
"hulls": false,
"ikHandles": false,
"imagePlane": true,
"joints": false,
"lights": false,
"locators": false,
"manipulators": false,
"motionTrails": false,
"nCloths": false,
"nParticles": false,
"nRigids": false,
"nurbsCurves": false,
"nurbsSurfaces": false,
"override_viewport_options": true,
"particleInstancers": false,
"pivots": false,
"planes": false,
"pluginShapes": false,
"polymeshes": true,
"shadows": true,
"strokes": false,
"subdivSurfaces": false,
"textures": false,
"twoSidedLighting": true
},
"Camera Options": {
"displayGateMask": false,
"displayResolution": false,
"displayFilmGate": false,
"displayFieldChart": false,
"displaySafeAction": false,
"displaySafeTitle": false,
"displayFilmPivot": false,
"displayFilmOrigin": false,
"overscan": 1.0
}
"ext_mapping": {
"model": "ma",
"mayaAscii": "ma",
"camera": "ma",
"rig": "ma",
"workfile": "ma",
"yetiRig": "ma"
},
"create": {
"CreateAnimation": {
@ -299,6 +199,10 @@
"enabled": false,
"optional": true
},
"ValidateMeshNormalsUnlocked": {
"enabled": false,
"optional": true
},
"ValidateMeshUVSetMap1": {
"enabled": false,
"optional": true
@ -336,7 +240,7 @@
"optional": true
},
"ValidateTransformZero": {
"enabled": true,
"enabled": false,
"optional": true
},
"ValidateCameraAttributes": {
@ -351,6 +255,105 @@
"enabled": true,
"optional": true
},
"ExtractPlayblast": {
"capture_preset": {
"Codec": {
"compression": "jpg",
"format": "image",
"quality": 95
},
"Display Options": {
"background": [
0.7,
0.7,
0.7
],
"backgroundBottom": [
0.7,
0.7,
0.7
],
"backgroundTop": [
0.7,
0.7,
0.7
],
"override_display": true
},
"Generic": {
"isolate_view": true,
"off_screen": true
},
"PanZoom": {
"pan_zoom": true
},
"Renderer": {
"rendererName": "vp2Renderer"
},
"Resolution": {
"width": 1080,
"height": 1920,
"percent": 1.0,
"mode": "Custom"
},
"Viewport Options": {
"override_viewport_options": true,
"displayLights": "0",
"textureMaxResolution": 1024,
"multiSample": 4,
"shadows": true,
"textures": true,
"twoSidedLighting": true,
"ssaoEnable": true,
"cameras": false,
"clipGhosts": false,
"controlVertices": false,
"deformers": false,
"dimensions": false,
"dynamicConstraints": false,
"dynamics": false,
"fluids": false,
"follicles": false,
"gpuCacheDisplayFilter": false,
"greasePencils": false,
"grid": false,
"hairSystems": true,
"handles": false,
"hud": false,
"hulls": false,
"ikHandles": false,
"imagePlane": true,
"joints": false,
"lights": false,
"locators": false,
"manipulators": false,
"motionTrails": false,
"nCloths": false,
"nParticles": false,
"nRigids": false,
"nurbsCurves": false,
"nurbsSurfaces": false,
"particleInstancers": false,
"pivots": false,
"planes": false,
"pluginShapes": false,
"polymeshes": true,
"strokes": false,
"subdivSurfaces": false
},
"Camera Options": {
"displayGateMask": false,
"displayResolution": false,
"displayFilmGate": false,
"displayFieldChart": false,
"displaySafeAction": false,
"displaySafeTitle": false,
"displayFilmPivot": false,
"displayFilmOrigin": false,
"overscan": 1.0
}
}
},
"ExtractCameraAlembic": {
"enabled": true,
"optional": true,

View file

@ -11,6 +11,30 @@
"PreCollectNukeInstances": {
"sync_workfile_version": true
},
"ValidateKnobs": {
"enabled": false,
"knobs": {
"render": {
"review": true
}
}
},
"ValidateOutputResolution": {
"enabled": true,
"optional": true
},
"ValidateGizmo": {
"enabled": true,
"optional": true
},
"ValidateScript": {
"enabled": true,
"optional": true
},
"ValidateNukeWriteBoundingBox": {
"enabled": true,
"optional": true
},
"ExtractThumbnail": {
"enabled": true,
"nodes": {
@ -38,14 +62,6 @@
]
}
},
"ValidateKnobs": {
"enabled": false,
"knobs": {
"render": {
"review": true
}
}
},
"ExtractReviewDataLut": {
"enabled": false
},
@ -61,22 +77,25 @@
"deadline_pool": "",
"deadline_pool_secondary": "",
"deadline_chunk_size": 1
},
"ValidateOutputResolution": {
}
},
"load": {
"LoadImage": {
"enabled": true,
"optional": true
"representations": []
},
"ValidateGizmo": {
"LoadMov": {
"enabled": true,
"optional": true
"representations": []
},
"ValidateScript": {
"LoadSequence": {
"enabled": true,
"optional": true
},
"ValidateNukeWriteBoundingBox": {
"enabled": true,
"optional": true
"representations": [
"png",
"jpg",
"exr",
""
]
}
},
"workfile_build": {

View file

@ -1,14 +1,4 @@
{
"publish": {
"ExtractThumbnailSP": {
"ffmpeg_args": {
"input": [
"gamma 2.2"
],
"output": []
}
}
},
"create": {
"create_workfile": {
"name": "workfile",
@ -121,5 +111,15 @@
"create_image": "Image",
"create_matchmove": "Matchmove"
}
},
"publish": {
"ExtractThumbnailSP": {
"ffmpeg_args": {
"input": [
"gamma 2.2"
],
"output": []
}
}
}
}

View file

@ -17,21 +17,11 @@
"darwin": "{PYPE_ROOT}/vendor/bin/ffmpeg_exec/darwin/bin",
"linux": ":{PYPE_ROOT}/vendor/bin/ffmpeg_exec/linux"
},
"PYPE_PYTHON_EXE": {
"windows": "{VIRTUAL_ENV}/Scripts/python.exe",
"linux": "{VIRTUAL_ENV}/Scripts/python",
"darwin": "{VIRTUAL_ENV}/bin/python"
},
"PYPE_OCIO_CONFIG": "{STUDIO_SOFT}/OpenColorIO-Configs",
"PYBLISH_GUI": "pyblish_pype",
"QT_AUTO_SCREEN_SCALE_FACTOR": "1",
"__environment_keys__": {
"global": [
"FFMPEG_PATH",
"PYPE_PYTHON_EXE",
"PYPE_OCIO_CONFIG",
"PYBLISH_GUI",
"QT_AUTO_SCREEN_SCALE_FACTOR"
"PYPE_OCIO_CONFIG"
]
}
}

View file

@ -6,8 +6,13 @@
"is_file": true,
"children": [
{
"type": "schema",
"name": "schema_maya_capture"
"type": "dict-modifiable",
"key": "ext_mapping",
"label": "Extension Mapping",
"use_label_wrap": true,
"object_type": {
"type": "text"
}
},
{
"type": "schema",

View file

@ -45,6 +45,11 @@
"type": "schema",
"name": "schema_nuke_publish",
"template_data": []
},
{
"type": "schema",
"name": "schema_nuke_load",
"template_data": []
},
{
"type": "schema",

View file

@ -1,581 +0,0 @@
{
"type": "dict",
"collapsible": true,
"key": "capture",
"label": "Maya Playblast settings",
"is_file": true,
"children": [
{
"type": "dict",
"key": "Codec",
"children": [
{
"type": "label",
"label": "<b>Codec</b>"
},
{
"type": "text",
"key": "compression",
"label": "Compression type"
},
{
"type": "text",
"key": "format",
"label": "Data format"
},
{
"type": "number",
"key": "quality",
"label": "Quality",
"decimal": 0,
"minimum": 0,
"maximum": 100
},
{
"type": "splitter"
}
]
},
{
"type": "dict",
"key": "Display Options",
"children": [
{
"type": "label",
"label": "<b>Display Options</b>"
},
{
"type": "list-strict",
"key": "background",
"label": "Background Color: ",
"object_types": [
{
"label": "Red",
"type": "number",
"minimum": 0,
"maximum": 1,
"decimal": 3
},
{
"label": "Green",
"type": "number",
"minimum": 0,
"maximum": 1,
"decimal": 3
},
{
"label": "Blue",
"type": "number",
"minimum": 0,
"maximum": 1,
"decimal": 3
}
]
},
{
"type": "list-strict",
"key": "backgroundBottom",
"label": "Background Bottom: ",
"object_types": [
{
"label": "Red",
"type": "number",
"minimum": 0,
"maximum": 1,
"decimal": 3
},
{
"label": "Green",
"type": "number",
"minimum": 0,
"maximum": 1,
"decimal": 3
},
{
"label": "Blue",
"type": "number",
"minimum": 0,
"maximum": 1,
"decimal": 3
}
]
},
{
"type": "list-strict",
"key": "backgroundTop",
"label": "Background Top: ",
"object_types": [
{
"label": "Red",
"type": "number",
"minimum": 0,
"maximum": 1,
"decimal": 3
},
{
"label": "Green",
"type": "number",
"minimum": 0,
"maximum": 1,
"decimal": 3
},
{
"label": "Blue",
"type": "number",
"minimum": 0,
"maximum": 1,
"decimal": 3
}
]
},
{
"type": "boolean",
"key": "override_display",
"label": "Override display options"
}
]
},
{
"type": "splitter"
},
{
"type": "dict",
"key": "Generic",
"children": [
{
"type": "label",
"label": "<b>Generic</b>"
},
{
"type": "boolean",
"key": "isolate_view",
"label": " Isolate view"
},
{
"type": "boolean",
"key": "off_screen",
"label": " Off Screen"
}
]
},
{
"type": "dict",
"key": "IO",
"children": [
{
"type": "label",
"label": "<b>IO</b>"
},
{
"type": "text",
"key": "name",
"label": "Name"
},
{
"type": "boolean",
"key": "open_finished",
"label": "Open finished"
},
{
"type": "boolean",
"key": "raw_frame_numbers",
"label": "Raw frame numbers"
},
{
"type": "list",
"key": "recent_playblasts",
"label": "Recent Playblasts",
"object_type": "text"
},
{
"type": "boolean",
"key": "save_file",
"label": "Save file"
}
]
},
{
"type": "dict",
"key": "PanZoom",
"children": [
{
"type": "boolean",
"key": "pan_zoom",
"label": " Pan Zoom"
}
]
},
{
"type": "splitter"
},
{
"type": "dict",
"key": "Renderer",
"children": [
{
"type": "label",
"label": "<b>Renderer</b>"
},
{
"type": "text",
"key": "rendererName",
"label": " Renderer name"
}
]
},
{
"type": "dict",
"key": "Resolution",
"children": [
{
"type": "splitter"
},
{
"type": "label",
"label": "<b>Resolution</b>"
},
{
"type": "number",
"key": "width",
"label": " Width",
"decimal": 0,
"minimum": 0,
"maximum": 99999
},
{
"type": "number",
"key": "height",
"label": "Height",
"decimal": 0,
"minimum": 0,
"maximum": 99999
},
{
"type": "number",
"key": "percent",
"label": "percent",
"decimal": 1,
"minimum": 0,
"maximum": 200
},
{
"type": "text",
"key": "mode",
"label": "Mode"
}
]
},
{
"type": "splitter"
},
{
"type": "dict",
"key": "Time Range",
"children": [
{
"type": "label",
"label": "<b>Time Range</b>"
},
{
"type": "number",
"key": "start_frame",
"label": " Start frame",
"decimal": 0,
"minimum": 0,
"maximum": 999999
},
{
"type": "number",
"key": "end_frame",
"label": "End frame",
"decimal": 0,
"minimum": 0,
"maximum": 999999
},
{
"type": "text",
"key": "frame",
"label": "Frame"
},
{
"type": "text",
"key": "time",
"label": "Time"
}
]
},
{
"type": "dict",
"collapsible": true,
"key": "Viewport Options",
"label": "Viewport Options",
"children": [
{
"type": "boolean",
"key": "cameras",
"label": "cameras"
},
{
"type": "boolean",
"key": "clipGhosts",
"label": "clipGhosts"
},
{
"type": "boolean",
"key": "controlVertices",
"label": "controlVertices"
},
{
"type": "boolean",
"key": "deformers",
"label": "deformers"
},
{
"type": "boolean",
"key": "dimensions",
"label": "dimensions"
},
{
"type": "number",
"key": "displayLights",
"label": "displayLights",
"decimal": 0,
"minimum": 0,
"maximum": 10
},
{
"type": "boolean",
"key": "dynamicConstraints",
"label": "dynamicConstraints"
},
{
"type": "boolean",
"key": "dynamics",
"label": "dynamics"
},
{
"type": "boolean",
"key": "fluids",
"label": "fluids"
},
{
"type": "boolean",
"key": "follicles",
"label": "follicles"
},
{
"type": "boolean",
"key": "gpuCacheDisplayFilter",
"label": "gpuCacheDisplayFilter"
},
{
"type": "boolean",
"key": "greasePencils",
"label": "greasePencils"
},
{
"type": "boolean",
"key": "grid",
"label": "grid"
},
{
"type": "boolean",
"key": "hairSystems",
"label": "hairSystems"
},
{
"type": "boolean",
"key": "handles",
"label": "handles"
},
{
"type": "boolean",
"key": "high_quality",
"label": "high_quality"
},
{
"type": "boolean",
"key": "hud",
"label": "hud"
},
{
"type": "boolean",
"key": "hulls",
"label": "hulls"
},
{
"type": "boolean",
"key": "ikHandles",
"label": "ikHandles"
},
{
"type": "boolean",
"key": "imagePlane",
"label": "imagePlane"
},
{
"type": "boolean",
"key": "joints",
"label": "joints"
},
{
"type": "boolean",
"key": "lights",
"label": "lights"
},
{
"type": "boolean",
"key": "locators",
"label": "locators"
},
{
"type": "boolean",
"key": "manipulators",
"label": "manipulators"
},
{
"type": "boolean",
"key": "motionTrails",
"label": "motionTrails"
},
{
"type": "boolean",
"key": "nCloths",
"label": "nCloths"
},
{
"type": "boolean",
"key": "nParticles",
"label": "nParticles"
},
{
"type": "boolean",
"key": "nRigids",
"label": "nRigids"
},
{
"type": "boolean",
"key": "nurbsCurves",
"label": "nurbsCurves"
},
{
"type": "boolean",
"key": "nurbsSurfaces",
"label": "nurbsSurfaces"
},
{
"type": "boolean",
"key": "override_viewport_options",
"label": "override_viewport_options"
},
{
"type": "boolean",
"key": "particleInstancers",
"label": "particleInstancers"
},
{
"type": "boolean",
"key": "pivots",
"label": "pivots"
},
{
"type": "boolean",
"key": "planes",
"label": "planes"
},
{
"type": "boolean",
"key": "pluginShapes",
"label": "pluginShapes"
},
{
"type": "boolean",
"key": "polymeshes",
"label": "polymeshes"
},
{
"type": "boolean",
"key": "shadows",
"label": "shadows"
},
{
"type": "boolean",
"key": "strokes",
"label": "strokes"
},
{
"type": "boolean",
"key": "subdivSurfaces",
"label": "subdivSurfaces"
},
{
"type": "boolean",
"key": "textures",
"label": "textures"
},
{
"type": "boolean",
"key": "twoSidedLighting",
"label": "twoSidedLighting"
}
]
},
{
"type": "dict",
"collapsible": true,
"key": "Camera Options",
"label": "Camera Options",
"children": [
{
"type": "boolean",
"key": "displayGateMask",
"label": "displayGateMask"
},
{
"type": "boolean",
"key": "displayResolution",
"label": "displayResolution"
},
{
"type": "boolean",
"key": "displayFilmGate",
"label": "displayFilmGate"
},
{
"type": "boolean",
"key": "displayFieldChart",
"label": "displayFieldChart"
},
{
"type": "boolean",
"key": "displaySafeAction",
"label": "displaySafeAction"
},
{
"type": "boolean",
"key": "displaySafeTitle",
"label": "displaySafeTitle"
},
{
"type": "boolean",
"key": "displayFilmPivot",
"label": "displayFilmPivot"
},
{
"type": "boolean",
"key": "displayFilmOrigin",
"label": "displayFilmOrigin"
},
{
"type": "number",
"key": "overscan",
"label": "overscan",
"decimal": 1,
"minimum": 0,
"maximum": 10
}
]
}
]
}

View file

@ -170,6 +170,10 @@
"key": "ValidateMeshNonManifold",
"label": "ValidateMeshNonManifold"
},
{
"key": "ValidateMeshNormalsUnlocked",
"label": "ValidateMeshNormalsUnlocked"
},
{
"key": "ValidateMeshUVSetMap1",
"label": "ValidateMeshUVSetMap1",
@ -242,6 +246,10 @@
"type": "label",
"label": "Extractors"
},
{
"type": "schema_template",
"name": "template_maya_capture"
},
{
"type": "dict",
"collapsible": true,

View file

@ -0,0 +1,26 @@
{
"type": "dict",
"collapsible": true,
"key": "load",
"label": "Loader plugins",
"children": [
{
"type": "schema_template",
"name": "template_loader_plugin",
"template_data": [
{
"key": "LoadImage",
"label": "Image Loader"
},
{
"key": "LoadMov",
"label": "Movie Loader"
},
{
"key": "LoadSequence",
"label": "Image Sequence Loader"
}
]
}
]
}

View file

@ -0,0 +1,541 @@
[
{
"type": "dict",
"collapsible": true,
"key": "ExtractPlayblast",
"label": "Extract Playblast settings",
"children": [
{
"type": "dict",
"key": "capture_preset",
"children": [
{
"type": "dict",
"key": "Codec",
"children": [
{
"type": "label",
"label": "<b>Codec</b>"
},
{
"type": "text",
"key": "compression",
"label": "Compression type"
},
{
"type": "text",
"key": "format",
"label": "Data format"
},
{
"type": "number",
"key": "quality",
"label": "Quality",
"decimal": 0,
"minimum": 0,
"maximum": 100
},
{
"type": "splitter"
}
]
},
{
"type": "dict",
"key": "Display Options",
"children": [
{
"type": "label",
"label": "<b>Display Options</b>"
},
{
"type": "list-strict",
"key": "background",
"label": "Background Color: ",
"object_types": [
{
"label": "Red",
"type": "number",
"minimum": 0,
"maximum": 1,
"decimal": 3
},
{
"label": "Green",
"type": "number",
"minimum": 0,
"maximum": 1,
"decimal": 3
},
{
"label": "Blue",
"type": "number",
"minimum": 0,
"maximum": 1,
"decimal": 3
}
]
},
{
"type": "list-strict",
"key": "backgroundBottom",
"label": "Background Bottom: ",
"object_types": [
{
"label": "Red",
"type": "number",
"minimum": 0,
"maximum": 1,
"decimal": 3
},
{
"label": "Green",
"type": "number",
"minimum": 0,
"maximum": 1,
"decimal": 3
},
{
"label": "Blue",
"type": "number",
"minimum": 0,
"maximum": 1,
"decimal": 3
}
]
},
{
"type": "list-strict",
"key": "backgroundTop",
"label": "Background Top: ",
"object_types": [
{
"label": "Red",
"type": "number",
"minimum": 0,
"maximum": 1,
"decimal": 3
},
{
"label": "Green",
"type": "number",
"minimum": 0,
"maximum": 1,
"decimal": 3
},
{
"label": "Blue",
"type": "number",
"minimum": 0,
"maximum": 1,
"decimal": 3
}
]
},
{
"type": "boolean",
"key": "override_display",
"label": "Override display options"
}
]
},
{
"type": "splitter"
},
{
"type": "dict",
"key": "Generic",
"children": [
{
"type": "label",
"label": "<b>Generic</b>"
},
{
"type": "boolean",
"key": "isolate_view",
"label": " Isolate view"
},
{
"type": "boolean",
"key": "off_screen",
"label": " Off Screen"
}
]
},
{
"type": "dict",
"key": "PanZoom",
"children": [
{
"type": "boolean",
"key": "pan_zoom",
"label": " Pan Zoom"
}
]
},
{
"type": "splitter"
},
{
"type": "dict",
"key": "Renderer",
"children": [
{
"type": "label",
"label": "<b>Renderer</b>"
},
{
"type": "enum",
"key": "rendererName",
"label": "Renderer name",
"enum_items": [
{ "vp2Renderer": "Viewport 2.0" }
]
}
]
},
{
"type": "dict",
"key": "Resolution",
"children": [
{
"type": "splitter"
},
{
"type": "label",
"label": "<b>Resolution</b>"
},
{
"type": "number",
"key": "width",
"label": " Width",
"decimal": 0,
"minimum": 0,
"maximum": 99999
},
{
"type": "number",
"key": "height",
"label": "Height",
"decimal": 0,
"minimum": 0,
"maximum": 99999
},
{
"type": "number",
"key": "percent",
"label": "percent",
"decimal": 1,
"minimum": 0,
"maximum": 200
},
{
"type": "text",
"key": "mode",
"label": "Mode"
}
]
},
{
"type": "splitter"
},
{
"type": "dict",
"collapsible": true,
"key": "Viewport Options",
"label": "Viewport Options",
"children": [
{
"type": "boolean",
"key": "override_viewport_options",
"label": "override_viewport_options"
},
{
"type": "enum",
"key": "displayLights",
"label": "Display Lights",
"enum_items": [
{ "default": "Default Lighting"},
{ "all": "All Lights"},
{ "selected": "Selected Lights"},
{ "flat": "Flat Lighting"},
{ "nolights": "No Lights"}
]
},
{
"type": "number",
"key": "textureMaxResolution",
"label": "Texture Clamp Resolution",
"decimal": 0
},
{
"type": "number",
"key": "multiSample",
"label": "Anti Aliasing Samples",
"decimal": 0,
"minimum": 0,
"maximum": 32
},
{
"type": "boolean",
"key": "shadows",
"label": "Display Shadows"
},
{
"type": "boolean",
"key": "textures",
"label": "Display Textures"
},
{
"type": "boolean",
"key": "twoSidedLighting",
"label": "Two Sided Lighting"
},
{
"type": "boolean",
"key": "ssaoEnable",
"label": "Screen Space Ambient Occlusion"
},
{
"type": "splitter"
},
{
"type": "boolean",
"key": "cameras",
"label": "cameras"
},
{
"type": "boolean",
"key": "clipGhosts",
"label": "clipGhosts"
},
{
"type": "boolean",
"key": "controlVertices",
"label": "controlVertices"
},
{
"type": "boolean",
"key": "deformers",
"label": "deformers"
},
{
"type": "boolean",
"key": "dimensions",
"label": "dimensions"
},
{
"type": "boolean",
"key": "dynamicConstraints",
"label": "dynamicConstraints"
},
{
"type": "boolean",
"key": "dynamics",
"label": "dynamics"
},
{
"type": "boolean",
"key": "fluids",
"label": "fluids"
},
{
"type": "boolean",
"key": "follicles",
"label": "follicles"
},
{
"type": "boolean",
"key": "gpuCacheDisplayFilter",
"label": "gpuCacheDisplayFilter"
},
{
"type": "boolean",
"key": "greasePencils",
"label": "greasePencils"
},
{
"type": "boolean",
"key": "grid",
"label": "grid"
},
{
"type": "boolean",
"key": "hairSystems",
"label": "hairSystems"
},
{
"type": "boolean",
"key": "handles",
"label": "handles"
},
{
"type": "boolean",
"key": "hud",
"label": "hud"
},
{
"type": "boolean",
"key": "hulls",
"label": "hulls"
},
{
"type": "boolean",
"key": "ikHandles",
"label": "ikHandles"
},
{
"type": "boolean",
"key": "imagePlane",
"label": "imagePlane"
},
{
"type": "boolean",
"key": "joints",
"label": "joints"
},
{
"type": "boolean",
"key": "lights",
"label": "lights"
},
{
"type": "boolean",
"key": "locators",
"label": "locators"
},
{
"type": "boolean",
"key": "manipulators",
"label": "manipulators"
},
{
"type": "boolean",
"key": "motionTrails",
"label": "motionTrails"
},
{
"type": "boolean",
"key": "nCloths",
"label": "nCloths"
},
{
"type": "boolean",
"key": "nParticles",
"label": "nParticles"
},
{
"type": "boolean",
"key": "nRigids",
"label": "nRigids"
},
{
"type": "boolean",
"key": "nurbsCurves",
"label": "nurbsCurves"
},
{
"type": "boolean",
"key": "nurbsSurfaces",
"label": "nurbsSurfaces"
},
{
"type": "boolean",
"key": "particleInstancers",
"label": "particleInstancers"
},
{
"type": "boolean",
"key": "pivots",
"label": "pivots"
},
{
"type": "boolean",
"key": "planes",
"label": "planes"
},
{
"type": "boolean",
"key": "pluginShapes",
"label": "pluginShapes"
},
{
"type": "boolean",
"key": "polymeshes",
"label": "polymeshes"
},
{
"type": "boolean",
"key": "strokes",
"label": "strokes"
},
{
"type": "boolean",
"key": "subdivSurfaces",
"label": "subdivSurfaces"
}
]
},
{
"type": "dict",
"collapsible": true,
"key": "Camera Options",
"label": "Camera Options",
"children": [
{
"type": "boolean",
"key": "displayGateMask",
"label": "displayGateMask"
},
{
"type": "boolean",
"key": "displayResolution",
"label": "displayResolution"
},
{
"type": "boolean",
"key": "displayFilmGate",
"label": "displayFilmGate"
},
{
"type": "boolean",
"key": "displayFieldChart",
"label": "displayFieldChart"
},
{
"type": "boolean",
"key": "displaySafeAction",
"label": "displaySafeAction"
},
{
"type": "boolean",
"key": "displaySafeTitle",
"label": "displaySafeTitle"
},
{
"type": "boolean",
"key": "displayFilmPivot",
"label": "displayFilmPivot"
},
{
"type": "boolean",
"key": "displayFilmOrigin",
"label": "displayFilmOrigin"
},
{
"type": "number",
"key": "overscan",
"label": "overscan",
"decimal": 1,
"minimum": 0,
"maximum": 10
}
]
}
]
}
]
}
]

View file

@ -0,0 +1,402 @@
import os
import json
import collections
from avalon import style
from Qt import QtCore, QtGui, QtWidgets
from pype.api import resources
from pype.settings.lib import get_local_settings
from pype.lib.pype_info import (
get_all_current_info,
get_pype_info,
get_workstation_info,
extract_pype_info_to_file
)
IS_MAIN_ROLE = QtCore.Qt.UserRole
class EnvironmentValueDelegate(QtWidgets.QStyledItemDelegate):
def createEditor(self, parent, option, index):
edit_widget = QtWidgets.QLineEdit(parent)
edit_widget.setReadOnly(True)
return edit_widget
class EnvironmentsView(QtWidgets.QTreeView):
def __init__(self, parent=None):
super(EnvironmentsView, self).__init__(parent)
model = QtGui.QStandardItemModel()
env = os.environ.copy()
keys = []
values = []
for key in sorted(env.keys()):
key_item = QtGui.QStandardItem(key)
key_item.setFlags(
QtCore.Qt.ItemIsSelectable
| QtCore.Qt.ItemIsEnabled
)
key_item.setData(True, IS_MAIN_ROLE)
keys.append(key_item)
value = env[key]
value_item = QtGui.QStandardItem(value)
value_item.setData(True, IS_MAIN_ROLE)
values.append(value_item)
value_parts = [
part
for part in value.split(os.pathsep) if part
]
if len(value_parts) < 2:
continue
sub_parts = []
for part_value in value_parts:
part_item = QtGui.QStandardItem(part_value)
part_item.setData(False, IS_MAIN_ROLE)
sub_parts.append(part_item)
key_item.appendRows(sub_parts)
model.appendColumn(keys)
model.appendColumn(values)
model.setHorizontalHeaderLabels(["Key", "Value"])
self.setModel(model)
# self.setIndentation(0)
delegate = EnvironmentValueDelegate(self)
self.setItemDelegate(delegate)
self.header().setSectionResizeMode(
0, QtWidgets.QHeaderView.ResizeToContents
)
self.setSelectionMode(QtWidgets.QTreeView.ExtendedSelection)
def get_selection_as_dict(self):
indexes = self.selectionModel().selectedIndexes()
main_mapping = collections.defaultdict(dict)
for index in indexes:
is_main = index.data(IS_MAIN_ROLE)
if not is_main:
continue
row = index.row()
value = index.data(QtCore.Qt.DisplayRole)
if index.column() == 0:
key = "key"
else:
key = "value"
main_mapping[row][key] = value
result = {}
for item in main_mapping.values():
result[item["key"]] = item["value"]
return result
def keyPressEvent(self, event):
if (
event.type() == QtGui.QKeyEvent.KeyPress
and event.matches(QtGui.QKeySequence.Copy)
):
selected_data = self.get_selection_as_dict()
selected_str = json.dumps(selected_data, indent=4)
mime_data = QtCore.QMimeData()
mime_data.setText(selected_str)
QtWidgets.QApplication.instance().clipboard().setMimeData(
mime_data
)
event.accept()
else:
return super(EnvironmentsView, self).keyPressEvent(event)
class ClickableWidget(QtWidgets.QWidget):
clicked = QtCore.Signal()
def mouseReleaseEvent(self, event):
if event.button() == QtCore.Qt.LeftButton:
self.clicked.emit()
super(ClickableWidget, self).mouseReleaseEvent(event)
class CollapsibleWidget(QtWidgets.QWidget):
def __init__(self, label, parent):
super(CollapsibleWidget, self).__init__(parent)
self.content_widget = None
top_part = ClickableWidget(parent=self)
button_size = QtCore.QSize(5, 5)
button_toggle = QtWidgets.QToolButton(parent=top_part)
button_toggle.setIconSize(button_size)
button_toggle.setArrowType(QtCore.Qt.RightArrow)
button_toggle.setCheckable(True)
button_toggle.setChecked(False)
label_widget = QtWidgets.QLabel(label, parent=top_part)
spacer_widget = QtWidgets.QWidget(top_part)
top_part_layout = QtWidgets.QHBoxLayout(top_part)
top_part_layout.setContentsMargins(0, 0, 0, 5)
top_part_layout.addWidget(button_toggle)
top_part_layout.addWidget(label_widget)
top_part_layout.addWidget(spacer_widget, 1)
label_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground)
spacer_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground)
self.setAttribute(QtCore.Qt.WA_TranslucentBackground)
self.button_toggle = button_toggle
self.label_widget = label_widget
top_part.clicked.connect(self._top_part_clicked)
self.button_toggle.clicked.connect(self._btn_clicked)
main_layout = QtWidgets.QVBoxLayout(self)
main_layout.setContentsMargins(0, 0, 0, 0)
main_layout.setSpacing(0)
main_layout.setAlignment(QtCore.Qt.AlignTop)
main_layout.addWidget(top_part)
self.main_layout = main_layout
def set_content_widget(self, content_widget):
content_widget.setVisible(self.button_toggle.isChecked())
self.main_layout.addWidget(content_widget)
self.content_widget = content_widget
def _btn_clicked(self):
self.toggle_content(self.button_toggle.isChecked())
def _top_part_clicked(self):
self.toggle_content()
def toggle_content(self, *args):
if len(args) > 0:
checked = args[0]
else:
checked = not self.button_toggle.isChecked()
arrow_type = QtCore.Qt.RightArrow
if checked:
arrow_type = QtCore.Qt.DownArrow
self.button_toggle.setChecked(checked)
self.button_toggle.setArrowType(arrow_type)
if self.content_widget:
self.content_widget.setVisible(checked)
self.parent().updateGeometry()
def resizeEvent(self, event):
super(CollapsibleWidget, self).resizeEvent(event)
if self.content_widget:
self.content_widget.updateGeometry()
class PypeInfoWidget(QtWidgets.QWidget):
not_applicable = "N/A"
def __init__(self, parent=None):
super(PypeInfoWidget, self).__init__(parent)
self.setStyleSheet(style.load_stylesheet())
icon = QtGui.QIcon(resources.pype_icon_filepath())
self.setWindowIcon(icon)
self.setWindowTitle("Pype info")
main_layout = QtWidgets.QVBoxLayout(self)
main_layout.setAlignment(QtCore.Qt.AlignTop)
main_layout.addWidget(self._create_pype_info_widget(), 0)
main_layout.addWidget(self._create_separator(), 0)
main_layout.addWidget(self._create_workstation_widget(), 0)
main_layout.addWidget(self._create_separator(), 0)
main_layout.addWidget(self._create_local_settings_widget(), 0)
main_layout.addWidget(self._create_separator(), 0)
main_layout.addWidget(self._create_environ_widget(), 1)
main_layout.addWidget(self._create_btns_section(), 0)
def _create_btns_section(self):
btns_widget = QtWidgets.QWidget(self)
btns_layout = QtWidgets.QHBoxLayout(btns_widget)
btns_layout.setContentsMargins(0, 0, 0, 0)
copy_to_clipboard_btn = QtWidgets.QPushButton(
"Copy to clipboard", btns_widget
)
export_to_file_btn = QtWidgets.QPushButton(
"Export", btns_widget
)
btns_layout.addWidget(QtWidgets.QWidget(btns_widget), 1)
btns_layout.addWidget(copy_to_clipboard_btn)
btns_layout.addWidget(export_to_file_btn)
copy_to_clipboard_btn.clicked.connect(self._on_copy_to_clipboard)
export_to_file_btn.clicked.connect(self._on_export_to_file)
return btns_widget
def _on_export_to_file(self):
dst_dir_path = QtWidgets.QFileDialog.getExistingDirectory(
self,
"Choose directory",
os.path.expanduser("~"),
QtWidgets.QFileDialog.ShowDirsOnly
)
if not dst_dir_path or not os.path.exists(dst_dir_path):
return
filepath = extract_pype_info_to_file(dst_dir_path)
title = "Extraction done"
message = "Extraction is done. Destination filepath is \"{}\"".format(
filepath.replace("\\", "/")
)
dialog = QtWidgets.QMessageBox(self)
dialog.setIcon(QtWidgets.QMessageBox.NoIcon)
dialog.setWindowTitle(title)
dialog.setText(message)
dialog.exec_()
def _on_copy_to_clipboard(self):
all_data = get_all_current_info()
all_data_str = json.dumps(all_data, indent=4)
mime_data = QtCore.QMimeData()
mime_data.setText(all_data_str)
QtWidgets.QApplication.instance().clipboard().setMimeData(
mime_data
)
def _create_separator(self):
separator_widget = QtWidgets.QWidget(self)
separator_widget.setStyleSheet("background: #222222;")
separator_widget.setMinimumHeight(2)
separator_widget.setMaximumHeight(2)
return separator_widget
def _create_workstation_widget(self):
key_label_mapping = {
"system_name": "System:",
"local_id": "Local ID:",
"username": "Username:",
"hostname": "Hostname:",
"hostip": "Host IP:"
}
keys_order = [
"system_name",
"local_id",
"username",
"hostname",
"hostip"
]
workstation_info = get_workstation_info()
for key in workstation_info.keys():
if key not in keys_order:
keys_order.append(key)
wokstation_info_widget = CollapsibleWidget("Workstation info", self)
info_widget = QtWidgets.QWidget(self)
info_layout = QtWidgets.QGridLayout(info_widget)
# Add spacer to 3rd column
info_layout.addWidget(QtWidgets.QWidget(info_widget), 0, 2)
info_layout.setColumnStretch(2, 1)
for key in keys_order:
if key not in workstation_info:
continue
label = key_label_mapping.get(key, key)
value = workstation_info[key]
row = info_layout.rowCount()
info_layout.addWidget(
QtWidgets.QLabel(label), row, 0, 1, 1
)
value_label = QtWidgets.QLabel(value)
value_label.setTextInteractionFlags(
QtCore.Qt.TextSelectableByMouse
)
info_layout.addWidget(
value_label, row, 1, 1, 1
)
wokstation_info_widget.set_content_widget(info_widget)
return wokstation_info_widget
def _create_local_settings_widget(self):
local_settings = get_local_settings()
local_settings_widget = CollapsibleWidget("Local settings", self)
settings_input = QtWidgets.QPlainTextEdit(local_settings_widget)
settings_input.setReadOnly(True)
settings_input.setPlainText(json.dumps(local_settings, indent=4))
local_settings_widget.set_content_widget(settings_input)
return local_settings_widget
def _create_environ_widget(self):
env_widget = CollapsibleWidget("Environments", self)
env_view = EnvironmentsView(env_widget)
env_widget.set_content_widget(env_view)
return env_widget
def _create_pype_info_widget(self):
"""Create widget with information about pype application."""
# Get pype info data
pype_info = get_pype_info()
# Modify version key/values
version_value = "{} ({})".format(
pype_info.pop("version", self.not_applicable),
pype_info.pop("version_type", self.not_applicable)
)
pype_info["version_value"] = version_value
# Prepare lable mapping
key_label_mapping = {
"version_value": "Pype version:",
"executable": "Pype executable:",
"pype_root": "Pype location:",
"mongo_url": "Pype Mongo URL:"
}
# Prepare keys order
keys_order = ["version_value", "executable", "pype_root", "mongo_url"]
for key in pype_info.keys():
if key not in keys_order:
keys_order.append(key)
# Create widgets
info_widget = QtWidgets.QWidget(self)
info_layout = QtWidgets.QGridLayout(info_widget)
# Add spacer to 3rd column
info_layout.addWidget(QtWidgets.QWidget(info_widget), 0, 2)
info_layout.setColumnStretch(2, 1)
title_label = QtWidgets.QLabel(info_widget)
title_label.setText("Application information")
title_label.setStyleSheet("font-weight: bold;")
info_layout.addWidget(title_label, 0, 0, 1, 2)
for key in keys_order:
if key not in pype_info:
continue
value = pype_info[key]
label = key_label_mapping.get(key, key)
row = info_layout.rowCount()
info_layout.addWidget(
QtWidgets.QLabel(label), row, 0, 1, 1
)
value_label = QtWidgets.QLabel(value)
value_label.setTextInteractionFlags(
QtCore.Qt.TextSelectableByMouse
)
info_layout.addWidget(
value_label, row, 1, 1, 1
)
return info_widget

View file

@ -3,15 +3,12 @@ import sys
import platform
from avalon import style
from Qt import QtCore, QtGui, QtWidgets, QtSvg
from Qt import QtCore, QtGui, QtWidgets
from pype.api import Logger, resources
from pype.modules import TrayModulesManager, ITrayService
from pype.settings.lib import get_system_settings
import pype.version
try:
import configparser
except Exception:
import ConfigParser as configparser
from .pype_info_widget import PypeInfoWidget
class TrayManager:
@ -19,13 +16,14 @@ class TrayManager:
Load submenus, actions, separators and modules into tray's context.
"""
available_sourcetypes = ["python", "file"]
def __init__(self, tray_widget, main_window):
self.tray_widget = tray_widget
self.main_window = main_window
self.log = Logger().get_logger(self.__class__.__name__)
self.pype_info_widget = None
self.log = Logger.get_logger(self.__class__.__name__)
self.module_settings = get_system_settings()["modules"]
@ -36,7 +34,7 @@ class TrayManager:
def initialize_modules(self):
"""Add modules to tray."""
self.modules_manager.initialize(self.tray_widget.menu)
self.modules_manager.initialize(self, self.tray_widget.menu)
# Add services if they are
services_submenu = ITrayService.services_submenu(self.tray_widget.menu)
@ -58,6 +56,26 @@ class TrayManager:
# Print time report
self.modules_manager.print_report()
def show_tray_message(self, title, message, icon=None, msecs=None):
"""Show tray message.
Args:
title (str): Title of message.
message (str): Content of message.
icon (QSystemTrayIcon.MessageIcon): Message's icon. Default is
Information icon, may differ by Qt version.
msecs (int): Duration of message visibility in miliseconds.
Default is 10000 msecs, may differ by Qt version.
"""
args = [title, message]
kwargs = {}
if icon:
kwargs["icon"] = icon
if msecs:
kwargs["msecs"] = msecs
self.tray_widget.showMessage(*args, **kwargs)
def _add_version_item(self):
subversion = os.environ.get("PYPE_SUBVERSION")
client_name = os.environ.get("PYPE_CLIENT")
@ -70,12 +88,21 @@ class TrayManager:
version_string += ", {}".format(client_name)
version_action = QtWidgets.QAction(version_string, self.tray_widget)
version_action.triggered.connect(self._on_version_action)
self.tray_widget.menu.addAction(version_action)
self.tray_widget.menu.addSeparator()
def on_exit(self):
self.modules_manager.on_exit()
def _on_version_action(self):
if self.pype_info_widget is None:
self.pype_info_widget = PypeInfoWidget()
self.pype_info_widget.show()
self.pype_info_widget.raise_()
self.pype_info_widget.activateWindow()
class SystemTrayIcon(QtWidgets.QSystemTrayIcon):
"""Tray widget.
@ -85,9 +112,9 @@ class SystemTrayIcon(QtWidgets.QSystemTrayIcon):
"""
def __init__(self, parent):
self.icon = QtGui.QIcon(resources.pype_icon_filepath())
icon = QtGui.QIcon(resources.pype_icon_filepath())
QtWidgets.QSystemTrayIcon.__init__(self, self.icon, parent)
super(SystemTrayIcon, self).__init__(icon, parent)
# Store parent - QtWidgets.QMainWindow()
self.parent = parent
@ -100,15 +127,15 @@ class SystemTrayIcon(QtWidgets.QSystemTrayIcon):
self.tray_man = TrayManager(self, self.parent)
self.tray_man.initialize_modules()
# Catch activate event
self.activated.connect(self.on_systray_activated)
# Catch activate event for left click if not on MacOS
# - MacOS has this ability by design so menu would be doubled
if platform.system().lower() != "darwin":
self.activated.connect(self.on_systray_activated)
# Add menu to Context of SystemTrayIcon
self.setContextMenu(self.menu)
def on_systray_activated(self, reason):
# show contextMenu if left click
if platform.system().lower() == "darwin":
return
if reason == QtWidgets.QSystemTrayIcon.Trigger:
position = QtGui.QCursor().pos()
self.contextMenu().popup(position)
@ -128,119 +155,24 @@ class TrayMainWindow(QtWidgets.QMainWindow):
Every widget should have set this window as parent because
QSystemTrayIcon widget is not allowed to be a parent of any widget.
:param app: Qt application manages application's control flow
:type app: QtWidgets.QApplication
.. note::
*TrayMainWindow* has ability to show **working** widget.
Calling methods:
- ``show_working()``
- ``hide_working()``
.. todo:: Hide working widget if idle is too long
"""
def __init__(self, app):
super().__init__()
super(TrayMainWindow, self).__init__()
self.app = app
self.set_working_widget()
self.trayIcon = SystemTrayIcon(self)
self.trayIcon.show()
def set_working_widget(self):
image_file = resources.get_resource("icons", "working.svg")
img_pix = QtGui.QPixmap(image_file)
if image_file.endswith('.svg'):
widget = QtSvg.QSvgWidget(image_file)
else:
widget = QtWidgets.QLabel()
widget.setPixmap(img_pix)
# Set widget properties
widget.setGeometry(img_pix.rect())
widget.setMask(img_pix.mask())
widget.setWindowFlags(
QtCore.Qt.WindowStaysOnTopHint | QtCore.Qt.FramelessWindowHint
)
widget.setAttribute(QtCore.Qt.WA_TranslucentBackground, True)
self.center_widget(widget)
self._working_widget = widget
self.helper = DragAndDropHelper(self._working_widget)
def center_widget(self, widget):
frame_geo = widget.frameGeometry()
screen = self.app.desktop().cursor().pos()
center_point = self.app.desktop().screenGeometry(
self.app.desktop().screenNumber(screen)
).center()
frame_geo.moveCenter(center_point)
widget.move(frame_geo.topLeft())
def show_working(self):
self._working_widget.show()
def hide_working(self):
self.center_widget(self._working_widget)
self._working_widget.hide()
class DragAndDropHelper:
""" Helper adds to widget drag and drop ability
:param widget: Qt Widget where drag and drop ability will be added
"""
def __init__(self, widget):
self.widget = widget
self.widget.mousePressEvent = self.mousePressEvent
self.widget.mouseMoveEvent = self.mouseMoveEvent
self.widget.mouseReleaseEvent = self.mouseReleaseEvent
def mousePressEvent(self, event):
self.__mousePressPos = None
self.__mouseMovePos = None
if event.button() == QtCore.Qt.LeftButton:
self.__mousePressPos = event.globalPos()
self.__mouseMovePos = event.globalPos()
def mouseMoveEvent(self, event):
if event.buttons() == QtCore.Qt.LeftButton:
# adjust offset from clicked point to origin of widget
currPos = self.widget.mapToGlobal(
self.widget.pos()
)
globalPos = event.globalPos()
diff = globalPos - self.__mouseMovePos
newPos = self.widget.mapFromGlobal(currPos + diff)
self.widget.move(newPos)
self.__mouseMovePos = globalPos
def mouseReleaseEvent(self, event):
if self.__mousePressPos is not None:
moved = event.globalPos() - self.__mousePressPos
if moved.manhattanLength() > 3:
event.ignore()
return
self.tray_widget = SystemTrayIcon(self)
self.tray_widget.show()
class PypeTrayApplication(QtWidgets.QApplication):
"""Qt application manages application's control flow."""
def __init__(self):
super(self.__class__, self).__init__(sys.argv)
super(PypeTrayApplication, self).__init__(sys.argv)
# Allows to close widgets without exiting app
self.setQuitOnLastWindowClosed(False)
# Allow show icon istead of python icon in task bar (Windows)
if os.name == "nt":
import ctypes
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(
u"pype_tray"
)
# Sets up splash
splash_widget = self.set_splash()

View file

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

@ -1 +1 @@
Subproject commit eae14f2960c4ccf2f0211e0726e88563129c0296
Subproject commit 9e6b0d02e5a147cbafdcaeee7d786d4767e14c94

125
start.py
View file

@ -112,7 +112,7 @@ if getattr(sys, 'frozen', False):
os.environ["PYTHONPATH"] = os.pathsep.join(paths)
from igniter import BootstrapRepos # noqa: E402
from igniter.tools import load_environments # noqa: E402
from igniter.tools import get_pype_path_from_db # noqa
from igniter.bootstrap_repos import PypeVersion # noqa: E402
bootstrap = BootstrapRepos()
@ -120,32 +120,26 @@ silent_commands = ["run", "igniter", "standalonepublisher",
"extractenvironments"]
def set_environments() -> None:
"""Set loaded environments.
def set_pype_global_environments() -> None:
"""Set global pype's environments."""
import acre
.. todo:
better handling of environments
from pype.settings import get_environments
"""
try:
import acre
except ImportError:
if getattr(sys, 'frozen', False):
sys.path.append(os.path.join(
os.path.dirname(sys.executable),
"dependencies"
))
import acre
try:
env = load_environments(["global"])
except OSError as e:
print(f"!!! {e}")
sys.exit(1)
all_env = get_environments()
env = acre.merge(env, dict(os.environ))
# TODO Global environments will be stored in "general" settings so loading
# will be modified and can be done in igniter.
env = acre.merge(all_env["global"], dict(os.environ))
os.environ.clear()
os.environ.update(env)
# Hardcoded default values
os.environ["PYBLISH_GUI"] = "pyblish_pype"
# Change scale factor only if is not set
if "QT_AUTO_SCREEN_SCALE_FACTOR" not in os.environ:
os.environ["QT_AUTO_SCREEN_SCALE_FACTOR"] = "1"
def run(arguments: list, env: dict = None) -> int:
"""Use correct executable to run stuff.
@ -269,9 +263,13 @@ def _process_arguments() -> tuple:
# handle igniter
# this is helper to run igniter before anything else
if "igniter" in sys.argv:
pass
# import igniter
# igniter.run()
import igniter
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
@ -298,7 +296,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:
@ -364,19 +364,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),
@ -440,6 +462,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__)))
@ -497,7 +521,6 @@ def _bootstrap_from_code(use_version):
def boot():
"""Bootstrap Pype."""
version_path = None
# ------------------------------------------------------------------------
# Play animation
@ -529,7 +552,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.
@ -537,7 +560,12 @@ def boot():
os.environ["PYPE_ROOT"] = os.path.dirname(sys.executable)
else:
os.environ["PYPE_ROOT"] = os.path.dirname(__file__)
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
@ -566,10 +594,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]
@ -582,8 +612,12 @@ def boot():
from pype.lib import terminal as t
from pype.version import __version__
print(">>> loading environments ...")
# Must happen before `set_modules_environments`
# Avalon environments must be set before avalon module is imported
print(" - for Avalon ...")
set_avalon_environments()
print(" - global Pype ...")
set_pype_global_environments()
print(" - for modules ...")
set_modules_environments()
assert version_path, "Version path not defined."
@ -593,10 +627,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
@ -610,7 +641,7 @@ def boot():
try:
cli.main(obj={}, prog_name="pype")
except Exception:
except Exception: # noqa
exc_info = sys.exc_info()
print("!!! Pype crashed:")
traceback.print_exception(*exc_info)

View file

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

View file

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

View file

@ -0,0 +1,41 @@
[ScriptFile]
Type=filename
Label=Script File
Category=Python Options
CategoryOrder=0
Index=0
Description=The script file to be executed.
Required=false
DisableIfBlank=true
[Arguments]
Type=string
Label=Arguments
Category=Python Options
CategoryOrder=0
Index=1
Description=The arguments to pass to the script. If no arguments are required, leave this blank.
Required=false
DisableIfBlank=true
[Version]
Type=enum
Values=3.0
Label=Version
Category=Python Options
CategoryOrder=0
Index=2
Description=The version of Python to use.
Required=false
DisableIfBlank=true
[SingleFramesOnly]
Type=boolean
Label=Single Frames Only
Category=Job Options
CategoryOrder=1
Index=0
Description=If enabled, the plugin will only render one frame at a time even if a single task contains a chunk of frames.
Required=true
DisableIfBlank=true
Default=false