From bcf75bf37e06ff38575e6f92475260feb9f82425 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Mon, 23 Nov 2020 12:16:31 +0100 Subject: [PATCH] handle missing directory --- igniter/bootstrap_repos.py | 168 ++++++++++++++++-- pype.py | 63 +++++-- pype/api.py | 4 + pype/cli.py | 7 +- pype/lib/__init__.py | 43 +++-- pype/lib/applications.py | 13 +- pype/lib/avalon_context.py | 25 ++- pype/lib/config.py | 2 +- pype/lib/deprecated.py | 4 +- pype/lib/log.py | 19 ++ pype/lib/plugin_tools.py | 4 +- pype/modules/ftrack/__init__.py | 7 + pype/modules/logging/tray/gui/models.py | 2 +- pype/pype_commands.py | 17 +- .../defaults/environments/global.json | 2 +- .../system_settings/global/general.json | 5 - pype/tools/launcher/actions.py | 3 + pype/tools/settings/__init__.py | 2 +- pype/tools/tray/pype_tray.py | 18 +- repos/avalon-core | 2 +- repos/pype-config | 2 +- requirements.txt | 5 + setup.py | 1 + tests/igniter/test_bootstrap_repos.py | 130 +++++++++++++- 24 files changed, 443 insertions(+), 105 deletions(-) diff --git a/igniter/bootstrap_repos.py b/igniter/bootstrap_repos.py index 3e76f1e3e8..d778e096ef 100644 --- a/igniter/bootstrap_repos.py +++ b/igniter/bootstrap_repos.py @@ -6,9 +6,11 @@ import re import logging as log import shutil import tempfile -from typing import Union, Callable, Dict +from typing import Union, Callable, List from zipfile import ZipFile from pathlib import Path +import functools + from speedcopy import copyfile from appdirs import user_data_dir @@ -17,6 +19,125 @@ from pype.lib import PypeSettingsRegistry from .tools import load_environments +@functools.total_ordering +class PypeVersion: + """Class for storing information about Pype version. + + Attributes: + major (int): [1].2.3-variant-client + minor (int): 1.[2].3-variant-client + subversion (int): 1.2.[3]-variant-client + variant (str): 1.2.3-[variant]-client + client (str): 1.2.3-variant-[client] + path (str): path to Pype + + """ + major = 0 + minor = 0 + subversion = 0 + variant = "production" + client = None + path = None + + @property + def version(self): + """return formatted version string.""" + return self._compose_version() + + @version.setter + def version(self, val): + decomposed = self._decompose_version(val) + self.major = decomposed[0] + self.minor = decomposed[1] + self.subversion = decomposed[2] + self.variant = decomposed[3] + self.client = decomposed[4] + + def __init__(self, major: int = None, minor: int = None, + subversion: int = None, version: str = None, + variant: str = "production", client: str = None, + path: Path = None): + self.path = path + self._version_regex = re.compile( + r"(?P\d+)\.(?P\d+)\.(?P\d+)(-?((?Pstaging)|(?P.+))(-(?P.+))?)?") # noqa: E501 + + if major is None or minor is None or subversion is None: + if version is None: + raise ValueError("Need version specified in some way.") + if version: + values = self._decompose_version(version) + self.major = values[0] + self.minor = values[1] + self.subversion = values[2] + self.variant = values[3] + self.client = values[4] + else: + self.major = major + self.minor = minor + self.subversion = subversion + # variant is set only if it is "staging", otherwise "production" is + # implied and no need to mention it in version string. + if variant == "staging": + self.variant = variant + self.client = client + + def _compose_version(self): + version = "{}.{}.{}".format(self.major, self.minor, self.subversion) + if self.variant == "staging": + version = "{}-{}".format(version, self.variant) + + if self.client: + version = "{}-{}".format(version, self.client) + + return version + + def _decompose_version(self, version_string: str) -> tuple: + m = re.match(self._version_regex, version_string) + if not m: + raise ValueError( + "Cannot parse version string: {}".format(version_string)) + + variant = None + if m.group("variant") == "staging": + variant = "staging" + + client = m.group("client") or m.group("cli") + + return (int(m.group("major")), int(m.group("minor")), + int(m.group("sub")), variant, client) + + def __eq__(self, other): + if not isinstance(other, self.__class__): + return False + return self.version == other.version + + def __str__(self): + return self.version + + def __repr__(self): + return "{}, {}: {}".format( + self.__class__.__name__, self.version, self.path) + + def __hash__(self): + return hash(self.version) + + def __lt__(self, other): + if self.major < other.major: + return True + + if self.major <= other.major and self.minor < other.minor: + return True + if self.major <= other.major and self.minor <= other.minor and self.subversion < other.subversion: + return True + + if self.major == other.major and self.minor == other.minor and \ + self.subversion == other.subversion and \ + self.variant == "staging": + return True + + return False + + class BootstrapRepos: """Class for bootstrapping local Pype installation. @@ -55,6 +176,22 @@ class BootstrapRepos: else: self.live_repo_dir = Path(Path(__file__).parent / ".." / "repos") + @staticmethod + def get_version_path_from_list(version:str, version_list:list) -> Path: + """Get path for specific version in list of Pype versions. + + Args: + version (str): Version string to look for (1.2.4-staging) + version_list (list of PypeVersion): list of version to search. + + Returns: + Path: Path to given version. + + """ + for v in version_list: + if str(v) == version: + return v.path + @staticmethod def get_local_version() -> str: """Get version of local Pype.""" @@ -162,7 +299,8 @@ class BootstrapRepos: assert repo_files != 0, f"No repositories to include in {include_dir}" pype_inc = 0 if include_pype: - pype_files = sum(len(files) for _, _, files in os.walk('pype')) + pype_files = sum(len(files) for _, _, files in os.walk( + include_dir.parent)) repo_inc = 48.0 / float(repo_files) pype_inc = 48.0 / float(pype_files) else: @@ -224,7 +362,7 @@ class BootstrapRepos: os.environ["PYTHONPATH"] = os.pathsep.join(paths) def find_pype( - self, pype_path: Path = None) -> Union[Dict[str, Path], None]: + self, pype_path: Path = None) -> Union[List[PypeVersion], None]: """Get ordered dict of detected Pype version. Resolution order for Pype is following: @@ -265,15 +403,23 @@ class BootstrapRepos: if not dir_to_search.exists(): return None - _pype_versions = {} + _pype_versions = [] + file_pattern = re.compile(r"^pype-repositories-v(?P\d+\.\d+\.\d*.+?).zip$") # noqa: E501 for file in dir_to_search.iterdir(): m = re.match( - r"^pype-repositories-v(?P\d+\.\d+\.\d+).zip$", + file_pattern, file.name) if m: - _pype_versions[m.group("version")] = file + try: + _pype_versions.append( + PypeVersion( + version=m.group("version"), path=file)) + except ValueError: + # cannot parse version string + print(m) + pass - return dict(sorted(_pype_versions.items())) + return sorted(_pype_versions) @staticmethod def _get_pype_from_mongo(mongo_url: str) -> Union[Path, None]: @@ -336,12 +482,10 @@ class BootstrapRepos: # either "live" Pype repository, or multiple zip files. versions = self.find_pype(pype_path) if versions: - latest_version = ( - list(versions.keys())[-1], list(versions.values())[-1]) self._log.info(f"found Pype zips in [ {pype_path} ].") - self._log.info(f"latest version found is [ {latest_version[0]} ]") + self._log.info(f"latest version found is [ {versions[-1]} ]") - destination = self.data_dir / latest_version[1].name + 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. @@ -359,7 +503,7 @@ class BootstrapRepos: destination.parent.mkdir(parents=True) try: - copyfile(latest_version[1].as_posix(), destination.as_posix()) + copyfile(versions[-1].path.as_posix(), destination.as_posix()) except OSError: self._log.error( "cannot copy detected version to user data directory", diff --git a/pype.py b/pype.py index d6a63d3dce..df5831e26a 100644 --- a/pype.py +++ b/pype.py @@ -32,6 +32,9 @@ Pype depends on connection to `MongoDB`_. You can specify MongoDB connection string via `AVALON_MONGO` set in environment or it can be set in user settings or via **Igniter** GUI. +Todo: + Move or remove bootstrapping environments out of the code. + .. _MongoDB: https://www.mongodb.com/ @@ -40,6 +43,7 @@ import os import re import sys import traceback +from pathlib import Path from igniter.tools import load_environments @@ -79,7 +83,6 @@ def boot(): """ print(art) - print(">>> loading environments ...") set_environments() # find pype versions bootstrap = BootstrapRepos() @@ -89,7 +92,7 @@ def boot(): use_version = None for arg in sys.argv: - m = re.search(r"--use-version=(?P\d+\.\d+\.\d+)", arg) + m = re.search(r"--use-version=(?P\d+\.\d+\.\d*.+?)", arg) if m and m.group('version'): use_version = m.group('version') break @@ -115,28 +118,32 @@ def boot(): import igniter igniter.run() - if use_version in pype_versions.keys(): + version_path = BootstrapRepos.get_version_path_from_list( + use_version, pype_versions) + if version_path: # use specified - bootstrap.add_paths_from_archive(pype_versions[use_version]) - use_version = pype_versions[use_version] + bootstrap.add_paths_from_archive(version_path) + else: if use_version is not None: print(("!!! Specified version was not found, using " "latest available")) # use latest - bootstrap.add_paths_from_archive(list(pype_versions.values())[-1]) - use_version = list(pype_versions.keys())[-1] + version_path = pype_versions[-1].path + bootstrap.add_paths_from_archive(version_path) + use_version = str(pype_versions[-1]) - os.environ["PYPE_ROOT"] = pype_versions[use_version].as_posix() + os.environ["PYPE_ROOT"] = version_path.as_posix() else: # run through repos and add them to sys.path and PYTHONPATH pype_root = os.path.dirname(os.path.realpath(__file__)) local_version = bootstrap.get_local_version() if use_version and use_version != local_version: - if use_version in pype_versions.keys(): + version_path = BootstrapRepos.get_version_path_from_list( + use_version, pype_versions) + if version_path: # use specified - bootstrap.add_paths_from_archive(pype_versions[use_version]) - use_version = pype_versions[use_version] + bootstrap.add_paths_from_archive(version_path) os.environ["PYPE_ROOT"] = pype_root repos = os.listdir(os.path.join(pype_root, "repos")) @@ -149,6 +156,18 @@ def boot(): paths += repos os.environ["PYTHONPATH"] = os.pathsep.join(paths) + # DEPRECATED: remove when `pype-config` dissolves into Pype for good. + # .-=-----------------------=-=. ^ .=-=--------------------------=-. + os.environ["PYPE_CONFIG"] = os.path.join( + os.environ["PYPE_ROOT"], "repos", "pype-config") + os.environ["PYPE_MODULE_ROOT"] = os.environ["PYPE_ROOT"] + # ------------------------------------------------------------------ + # HARDCODED: + os.environ["AVALON_DB"] = "Avalon" + os.environ["AVALON_LABEL"] = "Pype" + os.environ["AVALON_TIMEOUT"]= "1000" + # .-=-----------------------=-=. v .=-=--------------------------=-. + # delete Pype module from cache so it is used from specific version try: del sys.modules["pype"] @@ -159,10 +178,17 @@ def boot(): from pype import cli from pype.lib import terminal as t from pype.version import __version__ + print(">>> loading environments ...") + set_environments() - t.echo(f"*** Pype [{__version__}] --------------------------------------") - t.echo(">>> Using Pype from [ {} ]".format(os.path.dirname(cli.__file__))) - print_info() + info = get_info() + info.insert(0, ">>> Using Pype from [ {} ]".format( + os.path.dirname(cli.__file__))) + + info_length = len(max(info, key=len)) + info.insert(0, f"*** Pype [{__version__}] " + "-" * info_length) + for i in info: + t.echo(i) try: cli.main(obj={}, prog_name="pype") @@ -173,9 +199,8 @@ def boot(): sys.exit(1) -def print_info() -> None: +def get_info() -> list: """Print additional information to console.""" - from pype.lib import terminal as t from pype.lib.mongo import get_default_components from pype.lib.log import LOG_DATABASE_NAME, LOG_COLLECTION_NAME @@ -211,10 +236,12 @@ def print_info() -> None: infos.append((" - auth source", components["auth_db"])) maximum = max([len(i[0]) for i in infos]) + formatted = [] for info in infos: padding = (maximum - len(info[0])) + 1 - t.echo("... {}:{}[ {} ]".format(info[0], " " * padding, info[1])) - print('\n') + formatted.append( + "... {}:{}[ {} ]".format(info[0], " " * padding, info[1])) + return formatted boot() diff --git a/pype/api.py b/pype/api.py index 3eb6a409e6..3b906a27ff 100644 --- a/pype/api.py +++ b/pype/api.py @@ -48,12 +48,16 @@ from .lib import ( # Special naming case for subprocess since its a built-in method. from .lib import _subprocess as subprocess +# for backward compatibility with Pype 2 +Logger = PypeLogger + __all__ = [ "system_settings", "project_settings", "environments", "PypeLogger", + "Logger", "Anatomy", "config", "execute", diff --git a/pype/cli.py b/pype/cli.py index 0a825ff671..72f15c31f4 100644 --- a/pype/cli.py +++ b/pype/cli.py @@ -17,10 +17,15 @@ def main(ctx): ctx.invoke(tray) +@main.command() +@click.option("-d", "--dev", is_flag=True, help="Settings in Dev mode") +def settings(dev=False): + PypeCommands().launch_settings_gui(dev) + @main.command() @click.option("-d", "--debug", is_flag=True, help=("Run pype tray in debug mode")) -def tray(debug): +def tray(debug=False): """Launch pype tray. Default action of pype command is to launch tray widget to control basic diff --git a/pype/lib/__init__.py b/pype/lib/__init__.py index e889215620..e9b091196b 100644 --- a/pype/lib/__init__.py +++ b/pype/lib/__init__.py @@ -1,7 +1,25 @@ # -*- coding: utf-8 -*- """Pype module API.""" -from .ffmpeg_utils import ffprobe_streams +from .terminal import Terminal +from .execute import execute +from .log import PypeLogger, timeit +from .mongo import ( + decompose_url, + compose_url, + get_default_components +) +from .anatomy import Anatomy + +from .config import ( + get_datetime_data, + load_json, + collect_json_from_path, + get_presets, + get_init_presets, + update_dict +) + from .path_tools import ( version_up, get_version_from_path, @@ -9,6 +27,7 @@ from .path_tools import ( get_paths_from_environ, get_ffmpeg_tool_path ) +from .ffmpeg_utils import ffprobe_streams from .plugin_tools import filter_pyblish_plugins, source_hash from .applications import ( ApplicationLaunchFailed, @@ -30,23 +49,8 @@ from .deprecated import ( get_avalon_database, set_io_database ) -from .terminal import Terminal -from .anatomy import Anatomy -from .config import ( - get_datetime_data, - load_json, - collect_json_from_path, - get_presets, - get_init_presets, - update_dict -) -from .execute import execute -from .log import PypeLogger -from .mongo import ( - decompose_url, - compose_url, - get_default_components -) + + from .user_settings import IniSettingRegistry from .user_settings import JSONSettingRegistry @@ -104,5 +108,6 @@ __all__ = [ "get_default_components", "IniSettingRegistry", "JSONSettingRegistry", - "PypeSettingsRegistry" + "PypeSettingsRegistry", + "timeit" ] diff --git a/pype/lib/applications.py b/pype/lib/applications.py index 159840ceb5..4b7daf62fa 100644 --- a/pype/lib/applications.py +++ b/pype/lib/applications.py @@ -6,11 +6,7 @@ import platform import logging import subprocess -import acre - -import avalon.lib - -from ..api import Anatomy, Logger, config +from . import Anatomy, config from .hooks import execute_hook from .deprecated import get_avalon_database @@ -27,6 +23,9 @@ def launch_application(project_name, asset_name, task_name, app_name): TODO(iLLiCiT): This should be split into more parts. """ # `get_avalon_database` is in Pype 3 replaced with using `AvalonMongoDB` + import acre + import avalon.lib + database = get_avalon_database() project_document = database[project_name].find_one({"type": "project"}) asset_document = database[project_name].find_one({ @@ -193,7 +192,7 @@ def launch_application(project_name, asset_name, task_name, app_name): return popen -class ApplicationAction(avalon.api.Action): +class ApplicationAction: """Default application launcher This is a convenience application Action that when "config" refers to a @@ -213,7 +212,7 @@ class ApplicationAction(avalon.api.Action): @property def log(self): if self._log is None: - self._log = Logger().get_logger(self.__class__.__name__) + self._log = logging.getLogger(self.__class__.__name__) return self._log def is_compatible(self, session): diff --git a/pype/lib/avalon_context.py b/pype/lib/avalon_context.py index 6cecdb93e3..d4369a5a1f 100644 --- a/pype/lib/avalon_context.py +++ b/pype/lib/avalon_context.py @@ -3,14 +3,22 @@ import json import re import logging import collections +import functools + +from . import config -from avalon import io, pipeline -from ..api import config -import avalon.api log = logging.getLogger("AvalonContext") +def with_avalon(func): + @functools.wraps(func) + def wrap_avalon(*args, **kwargs): + from avalon import api, io, pipeline + return func(*args, **kwargs) + pass + +@with_avalon def is_latest(representation): """Return whether the representation is from latest version @@ -37,7 +45,7 @@ def is_latest(representation): else: return False - +@with_avalon def any_outdated(): """Return whether the current scene has any outdated content""" @@ -65,7 +73,7 @@ def any_outdated(): checked.add(representation) return False - +@with_avalon def get_asset(asset_name=None): """ Returning asset document from database by its name. @@ -91,6 +99,7 @@ def get_asset(asset_name=None): return asset_document +@with_avalon def get_hierarchy(asset_name=None): """ Obtain asset hierarchy path string from mongo db @@ -138,6 +147,7 @@ def get_hierarchy(asset_name=None): return "/".join(hierarchy_items) +@with_avalon def get_linked_assets(asset_entity): """Return linked assets for `asset_entity` from DB @@ -152,6 +162,7 @@ def get_linked_assets(asset_entity): return inputs +@with_avalon def get_latest_version(asset_name, subset_name, dbcon=None, project_name=None): """Retrieve latest version from `asset_name`, and `subset_name`. @@ -257,6 +268,7 @@ class BuildWorkfile: return containers + @with_avalon def build_workfile(self): """Prepares and load containers into workfile. @@ -396,6 +408,7 @@ class BuildWorkfile: # Return list of loaded containers return loaded_containers + @with_avalon def get_build_presets(self, task_name): """ Returns presets to build workfile for task name. @@ -654,6 +667,7 @@ class BuildWorkfile: "containers": containers } + @with_avalon def _load_containers( self, repres_by_subset_id, subsets_by_id, profiles_per_subset_id, loaders_by_name @@ -774,6 +788,7 @@ class BuildWorkfile: return loaded_containers + @with_avalon def _collect_last_version_repres(self, asset_entities): """Collect subsets, versions and representations for asset_entities. diff --git a/pype/lib/config.py b/pype/lib/config.py index 1b7f11cf14..6244d65d68 100644 --- a/pype/lib/config.py +++ b/pype/lib/config.py @@ -253,7 +253,7 @@ def get_presets(project=None, first_run=False): def get_init_presets(project=None): """Loads content of presets. - Llike :func:`get_presets()`` but also evaluate `init.json` + Like :func:`get_presets()`` but also evaluate `init.json` pointer to default presets. Args: diff --git a/pype/lib/deprecated.py b/pype/lib/deprecated.py index e7296f67ef..7d4fd5a2b1 100644 --- a/pype/lib/deprecated.py +++ b/pype/lib/deprecated.py @@ -1,7 +1,5 @@ import os -from avalon import io - def get_avalon_database(): """Mongo database used in avalon's io. @@ -9,6 +7,7 @@ def get_avalon_database(): * Function is not used in pype 3.0 where was replaced with usage of AvalonMongoDB. """ + from avalon import io if io._database is None: set_io_database() return io._database @@ -20,6 +19,7 @@ def set_io_database(): * Function is not used in pype 3.0 where was replaced with usage of AvalonMongoDB. """ + from avalon import io required_keys = ["AVALON_PROJECT", "AVALON_ASSET", "AVALON_SILO"] for key in required_keys: os.environ[key] = os.environ.get(key, "") diff --git a/pype/lib/log.py b/pype/lib/log.py index 384532ea1b..71623eda82 100644 --- a/pype/lib/log.py +++ b/pype/lib/log.py @@ -379,3 +379,22 @@ class PypeLogger: _mongo_logging = False return logger + + +def timeit(method): + """ Decorator to print how much time function took. + For debugging. + Depends on presence of 'log' object + """ + def timed(*args, **kw): + ts = time.time() + result = method(*args, **kw) + te = time.time() + if 'log_time' in kw: + name = kw.get('log_name', method.__name__.upper()) + kw['log_time'][name] = int((te - ts) * 1000) + else: + log.debug('%r %2.2f ms' % (method.__name__, (te - ts) * 1000)) + print('%r %2.2f ms' % (method.__name__, (te - ts) * 1000)) + return result + return timed diff --git a/pype/lib/plugin_tools.py b/pype/lib/plugin_tools.py index 0b6ace807e..535100c1c2 100644 --- a/pype/lib/plugin_tools.py +++ b/pype/lib/plugin_tools.py @@ -4,7 +4,7 @@ import os import inspect import logging -from ..api import config +from . import get_presets log = logging.getLogger(__name__) @@ -25,7 +25,7 @@ def filter_pyblish_plugins(plugins): host = api.current_host() - presets = config.get_presets().get('plugins', {}) + presets = get_presets().get('plugins', {}) # iterate over plugins for plugin in plugins[:]: diff --git a/pype/modules/ftrack/__init__.py b/pype/modules/ftrack/__init__.py index a1f0b00ce0..06cc5d5565 100644 --- a/pype/modules/ftrack/__init__.py +++ b/pype/modules/ftrack/__init__.py @@ -1,7 +1,14 @@ +import os + from . import ftrack_server from .ftrack_server import FtrackServer, check_ftrack_url from .lib import BaseHandler, BaseEvent, BaseAction, ServerAction +from pype.api import system_settings + +os.environ["FTRACK_SERVER"] = ( + system_settings()["global"]["modules"]["Ftrack"]["ftrack_server"] +) __all__ = ( "ftrack_server", "FtrackServer", diff --git a/pype/modules/logging/tray/gui/models.py b/pype/modules/logging/tray/gui/models.py index ae2666f501..3591f3dde2 100644 --- a/pype/modules/logging/tray/gui/models.py +++ b/pype/modules/logging/tray/gui/models.py @@ -1,7 +1,7 @@ import collections from Qt import QtCore, QtGui from pype.api import Logger -from pypeapp.lib.log import _bootstrap_mongo_log, LOG_COLLECTION_NAME +from pype.lib.log import _bootstrap_mongo_log, LOG_COLLECTION_NAME log = Logger().get_logger("LogModel", "LoggingModule") diff --git a/pype/pype_commands.py b/pype/pype_commands.py index 573cbcb88d..cc54deb2ee 100644 --- a/pype/pype_commands.py +++ b/pype/pype_commands.py @@ -4,9 +4,6 @@ import os import subprocess import sys -from pype.lib import PypeLogger as Logger -from pype.lib import execute - class PypeCommands: """Class implementing commands used by Pype. @@ -14,7 +11,9 @@ class PypeCommands: Most of its methods are called by :mod:`cli` module. """ @staticmethod - def launch_tray(debug): + def launch_tray(debug=False): + from pype.lib import PypeLogger as Logger + from pype.lib import execute if debug: execute([ sys.executable, @@ -50,6 +49,16 @@ class PypeCommands: creationflags=detached_process ) + @staticmethod + def launch_settings_gui(dev): + from pype.lib import execute + + args = [sys.executable, "-m", "pype.tools.settings"] + if dev: + args.append("--develop") + return_code = execute(args) + return return_code + def launch_eventservercli(self, args): pass diff --git a/pype/settings/defaults/environments/global.json b/pype/settings/defaults/environments/global.json index 717e337db8..f3cc9867c3 100644 --- a/pype/settings/defaults/environments/global.json +++ b/pype/settings/defaults/environments/global.json @@ -4,7 +4,7 @@ "PYPE_APP_ROOT": "{PYPE_SETUP_PATH}/pypeapp", "PYPE_MODULE_ROOT": "{PYPE_SETUP_PATH}/repos/pype", "PYPE_PROJECT_PLUGINS": "", - "STUDIO_SOFT": "{PYP_SETUP_ROOT}/soft", + "STUDIO_SOFT": "{PYPE_SETUP_ROOT}/soft", "FFMPEG_PATH": { "windows": "{VIRTUAL_ENV}/localized/ffmpeg_exec/windows/bin;{PYPE_SETUP_PATH}/vendor/bin/ffmpeg_exec/windows/bin", "darwin": "{VIRTUAL_ENV}/localized/ffmpeg_exec/darwin/bin:{PYPE_SETUP_PATH}/vendor/bin/ffmpeg_exec/darwin/bin", diff --git a/pype/settings/defaults/system_settings/global/general.json b/pype/settings/defaults/system_settings/global/general.json index 23e8a3ff5d..e57b41cd12 100644 --- a/pype/settings/defaults/system_settings/global/general.json +++ b/pype/settings/defaults/system_settings/global/general.json @@ -14,8 +14,6 @@ "environment": { "__environment_keys__": { "global": [ - "PYPE_APP_ROOT", - "PYPE_MODULE_ROOT", "FFMPEG_PATH", "PATH", "PYTHONPATH", @@ -24,8 +22,6 @@ "PYBLISH_GUI" ] }, - "PYPE_APP_ROOT": "{PYPE_SETUP_PATH}/pypeapp", - "PYPE_MODULE_ROOT": "{PYPE_SETUP_PATH}/repos/pype", "FFMPEG_PATH": { "windows": "{VIRTUAL_ENV}/localized/ffmpeg_exec/windows/bin;{PYPE_SETUP_PATH}/vendor/bin/ffmpeg_exec/windows/bin", "darwin": "{VIRTUAL_ENV}/localized/ffmpeg_exec/darwin/bin:{PYPE_SETUP_PATH}/vendor/bin/ffmpeg_exec/darwin/bin", @@ -33,7 +29,6 @@ }, "PATH": [ "{PYPE_CONFIG}/launchers", - "{PYPE_APP_ROOT}", "{FFMPEG_PATH}", "{PATH}" ], diff --git a/pype/tools/launcher/actions.py b/pype/tools/launcher/actions.py index 80e6f71ae7..21b324402d 100644 --- a/pype/tools/launcher/actions.py +++ b/pype/tools/launcher/actions.py @@ -102,3 +102,6 @@ def register_environment_actions(): module, str(e) ) ) + +class ApplicationLaunchFailed(Exception): + pass \ No newline at end of file diff --git a/pype/tools/settings/__init__.py b/pype/tools/settings/__init__.py index 7df121f06e..88f33ac188 100644 --- a/pype/tools/settings/__init__.py +++ b/pype/tools/settings/__init__.py @@ -1,4 +1,4 @@ -from settings import style, MainWidget +from .settings import style, MainWidget __all__ = ( diff --git a/pype/tools/tray/pype_tray.py b/pype/tools/tray/pype_tray.py index 105982f77a..2deeccdbd6 100644 --- a/pype/tools/tray/pype_tray.py +++ b/pype/tools/tray/pype_tray.py @@ -120,21 +120,9 @@ class TrayManager: self.start_modules() def _add_version_item(self): - config_file_path = os.path.join( - os.environ["PYPE_SETUP_PATH"], "pypeapp", "config.ini" - ) - default_config = {} - if os.path.exists(config_file_path): - config = configparser.ConfigParser() - config.read(config_file_path) - try: - default_config = config["CLIENT"] - except Exception: - pass - - subversion = default_config.get("subversion") - client_name = default_config.get("client_name") + subversion = os.environ.get("PYPE_SUBVERSION") + client_name = os.environ.get("PYPE_CLIENT") version_string = pype.version.__version__ if subversion: @@ -224,7 +212,7 @@ class TrayManager: "Module \"{}\" does not have attribute \"{}\"." " Check your settings please." ).format(import_path, key)) - + p = os.environ["AVALON_SCHEMA"] obj = module.tray_init(self.tray_widget, self.main_window) name = obj.__class__.__name__ if hasattr(obj, 'tray_menu'): diff --git a/repos/avalon-core b/repos/avalon-core index d2fcbe96ef..3c10ad50aa 160000 --- a/repos/avalon-core +++ b/repos/avalon-core @@ -1 +1 @@ -Subproject commit d2fcbe96efbca3676ff09a824ef9f5c61483f960 +Subproject commit 3c10ad50aa1905dcd06959e08e7e0994561eb3e4 diff --git a/repos/pype-config b/repos/pype-config index f0298b2d07..178f5cdd18 160000 --- a/repos/pype-config +++ b/repos/pype-config @@ -1 +1 @@ -Subproject commit f0298b2d0757e1ae126d740cbe08835f6a4ee5e0 +Subproject commit 178f5cdd1859d079bc16094b321f8f06c1306c36 diff --git a/requirements.txt b/requirements.txt index 349c179f19..0044a655dc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ +aiohttp appdirs arrow certifi @@ -5,13 +6,16 @@ Click clique==1.5.0 coverage cx_Freeze +ftrack-python-api ffmpeg-python +google-api-python-client jsonschema keyring log4mongo OpenTimelineIO pathlib2 Pillow +pyinput pymongo pytest pytest-cov @@ -28,3 +32,4 @@ recommonmark toml tqdm wheel +wsrpc_aiohttp diff --git a/setup.py b/setup.py index f96d599640..76bb6a4aba 100644 --- a/setup.py +++ b/setup.py @@ -41,6 +41,7 @@ buildOptions = dict( excludes=[], bin_includes=[], include_files=[ + "igniter", "pype", "repos", "schema", diff --git a/tests/igniter/test_bootstrap_repos.py b/tests/igniter/test_bootstrap_repos.py index 7f343b1023..868b356771 100644 --- a/tests/igniter/test_bootstrap_repos.py +++ b/tests/igniter/test_bootstrap_repos.py @@ -6,17 +6,116 @@ from pathlib import Path import pytest import appdirs from igniter.bootstrap_repos import BootstrapRepos +from igniter.bootstrap_repos import PypeVersion from pype.lib import PypeSettingsRegistry @pytest.fixture -def fix_bootstrap(tmp_path): +def fix_bootstrap(tmp_path, pytestconfig): bs = BootstrapRepos() - bs.live_repo_dir = Path(os.path.abspath('repos')) + bs.live_repo_dir = pytestconfig.rootpath / 'repos' bs.data_dir = tmp_path return bs +def test_pype_version(): + v1 = PypeVersion(1, 2, 3) + assert str(v1) == "1.2.3" + + v2 = PypeVersion(1, 2, 3, client="x") + assert str(v2) == "1.2.3-x" + + v3 = PypeVersion(1, 2, 3, variant="staging") + assert str(v3) == "1.2.3-staging" + + v4 = PypeVersion(1, 2, 3, variant="staging", client="client") + assert str(v4) == "1.2.3-staging-client" + + v5 = PypeVersion(1, 2, 3, variant="foo", client="x") + assert str(v5) == "1.2.3-x" + assert v4 < v5 + + v6 = PypeVersion(1, 2, 3, variant="foo") + assert str(v6) == "1.2.3" + + v7 = PypeVersion(2, 0, 0) + assert v1 < v7 + + v8 = PypeVersion(0, 1, 5) + assert v8 < v7 + + v9 = PypeVersion(1, 2, 4) + assert v9 > v1 + + v10 = PypeVersion(1, 2, 2) + assert v10 < v1 + + assert v5 == v2 + + sort_versions = [ + PypeVersion(3, 2, 1), + PypeVersion(1, 2, 3), + PypeVersion(0, 0, 1), + PypeVersion(4, 8, 10), + PypeVersion(4, 8, 20), + PypeVersion(4, 8, 9), + PypeVersion(1, 2, 3, variant="staging"), + PypeVersion(1, 2, 3, client="client") + ] + res = sorted(sort_versions) + + assert res[0] == sort_versions[2] + assert res[1] == sort_versions[6] + assert res[2] == sort_versions[1] + assert res[-1] == sort_versions[4] + + str_versions = [ + "5.5.1", + "5.5.2-client", + "5.5.3-client-strange", + "5.5.4-staging", + "5.5.5-staging-client", + "5.6.3", + "5.6.3-staging" + ] + res_versions = [] + for v in str_versions: + res_versions.append(PypeVersion(version=v)) + + sorted_res_versions = sorted(res_versions) + + assert str(sorted_res_versions[0]) == str_versions[0] + assert str(sorted_res_versions[-1]) == str_versions[5] + + with pytest.raises(ValueError): + _ = PypeVersion() + + with pytest.raises(ValueError): + _ = PypeVersion(major=1) + + with pytest.raises(ValueError): + _ = PypeVersion(version="booobaa") + + v11 = PypeVersion(version="4.6.7-staging-client") + assert v11.major == 4 + assert v11.minor == 6 + assert v11.subversion == 7 + assert v11.variant == "staging" + assert v11.client == "client" + + +def test_get_version_path_from_list(): + versions = [ + PypeVersion(1, 2, 3, path=Path('/foo/bar')), + PypeVersion(3, 4, 5, variant="staging", path=Path("/bar/baz")), + PypeVersion(6, 7, 8, client="x", path=Path("boo/goo")) + ] + path = BootstrapRepos.get_version_path_from_list( + "3.4.5-staging", versions) + + assert path == Path("/bar/baz") + + def test_install_live_repos(fix_bootstrap, printer): rf = fix_bootstrap.install_live_repos() sep = os.path.sep @@ -50,11 +149,20 @@ def test_find_pype(fix_bootstrap, tmp_path_factory, monkeypatch, printer): test_versions_1 = [ "pype-repositories-v5.5.1.zip", + "pype-repositories-v5.5.2-client.zip", + "pype-repositories-v5.5.3-client-strange.zip", + "pype-repositories-v5.5.4-staging.zip", + "pype-repositories-v5.5.5-staging-client.zip", "pype-repositories-v5.6.3.zip", + "pype-repositories-v5.6.3-staging.zip" ] test_versions_2 = [ "pype-repositories-v7.2.6.zip", + "pype-repositories-v7.2.7-client.zip", + "pype-repositories-v7.2.8-client-strange.zip", + "pype-repositories-v7.2.9-staging.zip", + "pype-repositories-v7.2.10-staging-client.zip", "pype-repositories-v7.0.1.zip", ] @@ -63,6 +171,10 @@ def test_find_pype(fix_bootstrap, tmp_path_factory, monkeypatch, printer): "pype-repositories-v3.0.1.zip", "pype-repositories-v4.1.0.zip", "pype-repositories-v4.1.2.zip", + "pype-repositories-v3.0.1-client.zip", + "pype-repositories-v3.0.1-client-strange.zip", + "pype-repositories-v3.0.1-staging.zip", + "pype-repositories-v3.0.1-staging-client.zip", "pype-repositories-v3.2.0.zip", ] @@ -87,7 +199,7 @@ def test_find_pype(fix_bootstrap, tmp_path_factory, monkeypatch, printer): # we should have results as file were created assert result is not None, "no Pype version found" # latest item in `result` should be latest version found. - assert list(result.values())[-1] == Path( + assert result[-1].path == Path( fix_bootstrap.data_dir / test_versions_3[3] ), "not a latest version of Pype 3" @@ -97,8 +209,8 @@ def test_find_pype(fix_bootstrap, tmp_path_factory, monkeypatch, printer): # we should have results as file were created assert result is not None, "no Pype version found" # latest item in `result` should be latest version found. - assert list(result.values())[-1] == Path( - e_path / test_versions_1[1] + assert result[-1].path == Path( + e_path / test_versions_1[5] ), "not a latest version of Pype 1" monkeypatch.delenv("PYPE_PATH", raising=False) @@ -115,12 +227,12 @@ def test_find_pype(fix_bootstrap, tmp_path_factory, monkeypatch, printer): # we should have results as file were created assert result is not None, "no Pype version found" # latest item in `result` should be latest version found. - assert list(result.values())[-1] == Path( - r_path / test_versions_2[0] + assert result[-1].path == Path( + r_path / test_versions_2[4] ), "not a latest version of Pype 2" result = fix_bootstrap.find_pype(e_path) assert result is not None, "no Pype version found" - assert list(result.values())[-1] == Path( - e_path / test_versions_1[1] + assert result[-1].path == Path( + e_path / test_versions_1[5] ), "not a latest version of Pype 1"