From 544cdb7e870dddedd894514b2ae9f7c7b9cce9c1 Mon Sep 17 00:00:00 2001
From: Steve Brown If you are having trouble logging in with the browser, select this support link
Cancel & return to the login page
', + None, + ) + ) + self.asl_msg_help.setText( + QCoreApplication.translate( + "LoginDialog", + 'If you are having trouble logging in with the browser, select this support link
', + None, + ) + ) + # retranslateUi diff --git a/python/tk-core/python/tank/authentication/ui/resources_rc.py b/python/tk-core/python/tank/authentication/ui/resources_rc.py index 95c08531..8163a110 100644 --- a/python/tk-core/python/tank/authentication/ui/resources_rc.py +++ b/python/tk-core/python/tank/authentication/ui/resources_rc.py @@ -3715,10 +3715,17 @@ \x00\x00\x01\x8f05\xd1\xf5\ " + def qInitResources(): - QtCore.qRegisterResourceData(0x03, qt_resource_struct, qt_resource_name, qt_resource_data) + QtCore.qRegisterResourceData( + 0x03, qt_resource_struct, qt_resource_name, qt_resource_data + ) + def qCleanupResources(): - QtCore.qUnregisterResourceData(0x03, qt_resource_struct, qt_resource_name, qt_resource_data) + QtCore.qUnregisterResourceData( + 0x03, qt_resource_struct, qt_resource_name, qt_resource_data + ) + qInitResources() diff --git a/python/tk-core/python/tank/authentication/ui_authentication.py b/python/tk-core/python/tank/authentication/ui_authentication.py index 486262e6..8158e2c1 100644 --- a/python/tk-core/python/tank/authentication/ui_authentication.py +++ b/python/tk-core/python/tank/authentication/ui_authentication.py @@ -18,9 +18,9 @@ -------------------------------------------------------------------------------- """ -from .errors import AuthenticationCancelled, ShotgunAuthenticationError -from . import invoker from .. import LogManager +from . import invoker +from .errors import AuthenticationCancelled, ShotgunAuthenticationError logger = LogManager.get_logger(__name__) diff --git a/python/tk-core/python/tank/authentication/user.py b/python/tk-core/python/tank/authentication/user.py index 37c00350..56d20846 100644 --- a/python/tk-core/python/tank/authentication/user.py +++ b/python/tk-core/python/tank/authentication/user.py @@ -12,13 +12,10 @@ import threading import time -from . import sso_saml2 -from . import interactive_authentication -from . import user_impl from .. import LogManager +from . import interactive_authentication, sso_saml2, user_impl from .errors import AuthenticationCancelled - logger = LogManager.get_logger(__name__) # Ensure that the SSO-related logging will be merged in our loggin. diff --git a/python/tk-core/python/tank/authentication/user_impl.py b/python/tk-core/python/tank/authentication/user_impl.py index 3cd32e60..7d434043 100644 --- a/python/tk-core/python/tank/authentication/user_impl.py +++ b/python/tk-core/python/tank/authentication/user_impl.py @@ -18,16 +18,17 @@ at any point. -------------------------------------------------------------------------------- """ -import json + import http.client +import json -from .shotgun_wrapper import ShotgunWrapper -from tank_vendor.shotgun_api3 import Shotgun, AuthenticationFault, ProtocolError +from tank_vendor.shotgun_api3 import AuthenticationFault, ProtocolError, Shotgun -from . import session_cache -from .errors import IncompleteCredentials, UnresolvableHumanUser, UnresolvableScriptUser from .. import LogManager from ..util import pickle +from . import session_cache +from .errors import IncompleteCredentials, UnresolvableHumanUser, UnresolvableScriptUser +from .shotgun_wrapper import ShotgunWrapper # Indirection to create ShotgunWrapper instances. Great for unit testing. _shotgun_instance_factory = ShotgunWrapper @@ -606,7 +607,6 @@ def deserialize_user(payload): user_dict = pickle.loads(payload) # Find which user type we have - global __factories factory = __factories.get(user_dict.get("type")) # Unknown type, something is wrong. Maybe backward compatible code broke? if not factory: diff --git a/python/tk-core/python/tank/authentication/web_login_support.py b/python/tk-core/python/tank/authentication/web_login_support.py index a3a93e4a..21abf161 100644 --- a/python/tk-core/python/tank/authentication/web_login_support.py +++ b/python/tk-core/python/tank/authentication/web_login_support.py @@ -59,5 +59,4 @@ def get_shotgun_authenticator_support_web_login(): :returns: Bool indicating support for the Unified Login Flow. """ - global shotgun_authenticator_support_web_login return shotgun_authenticator_support_web_login diff --git a/python/tk-core/python/tank/bootstrap/__init__.py b/python/tk-core/python/tank/bootstrap/__init__.py index ada6fd37..e3edeaa0 100644 --- a/python/tk-core/python/tank/bootstrap/__init__.py +++ b/python/tk-core/python/tank/bootstrap/__init__.py @@ -8,5 +8,5 @@ # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights # not expressly granted therein are reserved by Shotgun Software Inc. -from .manager import ToolkitManager from .errors import TankBootstrapError, TankMissingTankNameError +from .manager import ToolkitManager diff --git a/python/tk-core/python/tank/bootstrap/baked_configuration.py b/python/tk-core/python/tank/bootstrap/baked_configuration.py index 21b2d81b..7ec5c64c 100644 --- a/python/tk-core/python/tank/bootstrap/baked_configuration.py +++ b/python/tk-core/python/tank/bootstrap/baked_configuration.py @@ -10,16 +10,12 @@ import os -from .configuration import Configuration -from .configuration_writer import ConfigurationWriter - -from .. import LogManager -from .. import constants - +from .. import LogManager, constants, pipelineconfig_utils +from ..errors import TankFileDoesNotExistError from ..util import ShotgunPath from ..util.pickle import store_env_var_pickled -from ..errors import TankFileDoesNotExistError -from .. import pipelineconfig_utils +from .configuration import Configuration +from .configuration_writer import ConfigurationWriter log = LogManager.get_logger(__name__) diff --git a/python/tk-core/python/tank/bootstrap/bundle_downloader.py b/python/tk-core/python/tank/bootstrap/bundle_downloader.py index 42a391fc..8a5c955a 100644 --- a/python/tk-core/python/tank/bootstrap/bundle_downloader.py +++ b/python/tk-core/python/tank/bootstrap/bundle_downloader.py @@ -9,8 +9,8 @@ # not expressly granted therein are reserved by Shotgun Software Inc. import os -from sgtk import hook -from sgtk import LogManager + +from sgtk import LogManager, hook log = LogManager.get_logger(__name__) diff --git a/python/tk-core/python/tank/bootstrap/cached_configuration.py b/python/tk-core/python/tank/bootstrap/cached_configuration.py index a8425398..2dc41623 100644 --- a/python/tk-core/python/tank/bootstrap/cached_configuration.py +++ b/python/tk-core/python/tank/bootstrap/cached_configuration.py @@ -9,21 +9,18 @@ # not expressly granted therein are reserved by Shotgun Software Inc. import os -import sys -import traceback import pprint - -from . import constants - -from ..descriptor import create_descriptor, Descriptor -from .errors import TankBootstrapError, TankMissingTankNameError - -from ..util import filesystem, version +import traceback from tank_vendor import yaml + +from .. import LogManager +from ..descriptor import Descriptor, create_descriptor +from ..util import filesystem +from . import constants from .configuration import Configuration from .configuration_writer import ConfigurationWriter -from .. import LogManager +from .errors import TankBootstrapError, TankMissingTankNameError log = LogManager.get_logger(__name__) @@ -140,7 +137,7 @@ def verify_required_shotgun_fields(self): % (storage_roots.roots_file, storage_roots.required_roots) ) - (_, unmapped_roots) = storage_roots.get_local_storages(self._sg_connection) + _, unmapped_roots = storage_roots.get_local_storages(self._sg_connection) # get a list of all defined storage roots without a corresponding PTR # local storage defined @@ -258,10 +255,10 @@ def update_configuration(self): try: # Move to backup needs to undo changes when failing because we need to put the configuration # in a usable state. - (config_backup_path, core_backup_path) = self._config_writer.move_to_backup( + config_backup_path, core_backup_path = self._config_writer.move_to_backup( undo_on_error=True ) - except Exception as e: + except Exception: log.exception( "Unexpected error while making a backup of the configuration. Toolkit will use the " "original configuration." diff --git a/python/tk-core/python/tank/bootstrap/configuration.py b/python/tk-core/python/tank/bootstrap/configuration.py index cb58e162..8c0debdb 100644 --- a/python/tk-core/python/tank/bootstrap/configuration.py +++ b/python/tk-core/python/tank/bootstrap/configuration.py @@ -11,16 +11,14 @@ import inspect import os -from .import_handler import CoreImportHandler - -from ..log import LogManager -from .. import pipelineconfig_utils -from .. import constants +from .. import constants, pipelineconfig_utils from ..authentication import ( + ShotgunSamlUser, get_shotgun_authenticator_support_web_login, serialize_user, - ShotgunSamlUser, ) +from ..log import LogManager +from .import_handler import CoreImportHandler log = LogManager.get_logger(__name__) @@ -178,8 +176,7 @@ def _tank_from_path(self, path): """ # Perform a local import here to make sure we are getting # the newly swapped in core code, if it was swapped - from .. import api - from .. import pipelineconfig + from .. import api, pipelineconfig log.debug("Executing tank_from_path('%s')" % path) @@ -267,6 +264,7 @@ def _set_authenticated_user( ShotgunAuthenticator, deserialize_user, ) + from ..util import CoreDefaultsManager except ImportError: log.debug("Using pre-0.16 core, no authenticated user will be set.") diff --git a/python/tk-core/python/tank/bootstrap/configuration_writer.py b/python/tk-core/python/tank/bootstrap/configuration_writer.py index 3e95a306..17f33aa4 100644 --- a/python/tk-core/python/tank/bootstrap/configuration_writer.py +++ b/python/tk-core/python/tank/bootstrap/configuration_writer.py @@ -8,23 +8,17 @@ # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights # not expressly granted therein are reserved by Shotgun Software Inc. +import datetime import os import sys -import datetime - -from . import constants - -from ..descriptor import Descriptor, create_descriptor, is_descriptor_version_missing - -from ..util import filesystem -from ..util import StorageRoots -from ..util.shotgun import connection -from ..util.move_guard import MoveGuard -from ..util import is_macos, is_windows from tank_vendor import yaml from .. import LogManager +from ..util import StorageRoots, filesystem, is_macos, is_windows +from ..util.move_guard import MoveGuard +from ..util.shotgun import connection +from . import constants log = LogManager.get_logger(__name__) @@ -265,7 +259,9 @@ def create_tank_command(self, executable=sys.executable, prefix=sys.prefix): if current_interpreter: log.debug("Current OS interpreter will be %s.", current_interpreter) else: - log.debug("Current OS interpreter will be the default PTR desktop app location.") + log.debug( + "Current OS interpreter will be the default PTR desktop app location." + ) config_root_path = self._path.current_os @@ -339,7 +335,9 @@ def write_config_info_file(self, config_descriptor): with filesystem.auto_created_yml(config_info_file) as fh: fh.write("# This file contains metadata describing what exact version\n") - fh.write("# Of the config that was downloaded from Flow Production Tracking\n") + fh.write( + "# Of the config that was downloaded from Flow Production Tracking\n" + ) fh.write("\n") fh.write("# Below follows details for the sg attachment that is\n") fh.write("# reflected within this local configuration.\n") diff --git a/python/tk-core/python/tank/bootstrap/import_handler.py b/python/tk-core/python/tank/bootstrap/import_handler.py index 715d61e1..2e16ad25 100644 --- a/python/tk-core/python/tank/bootstrap/import_handler.py +++ b/python/tk-core/python/tank/bootstrap/import_handler.py @@ -102,7 +102,7 @@ def swap_core(cls, core_path): try: if prev_log_file: tank.LogManager().initialize_base_file_handler_from_path(prev_log_file) - except AttributeError as e: + except AttributeError: # older versions of the API may not have this defined. log.debug( "Switching to a version of the core API that doesn't " @@ -289,20 +289,14 @@ def find_spec(self, module_fullname, package_path=None, target=None): # later raise FileNotFoundError instead of the expected ImportError # when the module doesn't exist on disk. if os.path.isdir(os.path.join(package_path[0], module_name)): - module_file = os.path.join( - package_path[0], module_name, "__init__.py" - ) + module_file = os.path.join(package_path[0], module_name, "__init__.py") else: - module_file = os.path.join( - package_path[0], module_name + ".py" - ) + module_file = os.path.join(package_path[0], module_name + ".py") if not os.path.isfile(module_file): return None - loader = importlib.machinery.SourceFileLoader( - module_fullname, module_file - ) + loader = importlib.machinery.SourceFileLoader(module_fullname, module_file) spec = importlib.util.spec_from_loader(loader.name, loader) self._module_info[module_fullname] = spec except ImportError: @@ -328,9 +322,7 @@ def load_module(self, module_fullname): try: spec.loader.exec_module(module) except FileNotFoundError as e: - raise ImportError( - f"No module named '{module_fullname}'" - ) from e + raise ImportError(f"No module named '{module_fullname}'") from e # the module needs to know the loader so that reload() works module.__loader__ = self diff --git a/python/tk-core/python/tank/bootstrap/installed_configuration.py b/python/tk-core/python/tank/bootstrap/installed_configuration.py index fa5f5334..4de4ffd7 100644 --- a/python/tk-core/python/tank/bootstrap/installed_configuration.py +++ b/python/tk-core/python/tank/bootstrap/installed_configuration.py @@ -10,9 +10,9 @@ import os -from .errors import TankBootstrapError -from .configuration import Configuration from .. import LogManager +from .configuration import Configuration +from .errors import TankBootstrapError log = LogManager.get_logger(__name__) diff --git a/python/tk-core/python/tank/bootstrap/manager.py b/python/tk-core/python/tank/bootstrap/manager.py index b058b6e8..7efc505d 100644 --- a/python/tk-core/python/tank/bootstrap/manager.py +++ b/python/tk-core/python/tank/bootstrap/manager.py @@ -8,18 +8,20 @@ # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights # not expressly granted therein are reserved by Shotgun Software Inc. -import os import inspect +import os -from . import constants -from .errors import TankBootstrapError -from .configuration import Configuration -from .resolver import ConfigurationResolver -from ..authentication import ShotgunAuthenticator -from ..pipelineconfig import PipelineConfiguration from .. import LogManager +from ..authentication import ShotgunAuthenticator, flow_auth from ..errors import TankError +from ..flowam import constants as flow_const +from ..flowam import utils as flow_utils +from ..pipelineconfig import PipelineConfiguration from ..util import ShotgunPath +from . import constants +from .configuration import Configuration +from .errors import TankBootstrapError +from .resolver import ConfigurationResolver log = LogManager.get_logger(__name__) @@ -33,12 +35,12 @@ class ToolkitManager(object): # Constants used to make the manager bootstrapping: # - download and cache the config dependencies needed to run the engine being started in a specific environment. # - download and cache all the config dependencies needed to run the engine in any environment. - (CACHE_SPARSE, CACHE_FULL) = range(2) + CACHE_SPARSE, CACHE_FULL = range(2) # Constants used to indicate that the manager is: # - bootstrapping the toolkit (with method bootstrap_toolkit), # - starting up the engine (with method _start_engine). - (TOOLKIT_BOOTSTRAP_PHASE, ENGINE_STARTUP_PHASE) = range(2) + TOOLKIT_BOOTSTRAP_PHASE, ENGINE_STARTUP_PHASE = range(2) # List of constants representing the status of the progress bar when these event occurs during bootstrap. _RESOLVING_PROJECT_RATE = 0.0 @@ -84,6 +86,10 @@ def __init__(self, sg_user=None): self._plugin_id = None self._allow_config_overrides = True + # flow fields + self._flow_project_id = None + self._flow_schema_version = None + # look for the standard env var SHOTGUN_PIPELINE_CONFIGURATION_ID # and in case this is set, use it as a default if constants.PIPELINE_CONFIG_ID_ENV_VAR in os.environ: @@ -914,6 +920,84 @@ def _log_startup_message(self, engine_name, entity): log.debug("Bootstrapping engine %s." % engine_name) log.debug("-----------------------------------------------------------------") + def _resolve_project_id(self, entity): + """ + Resolve a Shotgun project id from a target entity. + + :param entity: ``None``, a Project dict, an entity with a ``project`` + link, or any entity that can be looked up in Shotgun + to find its parent project. + :type entity: dict or None + :returns: Integer project id, or ``None`` for the site context. + :rtype: int or None + """ + if entity is None: + return None + + if entity.get("type") == "Project": + return entity["id"] + + if "project" in entity and entity["project"].get("type") == "Project": + return entity["project"]["id"] + + data = self._sg_connection.find_one( + entity["type"], [["id", "is", entity["id"]]], ["project"] + ) + if not data or not data.get("project"): + raise TankBootstrapError("Cannot resolve project for %s" % entity) + return data["project"]["id"] + + def _trigger_am_auth(self, pipeline_config, entity, progress_callback): + """ + Proactively obtain a Flow/MEDM access token. + Silent path (file store -> refresh) is tried first; falls + back to opening a browser for PKCE if no usable cached/refresh token + exists. + + Configuration errors raise ``TankBootstrapError`` (deployment bug). + Runtime auth failures are logged and swallowed unless the + ``TK_FLOW_AUTH_REQUIRED`` env var is set to ``"1"``, in which case + they raise ``TankBootstrapError``. + + :param pipeline_config: PipelineConfiguration object. + :param entity: Shotgun entity used to resolve a project context. + :type entity: dict or None + :param progress_callback: Callback function that reports back on the toolkit bootstrap progress. + Set to ``None`` to use the default callback function. + :rtype: None + """ + if entity is None: + return + + try: + log.info("Triggering Flow authentication...") + overrides = flow_utils.get_config_flow_settings(pipeline_config) + # Set authentication overrides in reserved env vars so they will persist + # across toolkit engine sessions + if flow_const.FLOW_AUTH_APP_ID in overrides: + os.environ["TK_FLOW_AUTH_APPLICATION_ID"] = overrides[ + flow_const.FLOW_AUTH_APP_ID + ] + if flow_const.FLOW_AUTH_BASE_URL in overrides: + os.environ["TK_FLOW_AUTH_BASE_URL"] = overrides[ + flow_const.FLOW_AUTH_BASE_URL + ] + if flow_const.FLOW_AUTH_CALLBACK_URL in overrides: + os.environ["TK_FLOW_AUTH_CALLBACK_URL"] = overrides[ + flow_const.FLOW_AUTH_CALLBACK_URL + ] + settings = flow_auth.resolve_flow_auth_settings() + flow_auth.init_authentication(settings) + # Token is intentionally discarded here; it now sits in the file + # store and adsk_auth's in-memory cache for the next consumer. + flow_auth.get_access_token() + except flow_auth.FlowAuthConfigurationError as e: + raise TankBootstrapError( + "MEDM auth misconfigured for AM-ready project: %s" % e + ) + except Exception as e: + raise TankBootstrapError("MEDM auth failed for AM-ready project: %s" % e) + def _get_configuration(self, entity, progress_callback): """ Resolves the configuration to use without creating it on disk. @@ -928,25 +1012,7 @@ def _get_configuration(self, entity, progress_callback): self._report_progress( progress_callback, self._RESOLVING_PROJECT_RATE, "Resolving project..." ) - if entity is None: - project_id = None - - elif entity.get("type") == "Project": - project_id = entity["id"] - - elif "project" in entity and entity["project"].get("type") == "Project": - # user passed a project link - project_id = entity["project"]["id"] - - else: - # resolve from shotgun - data = self._sg_connection.find_one( - entity["type"], [["id", "is", entity["id"]]], ["project"] - ) - - if not data or not data.get("project"): - raise TankBootstrapError("Cannot resolve project for %s" % entity) - project_id = data["project"]["id"] + project_id = self._resolve_project_id(entity) # get an object to represent the business logic for # how a configuration location is being determined @@ -997,7 +1063,9 @@ def _get_configuration(self, entity, progress_callback): elif self._do_shotgun_config_lookup: # do the full resolve where we connect to shotgun etc. - log.debug("Checking for pipeline configuration overrides in Flow Production Tracking.") + log.debug( + "Checking for pipeline configuration overrides in Flow Production Tracking." + ) log.debug( "In order to turn this off, set do_shotgun_config_lookup to False" ) @@ -1063,6 +1131,36 @@ def _get_updated_configuration(self, entity, progress_callback): else: raise TankBootstrapError("Unknown configuration update status!") + if entity is not None: + project_id = self._resolve_project_id(entity) + if project_id: + sg_project = self._sg_connection.find_one( + "Project", + [["id", "is", project_id]], + [ + flow_auth.AM_READY_PROJECT_FIELD, + flow_const.FLOW_SCHEMA_VERSION_FIELD, + ], + ) + if sg_project and sg_project.get(flow_auth.AM_READY_PROJECT_FIELD): + # Store flow fields temporarily to avoid re-querying. + # They will be cached on the context object after the context object has been rebuilt. + self._flow_project_id = sg_project.get( + flow_auth.AM_READY_PROJECT_FIELD + ) + self._flow_schema_version = sg_project.get( + flow_const.FLOW_SCHEMA_VERSION_FIELD + ) + log.info( + f"Current SG project is associated with a Flow project: {self._flow_project_id} with schema version {self._flow_schema_version}." + ) + + tk, _ = config.get_tk_instance(self._sg_user) + # Authenticate into Flow AM + self._trigger_am_auth( + tk.pipeline_configuration, entity, progress_callback + ) + return config def _bootstrap_sgtk(self, engine_name, entity, progress_callback=None): @@ -1114,6 +1212,11 @@ def _start_engine(self, tk, engine_name, entity, progress_callback=None): If entity is None, the method will bootstrap into the site config. + For Flow-enabled projects, the Flow project id and schema version + cached during ``_get_updated_configuration()`` are injected onto + ``ctx.project`` so the engine can read them via + ``context.flow_project_id`` and ``context.flow_schema_version``. + Please note that the API version of the tk instance that hosts the engine may not be the same as the API version that was executed during the bootstrap. @@ -1140,6 +1243,13 @@ def _start_engine(self, tk, engine_name, entity, progress_callback=None): else: ctx = tk.context_from_entity_dictionary(entity) + # Inject flow fields to context if current project is related to a Flow project. + if ctx.project and self._flow_project_id is not None: + ctx.project[flow_auth.AM_READY_PROJECT_FIELD] = self._flow_project_id + ctx.project[flow_const.FLOW_SCHEMA_VERSION_FIELD] = ( + self._flow_schema_version + ) + self._report_progress( progress_callback, self._LAUNCHING_ENGINE_RATE, "Launching Engine..." ) diff --git a/python/tk-core/python/tank/bootstrap/resolver.py b/python/tk-core/python/tank/bootstrap/resolver.py index d7f19204..e4990373 100644 --- a/python/tk-core/python/tank/bootstrap/resolver.py +++ b/python/tk-core/python/tank/bootstrap/resolver.py @@ -13,27 +13,25 @@ on disk. """ -import sys -import os import fnmatch +import os import pprint +import sys +from .. import LogManager from ..descriptor import ( Descriptor, create_descriptor, descriptor_uri_to_dict, is_descriptor_version_missing, ) -from .errors import TankBootstrapError, TankBootstrapInvalidPipelineConfigurationError +from ..descriptor.descriptor_installed_config import InstalledConfigDescriptor +from ..util import LocalFileStorageManager, ShotgunPath, filesystem +from . import constants from .baked_configuration import BakedConfiguration from .cached_configuration import CachedConfiguration +from .errors import TankBootstrapError, TankBootstrapInvalidPipelineConfigurationError from .installed_configuration import InstalledConfiguration -from ..descriptor.descriptor_installed_config import InstalledConfigDescriptor -from ..util import filesystem -from ..util import ShotgunPath -from ..util import LocalFileStorageManager -from .. import LogManager -from . import constants log = LogManager.get_logger(__name__) @@ -376,9 +374,9 @@ def _get_pipeline_configurations_for_project( # field. Note that this may be None if for example the pipeline configuration # is defined for another operating system. try: - pipeline_config[ - "config_descriptor" - ] = self._create_config_descriptor(sg_connection, pipeline_config) + pipeline_config["config_descriptor"] = ( + self._create_config_descriptor(sg_connection, pipeline_config) + ) yield pipeline_config except TankBootstrapInvalidPipelineConfigurationError as e: @@ -841,7 +839,9 @@ def resolve_shotgun_configuration( "Will use pipeline configuration id '%s'" % pipeline_config_identifier ) - log.debug("Requesting pipeline configuration data from Flow Production Tracking...") + log.debug( + "Requesting pipeline configuration data from Flow Production Tracking..." + ) # Fetch the one and only config that matches this id. pipeline_config = sg_connection.find_one( @@ -869,7 +869,9 @@ def resolve_shotgun_configuration( # We couldn't resolve anything from Shotgun, so we'll resolve the configuration using # an offline resolve. - return self.resolve_not_found_sg_configuration(fallback_config_descriptor, sg_connection) + return self.resolve_not_found_sg_configuration( + fallback_config_descriptor, sg_connection + ) else: # Something was found in Shotgun, which means we've also potentially resolved its diff --git a/python/tk-core/python/tank/commands/__init__.py b/python/tk-core/python/tank/commands/__init__.py index 3df351e8..e63da80e 100644 --- a/python/tk-core/python/tank/commands/__init__.py +++ b/python/tk-core/python/tank/commands/__init__.py @@ -8,5 +8,5 @@ # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights # not expressly granted therein are reserved by Shotgun Software Inc. -from .tank_command import list_commands, get_command, SgtkSystemCommand from .interaction import CommandInteraction +from .tank_command import SgtkSystemCommand, get_command, list_commands diff --git a/python/tk-core/python/tank/commands/app_info.py b/python/tk-core/python/tank/commands/app_info.py index 03de1802..2f1c214d 100644 --- a/python/tk-core/python/tank/commands/app_info.py +++ b/python/tk-core/python/tank/commands/app_info.py @@ -8,8 +8,8 @@ # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights # not expressly granted therein are reserved by Shotgun Software Inc. -from .action_base import Action from ..errors import TankError +from .action_base import Action class AppInfoAction(Action): @@ -108,7 +108,7 @@ def _env_breakdown(self, log, env_name): descriptor = env.get_app_descriptor(eng, app) log.info("App %s" % app) log.info("-" * (4 + len(app))) - for (k, v) in descriptor.get_dict().items(): + for k, v in descriptor.get_dict().items(): log.info(" %s: %s" % (k.capitalize(), v)) log.info(" Docs: %s" % descriptor.documentation_url) log.info("") diff --git a/python/tk-core/python/tank/commands/cache_apps.py b/python/tk-core/python/tank/commands/cache_apps.py index d6dc0d04..30ecaf35 100644 --- a/python/tk-core/python/tank/commands/cache_apps.py +++ b/python/tk-core/python/tank/commands/cache_apps.py @@ -8,8 +8,8 @@ # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights # not expressly granted therein are reserved by Shotgun Software Inc. -from .action_base import Action from ..errors import TankError +from .action_base import Action class CacheAppsAction(Action): diff --git a/python/tk-core/python/tank/commands/cache_yaml.py b/python/tk-core/python/tank/commands/cache_yaml.py index b299f4c0..3628aab4 100644 --- a/python/tk-core/python/tank/commands/cache_yaml.py +++ b/python/tk-core/python/tank/commands/cache_yaml.py @@ -8,13 +8,12 @@ # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights # not expressly granted therein are reserved by Shotgun Software Inc. -import os import fnmatch +import os - -from .action_base import Action from ..errors import TankError -from ..util import yaml_cache, pickle +from ..util import pickle, yaml_cache +from .action_base import Action class CacheYamlAction(Action): diff --git a/python/tk-core/python/tank/commands/clone_configuration.py b/python/tk-core/python/tank/commands/clone_configuration.py index bed03b0d..f1115dc1 100644 --- a/python/tk-core/python/tank/commands/clone_configuration.py +++ b/python/tk-core/python/tank/commands/clone_configuration.py @@ -8,19 +8,16 @@ # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights # not expressly granted therein are reserved by Shotgun Software Inc. -from ..util import ShotgunPath -from ..errors import TankError -from . import constants -from ..util import filesystem -from ..util import is_linux, is_macos, is_windows +import os +import shutil from tank_vendor import yaml +from ..errors import TankError +from ..util import ShotgunPath, filesystem, is_linux, is_macos, is_windows +from . import constants from .action_base import Action -import os -import shutil - class CloneConfigAction(Action): """ diff --git a/python/tk-core/python/tank/commands/console_utils.py b/python/tk-core/python/tank/commands/console_utils.py index b8838566..5d4f21ea 100644 --- a/python/tk-core/python/tank/commands/console_utils.py +++ b/python/tk-core/python/tank/commands/console_utils.py @@ -15,12 +15,11 @@ import textwrap from .. import pipelineconfig_utils -from ..platform import validation -from ..errors import TankError, TankNoDefaultValueError from ..descriptor import CheckVersionConstraintsError +from ..errors import TankError, TankNoDefaultValueError +from ..platform import validation from ..platform.bundle import resolve_default_value - ########################################################################################## # displaying of info in the terminal, ascii-graphcics style @@ -34,7 +33,7 @@ def format_bundle_info(log, descriptor, required_updates=None): :param required_updates: A list of bundle names that require updating. """ # yay we can install! - get release notes - (summary, url) = descriptor.changelog + summary, url = descriptor.changelog if required_updates: add_padding = " " diff --git a/python/tk-core/python/tank/commands/core_localize.py b/python/tk-core/python/tank/commands/core_localize.py index f29f1385..0eb6b1bd 100644 --- a/python/tk-core/python/tank/commands/core_localize.py +++ b/python/tk-core/python/tank/commands/core_localize.py @@ -8,17 +8,16 @@ # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights # not expressly granted therein are reserved by Shotgun Software Inc. +import datetime import os -import sys import shutil -import datetime +import sys +from .. import pipelineconfig_factory, pipelineconfig_utils from ..errors import TankError from ..util import filesystem from ..util.version import is_version_older from .action_base import Action -from .. import pipelineconfig_utils -from .. import pipelineconfig_factory # these are the items that need to be copied across # when a configuration is upgraded to contain a core API diff --git a/python/tk-core/python/tank/commands/core_upgrade.py b/python/tk-core/python/tank/commands/core_upgrade.py index 183044c9..0516f2b7 100644 --- a/python/tk-core/python/tank/commands/core_upgrade.py +++ b/python/tk-core/python/tank/commands/core_upgrade.py @@ -11,22 +11,21 @@ """ Tank command allowing to do core updates. """ -from ..errors import TankError -from .action_base import Action +import copy +import optparse import os import sys import textwrap -import optparse -import copy - -from ..util import shotgun -from .. import pipelineconfig_utils -from . import console_utils -from ..util.version import is_version_newer, is_version_head from tank_vendor import yaml +from .. import pipelineconfig_utils +from ..errors import TankError +from ..util import shotgun +from ..util.version import is_version_head, is_version_newer +from .action_base import Action + # FIXME: This should be refactored into something that can be used by other commands. class TkOptParse(optparse.OptionParser): @@ -197,7 +196,7 @@ def _run(self, log, core_version): elif status == TankCoreUpdater.UPDATE_POSSIBLE: - (summary, url) = installer.get_release_notes() + summary, url = installer.get_release_notes() log.info("") log.info("Newer version %s is available." % new_version) diff --git a/python/tk-core/python/tank/commands/desktop_migration.py b/python/tk-core/python/tank/commands/desktop_migration.py index 90f671ed..92ffc3fb 100644 --- a/python/tk-core/python/tank/commands/desktop_migration.py +++ b/python/tk-core/python/tank/commands/desktop_migration.py @@ -8,9 +8,8 @@ # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights # not expressly granted therein are reserved by Shotgun Software Inc. -from .action_base import Action -from . import console_utils from . import constants +from .action_base import Action _MESSAGE = ( "This command will migrate the PTR site configuration used by the Desktop app so " diff --git a/python/tk-core/python/tank/commands/dump_config.py b/python/tk-core/python/tank/commands/dump_config.py index d0f68b21..12c7f2ab 100644 --- a/python/tk-core/python/tank/commands/dump_config.py +++ b/python/tk-core/python/tank/commands/dump_config.py @@ -12,8 +12,8 @@ from io import StringIO from ..errors import TankError -from .action_base import Action from ..util import filesystem +from .action_base import Action class DumpConfigAction(Action): diff --git a/python/tk-core/python/tank/commands/get_entity_commands.py b/python/tk-core/python/tank/commands/get_entity_commands.py index f22424a4..a18d41dc 100644 --- a/python/tk-core/python/tank/commands/get_entity_commands.py +++ b/python/tk-core/python/tank/commands/get_entity_commands.py @@ -7,16 +7,15 @@ # By accessing, using, copying or modifying this work you indicate your # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights # not expressly granted therein are reserved by Shotgun Software Inc. +import itertools +import operator +import os import sys -from .action_base import Action from ..errors import TankError -from ..util.process import SubprocessCalledProcessError, subprocess_check_output from ..util import is_linux, is_macos, is_windows - -import itertools -import operator -import os +from ..util.process import SubprocessCalledProcessError, subprocess_check_output +from .action_base import Action def execute_tank_command(pipeline_config_path, args): @@ -150,7 +149,7 @@ def run_noninteractive(self, log, parameters): per_entity_type = itertools.groupby(entities, operator.itemgetter(0)) commands_per_entity = {} - for (entity_type, entities_of_type) in per_entity_type: + for entity_type, entities_of_type in per_entity_type: # make a list out of the grouped entity tuples entities_of_type = list(entities_of_type) try: @@ -311,7 +310,7 @@ def _parse_cached_commands(self, commands_data): tokens += [""] * (NUM_EXPECTED_TOKENS - len(tokens)) # extract the information from the tokens - (name, title, _, _, icon, description) = tuple(tokens) + name, title, _, _, icon, description = tuple(tokens) commands.append( {"name": name, "title": title, "icon": icon, "description": description} diff --git a/python/tk-core/python/tank/commands/install.py b/python/tk-core/python/tank/commands/install.py index 0d1fd957..baf7dfda 100644 --- a/python/tk-core/python/tank/commands/install.py +++ b/python/tk-core/python/tank/commands/install.py @@ -9,10 +9,8 @@ # not expressly granted therein are reserved by Shotgun Software Inc. from ..errors import TankError -from . import console_utils +from . import console_utils, constants, util from .action_base import Action -from . import util -from . import constants class InstallAppAction(Action): @@ -94,7 +92,7 @@ def run_interactive(self, log, args): :param log: std python logger :param args: command line args """ - (use_legacy_parser, args) = util.should_use_legacy_yaml_parser(args) + use_legacy_parser, args = util.should_use_legacy_yaml_parser(args) preserve_yaml = not use_legacy_parser if len(args) != 3: @@ -411,7 +409,7 @@ def run_interactive(self, log, args): :param log: std python logger :param args: command line args """ - (use_legacy_parser, args) = util.should_use_legacy_yaml_parser(args) + use_legacy_parser, args = util.should_use_legacy_yaml_parser(args) preserve_yaml = not use_legacy_parser if len(args) != 2: @@ -539,8 +537,8 @@ def _run(self, log, env_name, engine_name, preserve_yaml): # run descriptor factory method log.info("Connecting to git...") location = {"type": "git", "path": engine_name} - engine_descriptor = self.tk.pipeline_configuration.get_latest_engine_descriptor( - location + engine_descriptor = ( + self.tk.pipeline_configuration.get_latest_engine_descriptor(location) ) log.info( "Latest tag in repository '%s' is %s." @@ -559,8 +557,8 @@ def _run(self, log, env_name, engine_name, preserve_yaml): # this is an app store app! log.info("Connecting to the Toolkit App Store...") location = {"type": "app_store", "name": engine_name} - engine_descriptor = self.tk.pipeline_configuration.get_latest_engine_descriptor( - location + engine_descriptor = ( + self.tk.pipeline_configuration.get_latest_engine_descriptor(location) ) log.info( "Latest approved App Store Version is %s." % engine_descriptor.version diff --git a/python/tk-core/python/tank/commands/interaction.py b/python/tk-core/python/tank/commands/interaction.py index 0fed12b3..5229db4d 100644 --- a/python/tk-core/python/tank/commands/interaction.py +++ b/python/tk-core/python/tank/commands/interaction.py @@ -14,7 +14,6 @@ from .. import LogManager - log = LogManager.get_logger(__name__) diff --git a/python/tk-core/python/tank/commands/misc.py b/python/tk-core/python/tank/commands/misc.py index f4d1b911..5694b686 100644 --- a/python/tk-core/python/tank/commands/misc.py +++ b/python/tk-core/python/tank/commands/misc.py @@ -8,12 +8,12 @@ # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights # not expressly granted therein are reserved by Shotgun Software Inc. -from ..errors import TankError -from .action_base import Action - import code -import sys import os +import sys + +from ..errors import TankError +from .action_base import Action class ClearCacheAction(Action): @@ -70,7 +70,7 @@ def _run(self, log): log.debug("Deleting cache file %s..." % full_path) try: os.remove(full_path) - except: + except Exception: log.warning("Could not delete cache file '%s'!" % full_path) log.info("The PTR menu cache has been cleared.") @@ -128,8 +128,8 @@ def run_interactive(self, log, args): # attempt install tab command completion try: - import rlcompleter import readline + import rlcompleter # noqa: F401 if "libedit" in readline.__doc__: # macosx, some versions - see @@ -137,7 +137,7 @@ def run_interactive(self, log, args): readline.parse_and_bind("bind ^I rl_complete") else: readline.parse_and_bind("tab: complete") - except: + except Exception: pass code.interact(banner="\n".join(msg), local=tk_locals) diff --git a/python/tk-core/python/tank/commands/move_pc.py b/python/tk-core/python/tank/commands/move_pc.py index 24017746..5aac6c9a 100644 --- a/python/tk-core/python/tank/commands/move_pc.py +++ b/python/tk-core/python/tank/commands/move_pc.py @@ -8,15 +8,14 @@ # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights # not expressly granted therein are reserved by Shotgun Software Inc. -from . import constants -from ..errors import TankError - -from .action_base import Action - import os import shutil import sys +from ..errors import TankError +from . import constants +from .action_base import Action + class MovePCAction(Action): """ diff --git a/python/tk-core/python/tank/commands/path_cache.py b/python/tk-core/python/tank/commands/path_cache.py index bd7554a7..11218f7a 100644 --- a/python/tk-core/python/tank/commands/path_cache.py +++ b/python/tk-core/python/tank/commands/path_cache.py @@ -12,10 +12,8 @@ Methods relating to the path cache """ +from .. import folder, path_cache from ..errors import TankError -from .. import path_cache -from .. import folder - from .action_base import Action @@ -176,7 +174,9 @@ def run_interactive(self, log, args): # first load up the current path cache file and make sure # shotgun has got all those entries present as FilesystemLocations. log.info("") - log.info("Phase 1/3: Pushing data from the current path cache to Flow Production Tracking...") + log.info( + "Phase 1/3: Pushing data from the current path cache to Flow Production Tracking..." + ) curr_pc = path_cache.PathCache(self.tk) try: curr_pc.ensure_all_entries_are_in_shotgun() @@ -191,7 +191,9 @@ def run_interactive(self, log, args): # and synchronize path cache log.info("") - log.info("Phase 3/3: Synchronizing your local machine with Flow Production Tracking...") + log.info( + "Phase 3/3: Synchronizing your local machine with Flow Production Tracking..." + ) pc = path_cache.PathCache(self.tk) try: pc.synchronize(full_sync=True) diff --git a/python/tk-core/python/tank/commands/pc_overview.py b/python/tk-core/python/tank/commands/pc_overview.py index 76b1b48e..d3568441 100644 --- a/python/tk-core/python/tank/commands/pc_overview.py +++ b/python/tk-core/python/tank/commands/pc_overview.py @@ -9,14 +9,14 @@ # not expressly granted therein are reserved by Shotgun Software Inc. +import os + from .. import pipelineconfig_utils +from ..errors import TankError from ..util import ShotgunPath from . import constants -from ..errors import TankError from .action_base import Action -import os - class PCBreakdownAction(Action): """ diff --git a/python/tk-core/python/tank/commands/push_pc.py b/python/tk-core/python/tank/commands/push_pc.py index aa5f6e47..faa27e7d 100644 --- a/python/tk-core/python/tank/commands/push_pc.py +++ b/python/tk-core/python/tank/commands/push_pc.py @@ -8,21 +8,16 @@ # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights # not expressly granted therein are reserved by Shotgun Software Inc. -from ..util import filesystem -from . import constants +import datetime +import os +import shutil + from ..errors import TankError from ..pipelineconfig import PipelineConfiguration - -from . import console_utils - +from ..util import ShotgunPath, filesystem +from . import constants from .action_base import Action -from ..util import ShotgunPath - -import os -import datetime -import shutil - # Core configuration files which are associated with the core API installation and not # the pipeline configuration. CORE_API_FILES = [ @@ -136,7 +131,7 @@ def run_interactive(self, log, args): raise TankError("Aborted by user.") try: target_pc_id = int(answer) - except: + except Exception: raise TankError("Please enter a number!") self._run( diff --git a/python/tk-core/python/tank/commands/setup_project.py b/python/tk-core/python/tank/commands/setup_project.py index 4ed10d33..44b9184f 100644 --- a/python/tk-core/python/tank/commands/setup_project.py +++ b/python/tk-core/python/tank/commands/setup_project.py @@ -13,19 +13,15 @@ import textwrap import traceback -from .action_base import Action -from . import core_localize -from ..errors import TankError -from ..util import shotgun -from ..util import ShotgunPath -from ..util import is_linux, is_macos, is_windows -from . import constants from .. import pipelineconfig_utils +from ..errors import TankError +from ..util import ShotgunPath, is_linux, is_macos, is_windows, shotgun from ..util.filesystem import ensure_folder_exists - +from . import constants, core_localize +from .action_base import Action +from .interaction import YesToEverythingInteraction from .setup_project_core import run_project_setup from .setup_project_params import ProjectSetupParameters -from .interaction import YesToEverythingInteraction class SetupProjectAction(Action): @@ -483,7 +479,7 @@ def _select_project(self, log, sg, show_initialized_projects): ["archived", "is_not", True], ] - if show_initialized_projects == False: + if show_initialized_projects is False: # not force mode. Only show non-set up projects filters.append(["tank_name", "is", None]) @@ -545,7 +541,7 @@ def _select_project(self, log, sg, show_initialized_projects): raise TankError("Aborted by user.") try: project_id = int(answer) - except: + except Exception: raise TankError("Please enter a number!") if project_id not in [x["id"] for x in projs]: @@ -687,9 +683,7 @@ def _get_disk_location(self, log, params): default_config_locations = self._get_default_configuration_location(log, params) - linux_path = self._ask_location( - log, default_config_locations["linux"], "Linux" - ) + linux_path = self._ask_location(log, default_config_locations["linux"], "Linux") windows_path = self._ask_location( log, default_config_locations["win32"], "Windows" ) @@ -726,7 +720,6 @@ def _get_default_configuration_location(self, log, params): # location for the installed config. # Multi-root configurations require a storage named "primary" so we base # our default on that. If only a single storage is available, we just use it. - storage_names = params.get_required_storages() default_storage_name = params.default_storage_name # There is no default storage name for a config that doesn't use roots, like @@ -950,7 +943,7 @@ def _map_storages(self, params, config_uri, log, sg): mapped_roots = [] # loop over required storage roots - for (root_name, root_info) in required_roots.items(): + for root_name, root_info in required_roots.items(): log.info("%s" % (root_name,)) log.info("-" * len(root_name)) @@ -1057,7 +1050,7 @@ def _map_storages(self, params, config_uri, log, sg): # ---- now we've mapped the roots, and they're all valid, we need to # update the root information on the core wizard - for (root_name, storage_name) in mapped_roots: + for root_name, storage_name in mapped_roots: root_info = required_roots[root_name] storage_data = storage_by_name[storage_name.lower()] diff --git a/python/tk-core/python/tank/commands/setup_project_core.py b/python/tk-core/python/tank/commands/setup_project_core.py index 38b54395..6f315c19 100644 --- a/python/tk-core/python/tank/commands/setup_project_core.py +++ b/python/tk-core/python/tank/commands/setup_project_core.py @@ -8,18 +8,17 @@ # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights # not expressly granted therein are reserved by Shotgun Software Inc. -import sys import os import shutil - -from . import constants -from ..errors import TankError -from ..util import StorageRoots -from ..util import filesystem -from ..api import sgtk_from_path +import sys from tank_vendor import yaml +from ..api import sgtk_from_path +from ..errors import TankError +from ..util import StorageRoots, filesystem +from . import constants + @filesystem.with_cleared_umask def run_project_setup(log, sg, setup_params): diff --git a/python/tk-core/python/tank/commands/setup_project_params.py b/python/tk-core/python/tank/commands/setup_project_params.py index bd9bed67..3f7cc2aa 100644 --- a/python/tk-core/python/tank/commands/setup_project_params.py +++ b/python/tk-core/python/tank/commands/setup_project_params.py @@ -9,29 +9,21 @@ # not expressly granted therein are reserved by Shotgun Software Inc. import os +import sys import tempfile import uuid -import sys -from . import constants -from . import util +from tank_vendor import yaml -from ..util import StorageRoots +from .. import hook, pipelineconfig_utils +from ..descriptor import Descriptor, create_descriptor +from ..errors import TankError, TankErrorProjectIsSetup +from ..util import ShotgunPath, StorageRoots, filesystem from ..util import sgre as re from ..util import shotgun -from ..util import filesystem -from ..util import is_windows from ..util.version import is_version_newer from ..util.zip import unzip_file, zip_file - -from .. import hook -from ..errors import TankError, TankErrorProjectIsSetup -from .. import pipelineconfig_utils -from ..descriptor import create_descriptor, Descriptor - -from tank_vendor import yaml - -from ..util import ShotgunPath +from . import constants, util class ProjectSetupParameters(object): @@ -57,7 +49,7 @@ class ProjectSetupParameters(object): - run project setup! """ - (CENTRALIZED_CONFIG, DISTRIBUTED_CONFIG) = range(2) + CENTRALIZED_CONFIG, DISTRIBUTED_CONFIG = range(2) def __init__(self, log, sg): """ @@ -514,8 +506,8 @@ def set_project_id(self, project_id, force=False): # if force is false then tank_name must be empty if ( - self.get_auto_path_mode() == False - and force == False + self.get_auto_path_mode() is False + and force is False and proj["tank_name"] is not None ): raise TankErrorProjectIsSetup() @@ -1019,7 +1011,7 @@ def __init__(self, config_uri, sg, log): # now extract the cfg and validate old_umask = os.umask(0) try: - (self._cfg_folder, self._version, self._config_mode) = self._process_config( + self._cfg_folder, self._version, self._config_mode = self._process_config( config_uri ) finally: @@ -1272,7 +1264,7 @@ def resolve_storages(self): storage_info = {} # do the storage lookup and mapping in PTR - (local_storage_lookup, unmapped_roots) = self._storage_roots.get_local_storages( + local_storage_lookup, unmapped_roots = self._storage_roots.get_local_storages( self._sg ) diff --git a/python/tk-core/python/tank/commands/setup_project_wizard.py b/python/tk-core/python/tank/commands/setup_project_wizard.py index 45f11947..465275d9 100644 --- a/python/tk-core/python/tank/commands/setup_project_wizard.py +++ b/python/tk-core/python/tank/commands/setup_project_wizard.py @@ -651,9 +651,9 @@ def get_core_settings(self): ) return_data["using_runtime"] = False return_data["pipeline_config"] = data - return_data[ - "core_path" - ] = pipelineconfig_utils.resolve_all_os_paths_to_core(core_api_root) + return_data["core_path"] = ( + pipelineconfig_utils.resolve_all_os_paths_to_core(core_api_root) + ) # finally, check the logic for localization: # if this core that we have found and resolved is localized, diff --git a/python/tk-core/python/tank/commands/switch.py b/python/tk-core/python/tank/commands/switch.py index b1cbfd3f..d2027995 100644 --- a/python/tk-core/python/tank/commands/switch.py +++ b/python/tk-core/python/tank/commands/switch.py @@ -8,14 +8,12 @@ # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights # not expressly granted therein are reserved by Shotgun Software Inc. +import os + from ..errors import TankError -from . import constants -from . import util -from . import console_utils +from . import console_utils, constants, util from .action_base import Action -import os - class SwitchAppAction(Action): """ @@ -116,7 +114,7 @@ def run_interactive(self, log, args): log.info("") return - (use_legacy_parser, args) = util.should_use_legacy_yaml_parser(args) + use_legacy_parser, args = util.should_use_legacy_yaml_parser(args) preserve_yaml = not use_legacy_parser # get parameters @@ -203,13 +201,13 @@ def run_interactive(self, log, args): log.info("") log.info("Current version") log.info("------------------------------------") - for (k, v) in descriptor.get_dict().items(): + for k, v in descriptor.get_dict().items(): log.info(" - %s: %s" % (k.capitalize(), v)) log.info("") log.info("New version") log.info("------------------------------------") - for (k, v) in new_descriptor.get_dict().items(): + for k, v in new_descriptor.get_dict().items(): log.info(" - %s: %s" % (k.capitalize(), v)) log.info("") @@ -229,9 +227,7 @@ def run_interactive(self, log, args): # ensure that all required frameworks have been installed # find the file where our item is being installed - (_, yml_file) = env.find_location_for_app( - engine_instance_name, app_instance_name - ) + _, yml_file = env.find_location_for_app(engine_instance_name, app_instance_name) console_utils.ensure_frameworks_installed( log, self.tk, yml_file, new_descriptor, env, self._interaction_interface diff --git a/python/tk-core/python/tank/commands/tank_command.py b/python/tk-core/python/tank/commands/tank_command.py index 8c5171e1..d803b20c 100644 --- a/python/tk-core/python/tank/commands/tank_command.py +++ b/python/tk-core/python/tank/commands/tank_command.py @@ -15,38 +15,38 @@ import logging -from .action_base import Action -from .interaction import RawInputCommandInteraction, YesToEverythingInteraction -from . import folders -from . import misc -from . import move_pc -from . import pc_overview -from . import path_cache -from . import update -from . import push_pc -from . import setup_project -from . import setup_project_wizard -from . import dump_config -from . import validate_config -from . import cache_apps -from . import switch -from . import app_info -from . import core_upgrade -from . import core_localize -from . import install -from . import clone_configuration -from . import copy_apps -from . import unregister_folders -from . import desktop_migration -from . import cache_yaml -from . import get_entity_commands -from . import constants - - -from .. import constants as constants_global from .. import LogManager -from ..platform.engine import start_engine, get_environment_from_context +from .. import constants as constants_global from ..errors import TankError +from ..platform.engine import get_environment_from_context, start_engine +from . import ( + app_info, + cache_apps, + cache_yaml, + clone_configuration, + constants, + copy_apps, + core_localize, + core_upgrade, + desktop_migration, + dump_config, + folders, + get_entity_commands, + install, + misc, + move_pc, + path_cache, + pc_overview, + push_pc, + setup_project, + setup_project_wizard, + switch, + unregister_folders, + update, + validate_config, +) +from .action_base import Action +from .interaction import RawInputCommandInteraction, YesToEverythingInteraction log = LogManager.get_logger(__name__) @@ -473,7 +473,7 @@ def run_action(log, tk, ctx, command, args): found_action = x break - if found_action and found_action.wants_running_shell_engine == False: + if found_action and found_action.wants_running_shell_engine is False: log.debug("No need to load up the engine for this command.") else: # try to load the engine. diff --git a/python/tk-core/python/tank/commands/unregister_folders.py b/python/tk-core/python/tank/commands/unregister_folders.py index 7aeb58e8..8b61967f 100644 --- a/python/tk-core/python/tank/commands/unregister_folders.py +++ b/python/tk-core/python/tank/commands/unregister_folders.py @@ -12,10 +12,9 @@ Method to unregister folders from the path cache """ -from ..errors import TankError from .. import path_cache +from ..errors import TankError from .action_base import Action -from ..util.login import get_current_user class UnregisterFoldersAction(Action): diff --git a/python/tk-core/python/tank/commands/update.py b/python/tk-core/python/tank/commands/update.py index 2b8129e5..2f3b1e64 100644 --- a/python/tk-core/python/tank/commands/update.py +++ b/python/tk-core/python/tank/commands/update.py @@ -10,14 +10,12 @@ import os -from .action_base import Action -from . import console_utils -from . import util -from ..platform.environment import WritableEnvironment -from ..descriptor import CheckVersionConstraintsError -from . import constants -from ..util.version import is_version_number, is_version_newer from .. import pipelineconfig_utils +from ..descriptor import CheckVersionConstraintsError +from ..platform.environment import WritableEnvironment +from ..util.version import is_version_newer, is_version_number +from . import console_utils, constants, util +from .action_base import Action class AppUpdatesAction(Action): @@ -110,7 +108,7 @@ def run_interactive(self, log, args): :param log: std python logger :param args: command line args """ - (use_legacy_parser, args) = util.should_use_legacy_yaml_parser(args) + use_legacy_parser, args = util.should_use_legacy_yaml_parser(args) preserve_yaml = not use_legacy_parser if len(args) == 0: @@ -368,7 +366,7 @@ def _check_for_updates( x["new_descriptor"].version, ) ) - (_, url) = x["new_descriptor"].changelog + _, url = x["new_descriptor"].changelog if url: summary.append("Change Log: %s" % url) summary.append("") @@ -512,11 +510,11 @@ def _update_item( # ensure that all required frameworks have been installed # find the file where our item is being installed if framework_name: - (_, yml_file) = env.find_location_for_framework(framework_name) + _, yml_file = env.find_location_for_framework(framework_name) elif app_name: - (_, yml_file) = env.find_location_for_app(engine_name, app_name) + _, yml_file = env.find_location_for_app(engine_name, app_name) else: - (_, yml_file) = env.find_location_for_engine(engine_name) + _, yml_file = env.find_location_for_engine(engine_name) console_utils.ensure_frameworks_installed( log, tk, yml_file, new_descriptor, env, self._interaction_interface @@ -791,7 +789,7 @@ def _check_item_update_status( out_of_date = latest_desc.version != curr_desc.version # check deprecation - (is_dep, dep_msg) = latest_desc.deprecation_status + is_dep, dep_msg = latest_desc.deprecation_status if is_dep: # we treat deprecation as an out of date that cannot be upgraded! diff --git a/python/tk-core/python/tank/commands/validate_config.py b/python/tk-core/python/tank/commands/validate_config.py index ce2d6b80..539ca38c 100644 --- a/python/tk-core/python/tank/commands/validate_config.py +++ b/python/tk-core/python/tank/commands/validate_config.py @@ -9,9 +9,10 @@ # not expressly granted therein are reserved by Shotgun Software Inc. import os -from .action_base import Action + from ..errors import TankError -from ..platform import validation, bundle +from ..platform import bundle, validation +from .action_base import Action class ValidateConfigAction(Action): @@ -83,9 +84,7 @@ def _run(self, log, parameters): log.info("") log.info("") - log.info( - "Welcome to the Flow Production Tracking Configuration validator!" - ) + log.info("Welcome to the Flow Production Tracking Configuration validator!") log.info("") log.info("Found the following environments:") diff --git a/python/tk-core/python/tank/context.py b/python/tk-core/python/tank/context.py index 4fe363ed..8c3757c8 100644 --- a/python/tk-core/python/tank/context.py +++ b/python/tk-core/python/tank/context.py @@ -13,22 +13,22 @@ """ -import os +from __future__ import annotations # needed for python 3.9 support + import copy import json +import os from tank_vendor import yaml -from . import authentication - -from .util import login -from .util import shotgun_entity -from .util import shotgun -from .util import get_sg_entity_name_field -from .util import pickle -from . import constants -from .errors import TankError, TankContextDeserializationError +from tank_vendor.flow_integration_sdk import sandbox + +from . import authentication, constants +from .authentication import flow_auth +from .errors import TankContextDeserializationError, TankError +from .flowam import constants as flow_const from .path_cache import PathCache from .template import TemplatePath +from .util import login, pickle, shotgun, shotgun_entity class Context(object): @@ -72,6 +72,7 @@ def __init__( user=None, additional_entities=None, source_entity=None, + flow_draft_id=None, ): """ Context objects are not constructed by hand but are fabricated by the @@ -86,6 +87,7 @@ def __init__( self.__user = user self.__additional_entities = additional_entities or [] self.__source_entity = source_entity + self.__flow_draft_id = flow_draft_id self._entity_fields_cache = {} def __repr__(self): @@ -176,9 +178,9 @@ def _entity_dicts_eq(d1, d2): :param d2: Second entity dictionary :returns: True if d1 and d2 are considered equal, otherwise False. """ - if d1 == d2 == None: + if d1 is None and d2 is None: return True - if d1 == None or d2 == None: + if d1 is None or d2 is None: return False return d1["type"] == d2["type"] and d1["id"] == d2["id"] @@ -259,6 +261,7 @@ def __deepcopy__(self, memo): ctx_copy.__user = copy.deepcopy(self.__user, memo) ctx_copy.__additional_entities = copy.deepcopy(self.__additional_entities, memo) ctx_copy.__source_entity = copy.deepcopy(self.__source_entity, memo) + ctx_copy.__flow_draft_id = copy.deepcopy(self.__flow_draft_id, memo) # except: # ctx_copy._entity_fields_cache @@ -403,6 +406,45 @@ def additional_entities(self): """ return self.__additional_entities + @property + def flow_project_id(self) -> str | None: + """ + The FlowAM project ID for this context, or ``None`` if the project is + not FlowAM-enabled or there is no project in this context. + + The value is read from the ``sg_flow_am_id`` field on the project dict. + It is populated by the bootstrap manager after it queries ShotGrid. + + :returns: A string containing the FlowAM project ID, or ``None``. + :rtype: str or None + """ + if self.project: + return self.project.get(flow_auth.AM_READY_PROJECT_FIELD) + return None + + @property + def flow_draft_id(self) -> str | None: + """Current Flow draft context. + If not None, this designates the current draft being worked on within the DCC session. + """ + return self.__flow_draft_id + + @property + def flow_schema_version(self) -> str | None: + """ + The FlowAM schema version of the project in context, or ``None`` if the project is + not FlowAM-enabled or there is no schema created. + + The value is read from the ``sg_flow_schema_config_version`` field on the project dict. + It is populated by the bootstrap manager after it queries ShotGrid. + + :returns: A string containing the FlowAM schema version, or ``None``. + :rtype: str or None + """ + if self.project: + return self.project.get(flow_const.FLOW_SCHEMA_VERSION_FIELD) + return None + @property def entity_locations(self): """ @@ -708,6 +750,19 @@ def create_copy_for_user(self, user): ctx_copy.__user = user return ctx_copy + def set_flow_context(self, file_path: str): + """Set the current flow asset context based on opened file path. + + Args: + file_path: File currently opened in DCC. + """ + self.__flow_draft_id = sandbox.get_draft_context(file_path) + + def clear_flow_context(self): + """Clear the current flow asset context.""" + + self.__flow_draft_id = None + ################################################################################################ # serialization @@ -815,8 +870,8 @@ def deserialize(cls, context_str): def to_dict(self): """ Converts the context into a dictionary with keys ``project``, - ``entity``, ``user``, ``step``, ``task``, ``additional_entities`` and - ``source_entity``. + ``entity``, ``user``, ``step``, ``task``, ``additional_entities``, + ``source_entity``, ``flow_project_id``, ``flow_draft_id`` and ``flow_schema_version``. .. note :: Contrary to :meth:`Context.serialize`, this method discards information @@ -835,6 +890,9 @@ def to_dict(self): self._cleanup_entity(entity) for entity in self.additional_entities ], "source_entity": self._cleanup_entity(self.source_entity), + "flow_project_id": self.flow_project_id, + "flow_draft_id": self.flow_draft_id, + "flow_schema_version": self.flow_schema_version, } def _cleanup_entity(self, entity): @@ -894,7 +952,7 @@ def _from_dict(cls, data): :returns: :class:`Context` """ - return Context( + ctx = Context( tk=data.get("tk"), project=data.get("project"), entity=data.get("entity"), @@ -903,7 +961,23 @@ def _from_dict(cls, data): user=data.get("user"), additional_entities=data.get("additional_entities"), source_entity=data.get("source_entity"), + flow_draft_id=data.get("flow_draft_id"), ) + if ( + ctx.project is not None + and "flow_project_id" in data + and flow_auth.AM_READY_PROJECT_FIELD not in ctx.project + ): + ctx.project[flow_auth.AM_READY_PROJECT_FIELD] = data["flow_project_id"] + if ( + ctx.project is not None + and "flow_schema_version" in data + and flow_const.FLOW_SCHEMA_VERSION_FIELD not in ctx.project + ): + ctx.project[flow_const.FLOW_SCHEMA_VERSION_FIELD] = data[ + "flow_schema_version" + ] + return ctx ################################################################################################ # private methods @@ -931,7 +1005,7 @@ def _fields_from_shotgun(self, template, entities, validate): # this key is a shotgun value that needs fetching! # ensure that the context actually provides the desired entities - if not key.shotgun_entity_type in entities: + if key.shotgun_entity_type not in entities: if validate: raise TankError( "Key '%s' in template '%s' could not be populated by " @@ -1305,7 +1379,8 @@ def _from_entity_type_and_id(tk, entity, source_entity=None): if sg_entity is None: raise TankError( - "Entity %s with id %s not found in Flow Production Tracking!" % (entity_type, entity_id) + "Entity %s with id %s not found in Flow Production Tracking!" + % (entity_type, entity_id) ) if sg_entity.get("task"): @@ -1412,7 +1487,6 @@ def _from_entity_dictionary(tk, entity_dictionary, source_entity=None): } entity_type = entity_dictionary["type"] - entity_id = entity_dictionary["id"] # try to determine the various entities from the entity dictionary: project = None @@ -1477,7 +1551,7 @@ def _build_clean_entity(ent): if "id" not in ent or "type" not in ent: return None ent_name = _get_entity_name(ent) - if ent_name == None: + if ent_name is None: return None # return a clean dictionary: return {"type": ent["type"], "id": ent["id"], "name": ent_name} @@ -1719,7 +1793,7 @@ def context_yaml_representer(dumper, context): # pipeline config path as part of the dict context_dict["_pc_path"] = context.tank.pipeline_configuration.get_path() - return dumper.represent_mapping(u"!TankContext", context_dict) + return dumper.represent_mapping("!TankContext", context_dict) def context_yaml_constructor(loader, node): @@ -1750,7 +1824,7 @@ def context_yaml_constructor(loader, node): yaml.add_representer(Context, context_yaml_representer) -yaml.add_constructor(u"!TankContext", context_yaml_constructor) +yaml.add_constructor("!TankContext", context_yaml_constructor) ################################################################################################ # utility methods @@ -1767,7 +1841,7 @@ def _get_entity_name(entity_dictionary): """ name_field = shotgun_entity.get_sg_entity_name_field(entity_dictionary["type"]) entity_name = entity_dictionary.get(name_field) - if entity_name == None: + if entity_name is None: # Also check to see if entity contains 'name': if name_field != "name": entity_name = entity_dictionary.get("name") @@ -1807,7 +1881,9 @@ def _task_from_sg(tk, task_id, additional_fields=None): "Task", [["id", "is", task_id]], standard_fields + additional_fields ) if not task: - raise TankError("Unable to locate Task with id %s in Flow Production Tracking" % task_id) + raise TankError( + "Unable to locate Task with id %s in Flow Production Tracking" % task_id + ) # add task so it can be processed with other shotgun entities task["task"] = {"type": "Task", "id": task_id, "name": task["content"]} @@ -1867,7 +1943,8 @@ def _entity_from_sg(tk, entity_type, entity_id): if not data: raise TankError( - "Unable to locate %s with id %s in Flow Production Tracking" % (entity_type, entity_id) + "Unable to locate %s with id %s in Flow Production Tracking" + % (entity_type, entity_id) ) # create context diff --git a/python/tk-core/python/tank/deploy/__init__.py b/python/tk-core/python/tank/deploy/__init__.py index 7f72de65..b129e819 100644 --- a/python/tk-core/python/tank/deploy/__init__.py +++ b/python/tk-core/python/tank/deploy/__init__.py @@ -8,6 +8,4 @@ # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights # not expressly granted therein are reserved by Shotgun Software Inc. -from . import descriptor -from . import dev_descriptor -from . import util +from . import descriptor, dev_descriptor, util diff --git a/python/tk-core/python/tank/deploy/descriptor.py b/python/tk-core/python/tank/deploy/descriptor.py index b8482eba..c6fcd948 100644 --- a/python/tk-core/python/tank/deploy/descriptor.py +++ b/python/tk-core/python/tank/deploy/descriptor.py @@ -14,7 +14,7 @@ This code may be removed at some point in the future. """ -from ..descriptor import create_descriptor, Descriptor +from ..descriptor import Descriptor, create_descriptor from ..util import shotgun diff --git a/python/tk-core/python/tank/deploy/util.py b/python/tk-core/python/tank/deploy/util.py index be85eb0d..9b55d6ba 100644 --- a/python/tk-core/python/tank/deploy/util.py +++ b/python/tk-core/python/tank/deploy/util.py @@ -16,4 +16,4 @@ # import methods to ensure that older version of the desktop engine # will function correctly - the code calls these internal methods -from ..util.version import is_version_newer, is_version_older +from ..util.version import is_version_newer, is_version_older # noqa: F401 diff --git a/python/tk-core/python/tank/descriptor/__init__.py b/python/tk-core/python/tank/descriptor/__init__.py index 2031cc02..b8b82e15 100644 --- a/python/tk-core/python/tank/descriptor/__init__.py +++ b/python/tk-core/python/tank/descriptor/__init__.py @@ -10,22 +10,20 @@ from .descriptor import Descriptor, create_descriptor -from .descriptor_core import CoreDescriptor -from .descriptor_bundle import AppDescriptor, FrameworkDescriptor, EngineDescriptor +from .descriptor_bundle import AppDescriptor, EngineDescriptor, FrameworkDescriptor from .descriptor_config import ConfigDescriptor - +from .descriptor_core import CoreDescriptor from .errors import ( + CheckVersionConstraintsError, + InvalidAppStoreCredentialsError, TankAppStoreConnectionError, TankAppStoreError, + TankCheckVersionConstraintsError, TankDescriptorError, - InvalidAppStoreCredentialsError, TankInvalidAppStoreCredentialsError, - CheckVersionConstraintsError, - TankCheckVersionConstraintsError, TankInvalidInterpreterLocationError, TankMissingManifestError, ) - from .io_descriptor import ( descriptor_dict_to_uri, descriptor_uri_to_dict, diff --git a/python/tk-core/python/tank/descriptor/descriptor.py b/python/tk-core/python/tank/descriptor/descriptor.py index 0ef5f448..b767fa93 100644 --- a/python/tk-core/python/tank/descriptor/descriptor.py +++ b/python/tk-core/python/tank/descriptor/descriptor.py @@ -8,17 +8,15 @@ # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights # not expressly granted therein are reserved by Shotgun Software Inc. -import os import copy +import os +from .. import constants as constants2 from ..log import LogManager -from ..util import filesystem -from .io_descriptor import create_io_descriptor -from .errors import TankDescriptorError -from ..util import LocalFileStorageManager +from ..util import LocalFileStorageManager, filesystem from . import constants -from .. import constants as constants2 - +from .errors import TankDescriptorError +from .io_descriptor import create_io_descriptor logger = LogManager.get_logger(__name__) diff --git a/python/tk-core/python/tank/descriptor/descriptor_bundle.py b/python/tk-core/python/tank/descriptor/descriptor_bundle.py index 92eb2a9c..0a591262 100644 --- a/python/tk-core/python/tank/descriptor/descriptor_bundle.py +++ b/python/tk-core/python/tank/descriptor/descriptor_bundle.py @@ -8,13 +8,11 @@ # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights # not expressly granted therein are reserved by Shotgun Software Inc. -from .descriptor import Descriptor -from .errors import TankDescriptorError, CheckVersionConstraintsError -from . import constants -from .. import LogManager +from .. import LogManager, pipelineconfig_utils from ..util.version import is_version_older -from ..util import shotgun -from .. import pipelineconfig_utils +from . import constants +from .descriptor import Descriptor +from .errors import CheckVersionConstraintsError, TankDescriptorError log = LogManager.get_logger(__name__) diff --git a/python/tk-core/python/tank/descriptor/descriptor_cached_config.py b/python/tk-core/python/tank/descriptor/descriptor_cached_config.py index 15ddb494..5c826420 100644 --- a/python/tk-core/python/tank/descriptor/descriptor_cached_config.py +++ b/python/tk-core/python/tank/descriptor/descriptor_cached_config.py @@ -12,10 +12,10 @@ from tank_vendor import yaml +from .. import LogManager from . import constants -from .errors import TankDescriptorError from .descriptor_config import ConfigDescriptor -from .. import LogManager +from .errors import TankDescriptorError log = LogManager.get_logger(__name__) @@ -69,7 +69,7 @@ def associated_core_descriptor(self): # # location: # name: tk-core - # type: app_store + # type: app_store # version: v0.16.34 log.debug("Detected core descriptor file '%s'" % core_descriptor_path) diff --git a/python/tk-core/python/tank/descriptor/descriptor_config.py b/python/tk-core/python/tank/descriptor/descriptor_config.py index ca8e00da..e86f12ba 100644 --- a/python/tk-core/python/tank/descriptor/descriptor_config.py +++ b/python/tk-core/python/tank/descriptor/descriptor_config.py @@ -10,16 +10,14 @@ import os +from .. import LogManager from ..errors import TankFileDoesNotExistError +from ..util import ShotgunPath, StorageRoots +from ..util.version import is_version_older from . import constants -from .errors import TankInvalidInterpreterLocationError from .descriptor import Descriptor, create_descriptor -from .io_descriptor import is_descriptor_version_missing -from .. import LogManager -from ..util import StorageRoots -from ..util import ShotgunPath -from ..util.version import is_version_older -from .io_descriptor import descriptor_uri_to_dict +from .errors import TankInvalidInterpreterLocationError +from .io_descriptor import descriptor_uri_to_dict, is_descriptor_version_missing log = LogManager.get_logger(__name__) diff --git a/python/tk-core/python/tank/descriptor/descriptor_core.py b/python/tk-core/python/tank/descriptor/descriptor_core.py index 904899d9..3a639f6c 100644 --- a/python/tk-core/python/tank/descriptor/descriptor_core.py +++ b/python/tk-core/python/tank/descriptor/descriptor_core.py @@ -8,9 +8,9 @@ # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights # not expressly granted therein are reserved by Shotgun Software Inc. +from . import constants from .descriptor import Descriptor from .errors import TankMissingManifestError -from . import constants class CoreDescriptor(Descriptor): diff --git a/python/tk-core/python/tank/descriptor/descriptor_installed_config.py b/python/tk-core/python/tank/descriptor/descriptor_installed_config.py index 1c2e3927..7c33a318 100644 --- a/python/tk-core/python/tank/descriptor/descriptor_installed_config.py +++ b/python/tk-core/python/tank/descriptor/descriptor_installed_config.py @@ -10,18 +10,16 @@ import os -from .descriptor_config import ConfigDescriptor -from .. import pipelineconfig_utils -from .. import LogManager -from ..util import ShotgunPath -from . import constants -from .errors import TankMissingManifestError - +from .. import LogManager, pipelineconfig_utils from ..errors import ( - TankNotPipelineConfigurationError, TankFileDoesNotExistError, TankInvalidCoreLocationError, + TankNotPipelineConfigurationError, ) +from ..util import ShotgunPath +from . import constants +from .descriptor_config import ConfigDescriptor +from .errors import TankMissingManifestError log = LogManager.get_logger(__name__) diff --git a/python/tk-core/python/tank/descriptor/errors.py b/python/tk-core/python/tank/descriptor/errors.py index 3ea15ae0..5774355d 100644 --- a/python/tk-core/python/tank/descriptor/errors.py +++ b/python/tk-core/python/tank/descriptor/errors.py @@ -12,8 +12,8 @@ All custom exceptions that this module emits are defined here. """ -from ..errors import TankError from .. import errors +from ..errors import TankError class TankDescriptorError(TankError): diff --git a/python/tk-core/python/tank/descriptor/io_descriptor/__init__.py b/python/tk-core/python/tank/descriptor/io_descriptor/__init__.py index 50763697..e294b962 100644 --- a/python/tk-core/python/tank/descriptor/io_descriptor/__init__.py +++ b/python/tk-core/python/tank/descriptor/io_descriptor/__init__.py @@ -10,8 +10,8 @@ from .factory import ( create_io_descriptor, - descriptor_uri_to_dict, descriptor_dict_to_uri, + descriptor_uri_to_dict, is_descriptor_version_missing, ) @@ -22,15 +22,15 @@ def _initialize_descriptor_factory(): This complex process for handling the IODescriptor abstract factory management is in order to avoid local imports in classes. """ - from .base import IODescriptorBase from .appstore import IODescriptorAppStore + from .base import IODescriptorBase from .dev import IODescriptorDev - from .path import IODescriptorPath - from .shotgun_entity import IODescriptorShotgunEntity - from .git_tag import IODescriptorGitTag from .git_branch import IODescriptorGitBranch + from .git_tag import IODescriptorGitTag from .github_release import IODescriptorGithubRelease from .manual import IODescriptorManual + from .path import IODescriptorPath + from .shotgun_entity import IODescriptorShotgunEntity IODescriptorBase.register_descriptor_factory("app_store", IODescriptorAppStore) IODescriptorBase.register_descriptor_factory("dev", IODescriptorDev) diff --git a/python/tk-core/python/tank/descriptor/io_descriptor/appstore.py b/python/tk-core/python/tank/descriptor/io_descriptor/appstore.py index 746b6c54..d6bfdf5a 100644 --- a/python/tk-core/python/tank/descriptor/io_descriptor/appstore.py +++ b/python/tk-core/python/tank/descriptor/io_descriptor/appstore.py @@ -65,7 +65,7 @@ class IODescriptorAppStore(IODescriptorDownloadable): _app_store_connections = {} # internal app store mappings - (APP, FRAMEWORK, ENGINE, CONFIG, CORE) = range(5) + APP, FRAMEWORK, ENGINE, CONFIG, CORE = range(5) _APP_STORE_OBJECT = { constants.DESCRIPTOR_APP: constants.TANK_APP_ENTITY_TYPE, @@ -231,7 +231,7 @@ def __refresh_metadata(self, path, sg_bundle_data=None, sg_version_data=None): link_field = self._APP_STORE_LINK[self._bundle_type] # connect to the app store - (sg, _) = self.__create_sg_app_store_connection() + sg, _ = self.__create_sg_app_store_connection() if self._bundle_type == self.CORE: # special handling of core since it doesn't have a high-level 'bundle' entity @@ -413,7 +413,7 @@ def _download_local(self, destination_path): is to be downloaded to. """ # connect to the app store - (sg, script_user) = self.__create_sg_app_store_connection() + sg, script_user = self.__create_sg_app_store_connection() # fetch metadata from sg... metadata = self.__refresh_metadata(destination_path) @@ -445,7 +445,7 @@ def _post_download(self, download_path): # write a stats record to the tank app store try: # connect to the app store - (sg, script_user) = self.__create_sg_app_store_connection() + sg, script_user = self.__create_sg_app_store_connection() # fetch metadata from sg... metadata = self.__refresh_metadata(download_path) @@ -745,7 +745,7 @@ def get_latest_version(self, constraint_pattern=None): ) # connect to the app store - (sg, _) = self.__create_sg_app_store_connection() + sg, _ = self.__create_sg_app_store_connection() # get latest get the filter logic for what to exclude if constants.APP_STORE_QA_MODE_ENV_VAR in os.environ: @@ -981,7 +981,7 @@ def __create_sg_app_store_connection(self): # Connect to associated Shotgun site and retrieve the credentials to use to # connect to the app store site try: - (script_name, script_key) = self.__get_app_store_key_from_shotgun() + script_name, script_key = self.__get_app_store_key_from_shotgun() except urllib.error.HTTPError as e: if e.code == 403: # edge case alert! @@ -994,7 +994,7 @@ def __create_sg_app_store_connection(self): # trigger a refresh of our session token by issuing a shotgun API call self._sg_connection.find_one("HumanUser", []) # and retry - (script_name, script_key) = self.__get_app_store_key_from_shotgun() + script_name, script_key = self.__get_app_store_key_from_shotgun() else: raise @@ -1146,7 +1146,7 @@ def has_remote_access(self): % self ) # connect to the app store - (sg, _) = self.__create_sg_app_store_connection() + sg, _ = self.__create_sg_app_store_connection() log.debug("...connection established: %s" % sg) except Exception as e: log.debug("...could not establish connection: %s" % e) diff --git a/python/tk-core/python/tank/descriptor/io_descriptor/dev.py b/python/tk-core/python/tank/descriptor/io_descriptor/dev.py index 73dfbd76..f5e621c6 100644 --- a/python/tk-core/python/tank/descriptor/io_descriptor/dev.py +++ b/python/tk-core/python/tank/descriptor/io_descriptor/dev.py @@ -8,9 +8,8 @@ # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights # not expressly granted therein are reserved by Shotgun Software Inc. -from .path import IODescriptorPath - from ... import LogManager +from .path import IODescriptorPath log = LogManager.get_logger(__name__) diff --git a/python/tk-core/python/tank/descriptor/io_descriptor/downloadable.py b/python/tk-core/python/tank/descriptor/io_descriptor/downloadable.py index 4d9e4f5f..a875f30d 100644 --- a/python/tk-core/python/tank/descriptor/io_descriptor/downloadable.py +++ b/python/tk-core/python/tank/descriptor/io_descriptor/downloadable.py @@ -12,11 +12,10 @@ import os import uuid -from .base import IODescriptorBase -from ..errors import TankDescriptorIOError -from ...util import filesystem - from ... import LogManager +from ...util import filesystem +from ..errors import TankDescriptorIOError +from .base import IODescriptorBase log = LogManager.get_logger(__name__) diff --git a/python/tk-core/python/tank/descriptor/io_descriptor/factory.py b/python/tk-core/python/tank/descriptor/io_descriptor/factory.py index 4759e031..9cb740e5 100644 --- a/python/tk-core/python/tank/descriptor/io_descriptor/factory.py +++ b/python/tk-core/python/tank/descriptor/io_descriptor/factory.py @@ -10,11 +10,10 @@ import copy +from ... import LogManager from ..errors import TankDescriptorError from .base import IODescriptorBase -from ... import LogManager - log = LogManager.get_logger(__name__) diff --git a/python/tk-core/python/tank/descriptor/io_descriptor/git.py b/python/tk-core/python/tank/descriptor/io_descriptor/git.py index dcaede1e..536aa61d 100644 --- a/python/tk-core/python/tank/descriptor/io_descriptor/git.py +++ b/python/tk-core/python/tank/descriptor/io_descriptor/git.py @@ -8,18 +8,16 @@ # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights # not expressly granted therein are reserved by Shotgun Software Inc. import os -import uuid import shutil -import tempfile import subprocess +import tempfile +import uuid -from .downloadable import IODescriptorDownloadable from ... import LogManager -from ...util.process import subprocess_check_output, SubprocessCalledProcessError - +from ...util import filesystem, is_windows +from ...util.process import SubprocessCalledProcessError, subprocess_check_output from ..errors import TankError -from ...util import filesystem -from ...util import is_windows +from .downloadable import IODescriptorDownloadable log = LogManager.get_logger(__name__) @@ -115,7 +113,7 @@ def _clone_then_execute_git_commands( log.debug("Checking that git exists and can be executed...") try: output = _check_output(["git", "--version"]) - except: + except Exception: log.exception("Unexpected error:") raise TankGitError( "Cannot execute the 'git' command. Please make sure that git is " @@ -236,7 +234,7 @@ def get_system_name(self): and for folders on disk, e.g. 'tk-maya' """ bn = os.path.basename(self._path) - (name, ext) = os.path.splitext(bn) + name, ext = os.path.splitext(bn) return name def has_remote_access(self): diff --git a/python/tk-core/python/tank/descriptor/io_descriptor/git_branch.py b/python/tk-core/python/tank/descriptor/io_descriptor/git_branch.py index 62951553..bfb779ed 100644 --- a/python/tk-core/python/tank/descriptor/io_descriptor/git_branch.py +++ b/python/tk-core/python/tank/descriptor/io_descriptor/git_branch.py @@ -115,7 +115,7 @@ def _is_latest_commit(self, version, branch): log.debug("Checking if the version is pointing to the latest commit...") try: output = _check_output(["git", "ls-remote", self._path, branch]) - except: + except Exception: log.exception("Unexpected error:") raise TankGitError( "Cannot execute the 'git' command. Please make sure that git is " diff --git a/python/tk-core/python/tank/descriptor/io_descriptor/git_tag.py b/python/tk-core/python/tank/descriptor/io_descriptor/git_tag.py index aa1026ed..33a7d71b 100644 --- a/python/tk-core/python/tank/descriptor/io_descriptor/git_tag.py +++ b/python/tk-core/python/tank/descriptor/io_descriptor/git_tag.py @@ -7,13 +7,13 @@ # By accessing, using, copying or modifying this work you indicate your # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights # not expressly granted therein are reserved by Shotgun Software Inc. -import os import copy +import os import re -from .git import IODescriptorGit -from ..errors import TankDescriptorError from ... import LogManager +from ..errors import TankDescriptorError +from .git import IODescriptorGit log = LogManager.get_logger(__name__) @@ -51,9 +51,7 @@ def __init__(self, descriptor_dict, sg_connection, bundle_type): ) # call base class - super().__init__( - descriptor_dict, sg_connection, bundle_type - ) + super().__init__(descriptor_dict, sg_connection, bundle_type) # path is handled by base class - all git descriptors # have a path to a repo diff --git a/python/tk-core/python/tank/descriptor/io_descriptor/manual.py b/python/tk-core/python/tank/descriptor/io_descriptor/manual.py index ff11158b..0b86fcff 100644 --- a/python/tk-core/python/tank/descriptor/io_descriptor/manual.py +++ b/python/tk-core/python/tank/descriptor/io_descriptor/manual.py @@ -8,9 +8,10 @@ # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights # not expressly granted therein are reserved by Shotgun Software Inc. import os -from .base import IODescriptorBase + from ... import LogManager from ..errors import TankDescriptorError +from .base import IODescriptorBase log = LogManager.get_logger(__name__) diff --git a/python/tk-core/python/tank/descriptor/io_descriptor/path.py b/python/tk-core/python/tank/descriptor/io_descriptor/path.py index 2dc603eb..502c44b3 100644 --- a/python/tk-core/python/tank/descriptor/io_descriptor/path.py +++ b/python/tk-core/python/tank/descriptor/io_descriptor/path.py @@ -10,10 +10,10 @@ import os -from .base import IODescriptorBase -from ..errors import TankDescriptorError -from ...util import ShotgunPath from ... import LogManager +from ...util import ShotgunPath +from ..errors import TankDescriptorError +from .base import IODescriptorBase log = LogManager.get_logger(__name__) diff --git a/python/tk-core/python/tank/descriptor/io_descriptor/shotgun_entity.py b/python/tk-core/python/tank/descriptor/io_descriptor/shotgun_entity.py index d04bd57a..62fae486 100644 --- a/python/tk-core/python/tank/descriptor/io_descriptor/shotgun_entity.py +++ b/python/tk-core/python/tank/descriptor/io_descriptor/shotgun_entity.py @@ -64,7 +64,7 @@ class IODescriptorShotgunEntity(IODescriptorDownloadable): The latest version is defined as the current record available in Shotgun. """ - (_MODE_ID_BASED, _MODE_NAME_BASED) = range(2) + _MODE_ID_BASED, _MODE_NAME_BASED = range(2) def __init__(self, descriptor_dict, sg_connection, bundle_type): """ diff --git a/python/tk-core/python/tank/flowam/__init__.py b/python/tk-core/python/tank/flowam/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/python/tk-core/python/tank/flowam/config.json b/python/tk-core/python/tank/flowam/config.json new file mode 100644 index 00000000..16d3ff4b --- /dev/null +++ b/python/tk-core/python/tank/flowam/config.json @@ -0,0 +1,188 @@ +{ + "name": "demo_pipeline", + "version": "1.0.2", + "description": "Flow Toolkit pipeline schema definitions", + "schemas": [ + { + "name": "type.container", + "version": "1.0.0", + "display_name": "Container", + "kind": "type", + "description": "Basic container representing a work unit.", + "inherits": [], + "properties": [] + }, + { + "name": "type.container.asset", + "version": "1.0.0", + "display_name": "Asset Build", + "kind": "type", + "description": "Asset type container.", + "inherits": [ + "$ref:type.container" + ], + "properties": [] + }, + { + "name": "type.container.shot", + "version": "1.0.0", + "display_name": "Shot", + "kind": "type", + "description": "Shot type container.", + "inherits": [ + "$ref:type.container" + ], + "properties": [] + }, + { + "name": "type.pipelineStep", + "version": "1.0.0", + "display_name": "Pipeline Step", + "kind": "type", + "description": "Base type for a step in a production pipeline.", + "inherits": [], + "properties": [] + }, + { + "name": "type.workfile", + "version": "1.0.0", + "display_name": "Workfile", + "kind": "type", + "description": "File-based work unit.", + "inherits": [], + "properties": [] + }, + { + "name": "type.workfile.generic", + "version": "1.0.0", + "display_name": "Generic Workfile", + "kind": "type", + "description": "Generic workfile which can contain arbitrary file types.", + "inherits": [ + "$ref:type.workfile" + ], + "properties": [] + }, + { + "name": "type.workfile.maya", + "version": "1.0.0", + "display_name": "Maya Workfile", + "kind": "type", + "description": "Maya native workfile.", + "inherits": [ + "$ref:type.workfile" + ], + "properties": [] + }, + { + "name": "type.workfile.hou", + "version": "1.0.0", + "display_name": "Houdini Workfile", + "kind": "type", + "description": "Houdini native workfile.", + "inherits": [ + "$ref:type.workfile" + ], + "properties": [] + }, + { + "name": "type.workfile.nuke", + "version": "1.0.0", + "display_name": "Nuke Workfile", + "kind": "type", + "description": "Nuke script workfile.", + "inherits": [ + "$ref:type.workfile" + ], + "properties": [] + }, + { + "name": "type.workfile.usd", + "version": "1.0.0", + "display_name": "USD Workfile", + "kind": "type", + "description": "USD native workfile.", + "inherits": [ + "$ref:type.workfile" + ], + "properties": [] + }, + { + "name": "type.derivative", + "version": "1.0.0", + "display_name": "Derivative", + "kind": "type", + "description": "Derivative output from a workfile, also referred to as a work product.", + "inherits": [], + "properties": [] + }, + { + "name": "type.derivative.maya", + "version": "1.0.0", + "display_name": "Maya Derivative", + "kind": "type", + "description": "Maya native type derivative work product.", + "inherits": [ + "$ref:type.derivative" + ], + "properties": [] + }, + { + "name": "type.derivative.usd", + "version": "1.0.0", + "display_name": "USD Derivative", + "kind": "type", + "description": "Universal Scene Description (USD) derivative work product.", + "inherits": [ + "$ref:type.derivative" + ], + "properties": [] + }, + { + "name": "type.template", + "version": "1.0.0", + "display_name": "Template", + "kind": "type", + "description": "Indicates an asset that is intended to be used as a template for building other assets.", + "inherits": [], + "properties": [] + }, + { + "name": "type.derivative.abc", + "version": "1.0.0", + "display_name": "Alembic Derivative", + "kind": "type", + "description": "Alembic derivative work product.", + "inherits": [ + "$ref:type.derivative" + ], + "properties": [] + }, + { + "name": "type.fileSequence", + "version": "1.0.0", + "display_name": "File Sequence", + "kind": "type", + "description": "Asset contains a file sequence.", + "inherits": [], + "properties": [ + { + "name": "frameStart", + "data_type": "Int32" + }, + { + "name": "frameEnd", + "data_type": "Int32" + }, + { + "name": "frameSet", + "data_type": "String" + }, + { + "name": "fileFormat", + "data_type": "String" + } + ] + } + ] +} diff --git a/python/tk-core/python/tank/flowam/constants.py b/python/tk-core/python/tank/flowam/constants.py new file mode 100644 index 00000000..08352768 --- /dev/null +++ b/python/tk-core/python/tank/flowam/constants.py @@ -0,0 +1,34 @@ +# Copyright (c) 2026 Shotgun Software Inc. +# +# CONFIDENTIAL AND PROPRIETARY +# +# This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit +# Source Code License included in this distribution package. See LICENSE. +# By accessing, using, copying or modifying this work you indicate your +# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights +# not expressly granted therein are reserved by Shotgun Software Inc. + +"""Constants for Flow Integration.""" + +import pathlib + +# Flow Settings +# ------------- +# These are settings names that can be configured within a flow.yml file. +# These attributes are used to prepare a Flow Integration SDK session. + +FLOW_AUTH_APP_ID = "auth_application_id" +FLOW_AUTH_BASE_URL = "auth_base_url" +FLOW_AUTH_CALLBACK_URL = "auth_callback_url" +FLOW_ENDPOINT = "endpoint" +FLOW_WEB_URL = "web_url" +FLOW_SANDBOX_ROOT = "sandbox_root" +FLOW_STORAGE_ROOT = "storage_root" + +# Location of config for specifying custom schemas used +# in the toolkit Flow integration +FLOW_SCHEMA_CONFIG_PATH = str(pathlib.Path(__file__).resolve().parent) + "/config.json" + +# SG Project field that stores the current schema config version, +# used to skip schema provisioning when the version already matches. +FLOW_SCHEMA_VERSION_FIELD = "sg_flow_schema_config_version" diff --git a/python/tk-core/python/tank/flowam/create.py b/python/tk-core/python/tank/flowam/create.py new file mode 100644 index 00000000..125989e8 --- /dev/null +++ b/python/tk-core/python/tank/flowam/create.py @@ -0,0 +1,250 @@ +# Copyright (c) 2026 Shotgun Software Inc. +# +# CONFIDENTIAL AND PROPRIETARY +# +# This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit +# Source Code License included in this distribution package. See LICENSE. +# By accessing, using, copying or modifying this work you indicate your +# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights +# not expressly granted therein are reserved by Shotgun Software Inc. + +from __future__ import annotations # needed for python 3.9 support + +import re +from enum import Enum + +from tank_vendor.flow_integration_sdk.exceptions import CreateAssetError, FlowError +from tank_vendor.flow_integration_sdk.globals import FOLDER_TYPE_ID +from tank_vendor.flow_integration_sdk.objects import FlowAsset, FlowProject +from tank_vendor.flow_integration_sdk.publish import ( + TypeComponentSpec, + publish_new_asset, +) +from tank_vendor.flow_integration_sdk.schema import get_schema_id +from tank_vendor.flow_integration_sdk.utils import get_logger, trace + +from .utils import BaseInputs + +# Folder names +ASSET_FOLDER = "Assets" +SHOT_FOLDER = "Shots" +GENERIC_FOLDER = "Generic" +TEMPLATE_FOLDER = "Templates" + +# SG entity types +SHOT_TYPE = "Shot" +ASSET_TYPE = "Asset" + +# Custom schema types +ASSET_CONTAINER_TYPE = "type.container.asset" +CONTAINER_TYPE = "type.container" +DERIVATIVE_TYPE = "type.derivative" +GENERIC_WORKFILE_TYPE = "type.workfile.generic" +PIPELINE_STEP_TYPE = "type.pipelineStep" +SHOT_CONTAINER_TYPE = "type.container.shot" +TEMPLATE_TYPE = "type.template" +WORKFILE_TYPE = "type.workfile" + + +class CreateMode(Enum): + """Enum of modes for creating a new asset.""" + + NEW = "new" #: Create a DCC asset from a new scene as the source. + CURRENT = "current" #: Create a DCC asset from the current scene as the source. + TEMPLATE = "template" #: Create a DCC asset from template scene as the source. + GENERIC = "generic" #: Create a generic asset from a specified source file. + + +@trace +def create_asset_hierarchy(inputs: BaseInputs) -> FlowAsset: + """Ensure the folder hierarchy above a new generic workfile exists. + + Returns the immediate parent under which the workfile asset should be + created. + + Args: + inputs: A ``BaseInputs`` instance (or any inputs object with + ``am_project_id``, ``sg_entity_name``, and a ``create_mode`` + compatible with ``CreateMode.GENERIC``). + + Returns: + The parent :class:`FlowAsset` for the new workfile. + """ + root_folder = get_or_create_root_folder(inputs) + + if inputs.sg_entity_name: + parent = get_or_create_workfile_parent(root_folder, inputs) + else: + parent = root_folder + + return parent + + +@trace +def get_or_create_root_folder(inputs: BaseInputs) -> FlowAsset: + """Retrieve (or create) the top-level folder for the new asset. + + Returns: + Folder :class:`FlowAsset`. + + Raises: + CreateAssetError + """ + logger = get_logger(__name__) + + am_project_id = inputs.am_project_id + sg_entity_type = inputs.sg_entity_type + + try: + project = FlowProject(am_project_id) + except FlowError as exc: + msg = f"Invalid Flow project id provided: {am_project_id}" + raise CreateAssetError(data=inputs.asdict(), details=msg) from exc + + if sg_entity_type == SHOT_TYPE: + folder = project.find_child(SHOT_TYPE) + if not folder: + logger.info(f'Creating "{SHOT_TYPE}" folder...') + raw_asset = publish_new_asset( + name=ensure_unique_name(SHOT_TYPE, project), + parent_id=project.id, + description="Folder for Shot assets.", + components=[ + TypeComponentSpec(type_id=FOLDER_TYPE_ID, name=f"Type {SHOT_TYPE}") + ], + ) + folder = FlowAsset(raw_asset) + elif sg_entity_type == ASSET_TYPE: + folder = project.find_child(ASSET_FOLDER) + if not folder: + logger.info(f'Creating "{ASSET_FOLDER}" folder...') + raw_asset = publish_new_asset( + name=ensure_unique_name(ASSET_FOLDER, project), + parent_id=project.id, + description="Folder for Asset Build assets.", + components=[ + TypeComponentSpec(type_id=FOLDER_TYPE_ID, name=f"Type {ASSET_TYPE}") + ], + ) + folder = FlowAsset(raw_asset) + elif inputs.create_mode == CreateMode.GENERIC: + folder = project.find_child(GENERIC_FOLDER) + if not folder: + logger.info(f'Creating "{GENERIC_FOLDER}" folder...') + raw_asset = publish_new_asset( + name=ensure_unique_name(GENERIC_FOLDER, project), + parent_id=project.id, + description="Folder for Generic assets.", + components=[ + TypeComponentSpec( + type_id=FOLDER_TYPE_ID, name=f"Type {GENERIC_FOLDER}" + ) + ], + ) + folder = FlowAsset(raw_asset) + else: + msg = f"Invalid entity type provided: {sg_entity_type}." + raise CreateAssetError(data=inputs.asdict(), details=msg) + + return folder + + +@trace +def get_or_create_workfile_parent( + root_folder: FlowAsset, inputs: BaseInputs +) -> FlowAsset: + """Determine (and create if necessary) the task-level folder that will be + the direct parent of the workfile asset. + + Returns: + The task-folder :class:`FlowAsset`. + """ + logger = get_logger(__name__) + + sg_entity_type = inputs.sg_entity_type + sg_entity_name = inputs.sg_entity_name + sg_pipeline_step = inputs.sg_pipeline_step + sg_task_name = inputs.sg_task_name + + container = root_folder.find_child(sg_entity_name) + if not container: + logger.info( + f'Creating container asset for "{sg_entity_name}" under ' + f'folder "{root_folder.name}"...' + ) + container_type = ( + ASSET_CONTAINER_TYPE + if sg_entity_type == ASSET_TYPE + else SHOT_CONTAINER_TYPE + ) + raw_asset = publish_new_asset( + name=ensure_unique_name(sg_entity_name, root_folder), + parent_id=root_folder.id, + components=[ + TypeComponentSpec( + type_id=get_schema_id(container_type), name=f"Type {container_type}" + ) + ], + ) + container = FlowAsset(raw_asset) + + pipeline_step = container.find_child(sg_pipeline_step) + if not pipeline_step: + logger.info(f'Creating pipeline step asset for "{sg_pipeline_step}"...') + raw_asset = publish_new_asset( + name=ensure_unique_name(sg_pipeline_step, container), + parent_id=container.id, + components=[ + TypeComponentSpec( + type_id=get_schema_id(PIPELINE_STEP_TYPE), + name=f"Type {PIPELINE_STEP_TYPE}", + ) + ], + ) + pipeline_step = FlowAsset(raw_asset) + + task_folder = pipeline_step.find_child(sg_task_name) + if not task_folder: + logger.info(f'Creating task folder asset for "{sg_task_name}"...') + raw_asset = publish_new_asset( + name=ensure_unique_name(sg_task_name, pipeline_step), + parent_id=pipeline_step.id, + description=f'Folder for task "{sg_task_name}".', + components=[ + TypeComponentSpec(type_id=FOLDER_TYPE_ID, name=f"Type {FOLDER_TYPE_ID}") + ], + ) + task_folder = FlowAsset(raw_asset) + + return task_folder + + +def ensure_unique_name(name: str, parent: FlowAsset | FlowProject) -> str: + """Return a unique sibling name under *parent*, adding a numeric suffix + if a child with the same name already exists. + + Example: siblings ``["asset 1", "asset 2 (1)"]``, input ``"asset 2"`` + → returns ``"asset 2 (2)"``. + """ + logger = get_logger(__name__) + + if parent.find_child(name): + copies = parent.find_children(f"{name} (*)") + highest_index = 0 + for c in copies: + m = re.match(rf"{re.escape(name)} \((?P%s" % value
value_str += "description
", None)) + Item.setWindowTitle(QCoreApplication.translate("Item", "Form", None)) + self.name.setText(QCoreApplication.translate("Item", "Settings Name", None)) + self.value.setText(QCoreApplication.translate("Item", "Value: foo bar", None)) + self.type.setText(QCoreApplication.translate("Item", "Type: bool", None)) + self.description.setText( + QCoreApplication.translate( + "Item", + '\n' + '\n" + 'description
', + None, + ) + ) + # retranslateUi diff --git a/python/tk-core/python/tank/platform/qt/ui_tank_dialog.py b/python/tk-core/python/tank/platform/qt/ui_tank_dialog.py index 344b2eb7..1f81444b 100644 --- a/python/tk-core/python/tank/platform/qt/ui_tank_dialog.py +++ b/python/tk-core/python/tank/platform/qt/ui_tank_dialog.py @@ -9,138 +9,153 @@ ################################################################################ from . import QtCore + for name, cls in QtCore.__dict__.items(): - if isinstance(cls, type): globals()[name] = cls + if isinstance(cls, type): + globals()[name] = cls from . import QtGui + for name, cls in QtGui.__dict__.items(): - if isinstance(cls, type): globals()[name] = cls + if isinstance(cls, type): + globals()[name] = cls + +from . import resources_rc -from . import resources_rc class Ui_TankDialog(object): def setupUi(self, TankDialog): if not TankDialog.objectName(): - TankDialog.setObjectName(u"TankDialog") + TankDialog.setObjectName("TankDialog") TankDialog.resize(879, 551) - TankDialog.setStyleSheet(u"") + TankDialog.setStyleSheet("") self.verticalLayout_3 = QVBoxLayout(TankDialog) self.verticalLayout_3.setSpacing(0) - self.verticalLayout_3.setObjectName(u"verticalLayout_3") + self.verticalLayout_3.setObjectName("verticalLayout_3") self.verticalLayout_3.setContentsMargins(0, 0, 0, 0) self.stackedWidget = QStackedWidget(TankDialog) - self.stackedWidget.setObjectName(u"stackedWidget") + self.stackedWidget.setObjectName("stackedWidget") self.page_1 = QWidget() - self.page_1.setObjectName(u"page_1") - self.page_1.setStyleSheet(u"QWidget#page_1 {\n" -"margin: 0px;\n" -"}") + self.page_1.setObjectName("page_1") + self.page_1.setStyleSheet("QWidget#page_1 {\n" "margin: 0px;\n" "}") self.verticalLayout = QVBoxLayout(self.page_1) self.verticalLayout.setSpacing(0) - self.verticalLayout.setObjectName(u"verticalLayout") + self.verticalLayout.setObjectName("verticalLayout") self.verticalLayout.setContentsMargins(0, 0, 0, 0) self.top_group = QGroupBox(self.page_1) - self.top_group.setObjectName(u"top_group") + self.top_group.setObjectName("top_group") self.top_group.setMinimumSize(QSize(0, 45)) self.top_group.setMaximumSize(QSize(16777215, 45)) - self.top_group.setStyleSheet(u"#top_group {\n" -"background-color: #2D2D2D;\n" -"border: none;\n" -"border-bottom:1px solid #202020;\n" -"}\n" -"") + self.top_group.setStyleSheet( + "#top_group {\n" + "background-color: #2D2D2D;\n" + "border: none;\n" + "border-bottom:1px solid #202020;\n" + "}\n" + "" + ) self.top_group.setFlat(False) self.horizontalLayout = QHBoxLayout(self.top_group) self.horizontalLayout.setSpacing(0) - self.horizontalLayout.setObjectName(u"horizontalLayout") + self.horizontalLayout.setObjectName("horizontalLayout") self.horizontalLayout.setContentsMargins(4, 0, 1, 1) self.tank_logo = QLabel(self.top_group) - self.tank_logo.setObjectName(u"tank_logo") + self.tank_logo.setObjectName("tank_logo") sizePolicy = QSizePolicy(QSizePolicy.Maximum, QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.tank_logo.sizePolicy().hasHeightForWidth()) self.tank_logo.setSizePolicy(sizePolicy) - self.tank_logo.setPixmap(QPixmap(u":/Tank.Platform.Qt/tank_logo.png")) + self.tank_logo.setPixmap(QPixmap(":/Tank.Platform.Qt/tank_logo.png")) self.horizontalLayout.addWidget(self.tank_logo) self.label = QLabel(self.top_group) - self.label.setObjectName(u"label") - self.label.setStyleSheet(u"/* want this stylesheet to apply to the label but not the tooltip */\n" -"QLabel{\n" -" color: white;\n" -" font-size: 20px;\n" -" margin-left: 5px;\n" -" font-family: \"Open Sans\";\n" -" font-style: \"Regular\";\n" -"}") + self.label.setObjectName("label") + self.label.setStyleSheet( + "/* want this stylesheet to apply to the label but not the tooltip */\n" + "QLabel{\n" + " color: white;\n" + " font-size: 20px;\n" + " margin-left: 5px;\n" + ' font-family: "Open Sans";\n' + ' font-style: "Regular";\n' + "}" + ) self.horizontalLayout.addWidget(self.label) self.lbl_context = QLabel(self.top_group) - self.lbl_context.setObjectName(u"lbl_context") - self.lbl_context.setStyleSheet(u"/* want this stylesheet to apply to the label but not the tooltip */\n" -"QLabel {\n" -" color: rgba(250,250,250,180);\n" -" font-size: 11px;\n" -" margin-right: 8px;\n" -" font-family: \"Open Sans\";\n" -" font-style: \"Regular\";\n" -"}\n" -"\n" -"\n" -"") - self.lbl_context.setAlignment(Qt.AlignRight|Qt.AlignTrailing|Qt.AlignVCenter) + self.lbl_context.setObjectName("lbl_context") + self.lbl_context.setStyleSheet( + "/* want this stylesheet to apply to the label but not the tooltip */\n" + "QLabel {\n" + " color: rgba(250,250,250,180);\n" + " font-size: 11px;\n" + " margin-right: 8px;\n" + ' font-family: "Open Sans";\n' + ' font-style: "Regular";\n' + "}\n" + "\n" + "\n" + "" + ) + self.lbl_context.setAlignment( + Qt.AlignRight | Qt.AlignTrailing | Qt.AlignVCenter + ) self.horizontalLayout.addWidget(self.lbl_context) self.details_show = QToolButton(self.top_group) - self.details_show.setObjectName(u"details_show") + self.details_show.setObjectName("details_show") self.details_show.setMinimumSize(QSize(34, 34)) self.details_show.setFocusPolicy(Qt.ClickFocus) - self.details_show.setStyleSheet(u"QToolButton{\n" -"width: 12px;\n" -"height: 20px;\n" -"background-image: url(:/Tank.Platform.Qt/arrow.png);\n" -"border: none;\n" -"background-color: none;\n" -"}\n" -"\n" -"QToolButton:hover{\n" -"background-image: url(:/Tank.Platform.Qt/arrow_hover.png);\n" -"}\n" -"\n" -"QToolButton:pressed{\n" -"background-image: url(:/Tank.Platform.Qt/arrow_pressed.png);\n" -"}\n" -"") + self.details_show.setStyleSheet( + "QToolButton{\n" + "width: 12px;\n" + "height: 20px;\n" + "background-image: url(:/Tank.Platform.Qt/arrow.png);\n" + "border: none;\n" + "background-color: none;\n" + "}\n" + "\n" + "QToolButton:hover{\n" + "background-image: url(:/Tank.Platform.Qt/arrow_hover.png);\n" + "}\n" + "\n" + "QToolButton:pressed{\n" + "background-image: url(:/Tank.Platform.Qt/arrow_pressed.png);\n" + "}\n" + "" + ) self.details_show.setAutoRaise(True) self.horizontalLayout.addWidget(self.details_show) self.details_hide = QToolButton(self.top_group) - self.details_hide.setObjectName(u"details_hide") + self.details_hide.setObjectName("details_hide") self.details_hide.setMinimumSize(QSize(34, 34)) self.details_hide.setFocusPolicy(Qt.ClickFocus) self.details_hide.setVisible(False) - self.details_hide.setStyleSheet(u"QToolButton{\n" -" width: 12px;\n" -" height: 20px;\n" -" background-image: url(:/Tank.Platform.Qt/arrow_flipped.png);\n" -" border: none;\n" -" background-color: none;\n" -" }\n" -"\n" -" QToolButton:hover{\n" -" background-image: url(:/Tank.Platform.Qt/arrow_flipped_hover.png);\n" -" }\n" -"\n" -" QToolButton:pressed{\n" -" background-image: url(:/Tank.Platform.Qt/arrow_flipped_pressed.png);\n" -" }\n" -" ") + self.details_hide.setStyleSheet( + "QToolButton{\n" + " width: 12px;\n" + " height: 20px;\n" + " background-image: url(:/Tank.Platform.Qt/arrow_flipped.png);\n" + " border: none;\n" + " background-color: none;\n" + " }\n" + "\n" + " QToolButton:hover{\n" + " background-image: url(:/Tank.Platform.Qt/arrow_flipped_hover.png);\n" + " }\n" + "\n" + " QToolButton:pressed{\n" + " background-image: url(:/Tank.Platform.Qt/arrow_flipped_pressed.png);\n" + " }\n" + " " + ) self.details_hide.setAutoRaise(True) self.horizontalLayout.addWidget(self.details_hide) @@ -149,79 +164,85 @@ def setupUi(self, TankDialog): self.target = QVBoxLayout() self.target.setSpacing(4) - self.target.setObjectName(u"target") + self.target.setObjectName("target") self.verticalLayout.addLayout(self.target) self.stackedWidget.addWidget(self.page_1) self.page_2 = QWidget() - self.page_2.setObjectName(u"page_2") - self.page_2.setStyleSheet(u"QWidget {\n" -" font-family: \"Open Sans\";\n" -" font-style: \"Regular\";\n" -"}") + self.page_2.setObjectName("page_2") + self.page_2.setStyleSheet( + "QWidget {\n" + ' font-family: "Open Sans";\n' + ' font-style: "Regular";\n' + "}" + ) self.verticalLayout_2 = QVBoxLayout(self.page_2) - self.verticalLayout_2.setObjectName(u"verticalLayout_2") + self.verticalLayout_2.setObjectName("verticalLayout_2") self.verticalLayout_2.setContentsMargins(1, 1, 1, 1) self.page_2_group = QGroupBox(self.page_2) - self.page_2_group.setObjectName(u"page_2_group") + self.page_2_group.setObjectName("page_2_group") self.page_2_group.setMinimumSize(QSize(0, 100)) - self.page_2_group.setStyleSheet(u"QGroupBox {\n" -"margin: 0px;\n" -"}") + self.page_2_group.setStyleSheet("QGroupBox {\n" "margin: 0px;\n" "}") self.horizontalLayout_2 = QHBoxLayout(self.page_2_group) self.horizontalLayout_2.setSpacing(0) - self.horizontalLayout_2.setObjectName(u"horizontalLayout_2") + self.horizontalLayout_2.setObjectName("horizontalLayout_2") self.horizontalLayout_2.setContentsMargins(0, 0, 0, 0) - self.horizontalSpacer = QSpacerItem(145, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) + self.horizontalSpacer = QSpacerItem( + 145, 20, QSizePolicy.Expanding, QSizePolicy.Minimum + ) self.horizontalLayout_2.addItem(self.horizontalSpacer) self.label_3 = QLabel(self.page_2_group) - self.label_3.setObjectName(u"label_3") + self.label_3.setObjectName("label_3") self.label_3.setMinimumSize(QSize(40, 0)) self.label_3.setMaximumSize(QSize(40, 16777215)) self.horizontalLayout_2.addWidget(self.label_3) self.gradient = QGroupBox(self.page_2_group) - self.gradient.setObjectName(u"gradient") + self.gradient.setObjectName("gradient") self.gradient.setMinimumSize(QSize(11, 0)) self.gradient.setMaximumSize(QSize(11, 16777215)) - self.gradient.setStyleSheet(u"#gradient {\n" -"background-image: url(:/Tank.Platform.Qt/gradient.png);\n" -"border: none;\n" -"}") + self.gradient.setStyleSheet( + "#gradient {\n" + "background-image: url(:/Tank.Platform.Qt/gradient.png);\n" + "border: none;\n" + "}" + ) self.horizontalLayout_2.addWidget(self.gradient) self.scrollArea = QScrollArea(self.page_2_group) - self.scrollArea.setObjectName(u"scrollArea") + self.scrollArea.setObjectName("scrollArea") self.scrollArea.setMinimumSize(QSize(400, 0)) self.scrollArea.setMaximumSize(QSize(400, 16777215)) - self.scrollArea.setStyleSheet(u"/*\n" -"All labels inside this scroll area should be 12px font.\n" -"This is to avoid the UI looking different in different app like\n" -"maya and nuke which all use slightly different style sheets.\n" -" */\n" -"QLabel{\n" -" font-size: 11px;\n" -" margin-bottom: 8px\n" -"}\n" -"") + self.scrollArea.setStyleSheet( + "/*\n" + "All labels inside this scroll area should be 12px font.\n" + "This is to avoid the UI looking different in different app like\n" + "maya and nuke which all use slightly different style sheets.\n" + " */\n" + "QLabel{\n" + " font-size: 11px;\n" + " margin-bottom: 8px\n" + "}\n" + "" + ) self.scrollArea.setWidgetResizable(True) self.scrollAreaWidgetContents = QWidget() - self.scrollAreaWidgetContents.setObjectName(u"scrollAreaWidgetContents") + self.scrollAreaWidgetContents.setObjectName("scrollAreaWidgetContents") self.scrollAreaWidgetContents.setGeometry(QRect(0, 0, 398, 550)) self.verticalLayout_4 = QVBoxLayout(self.scrollAreaWidgetContents) - self.verticalLayout_4.setObjectName(u"verticalLayout_4") + self.verticalLayout_4.setObjectName("verticalLayout_4") self.horizontalLayout_4 = QHBoxLayout() - self.horizontalLayout_4.setObjectName(u"horizontalLayout_4") + self.horizontalLayout_4.setObjectName("horizontalLayout_4") self.app_icon = QLabel(self.scrollAreaWidgetContents) - self.app_icon.setObjectName(u"app_icon") + self.app_icon.setObjectName("app_icon") self.app_icon.setMinimumSize(QSize(64, 64)) self.app_icon.setMaximumSize(QSize(64, 64)) - self.app_icon.setPixmap(QPixmap(u":/Tank.Platform.Qt/default_app_icon_256.png")) + self.app_icon.setPixmap(QPixmap(":/Tank.Platform.Qt/default_app_icon_256.png")) self.app_icon.setScaledContents(True) self.app_icon.setAlignment(Qt.AlignCenter) @@ -229,12 +250,11 @@ def setupUi(self, TankDialog): self.verticalLayout_8 = QVBoxLayout() self.verticalLayout_8.setSpacing(1) - self.verticalLayout_8.setObjectName(u"verticalLayout_8") + self.verticalLayout_8.setObjectName("verticalLayout_8") self.app_name = QLabel(self.scrollAreaWidgetContents) - self.app_name.setObjectName(u"app_name") - self.app_name.setStyleSheet(u"font-size: 16px;\n" -"") - self.app_name.setAlignment(Qt.AlignLeading|Qt.AlignLeft|Qt.AlignVCenter) + self.app_name.setObjectName("app_name") + self.app_name.setStyleSheet("font-size: 16px;\n" "") + self.app_name.setAlignment(Qt.AlignLeading | Qt.AlignLeft | Qt.AlignVCenter) self.verticalLayout_8.addWidget(self.app_name) @@ -243,61 +263,66 @@ def setupUi(self, TankDialog): self.verticalLayout_4.addLayout(self.horizontalLayout_4) self.app_description = QLabel(self.scrollAreaWidgetContents) - self.app_description.setObjectName(u"app_description") + self.app_description.setObjectName("app_description") self.app_description.setMaximumSize(QSize(350, 16777215)) self.app_description.setWordWrap(True) self.verticalLayout_4.addWidget(self.app_description) self.app_tech_details = QLabel(self.scrollAreaWidgetContents) - self.app_tech_details.setObjectName(u"app_tech_details") + self.app_tech_details.setObjectName("app_tech_details") sizePolicy1 = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) sizePolicy1.setHorizontalStretch(0) sizePolicy1.setVerticalStretch(0) - sizePolicy1.setHeightForWidth(self.app_tech_details.sizePolicy().hasHeightForWidth()) + sizePolicy1.setHeightForWidth( + self.app_tech_details.sizePolicy().hasHeightForWidth() + ) self.app_tech_details.setSizePolicy(sizePolicy1) self.app_tech_details.setMinimumSize(QSize(0, 22)) self.app_tech_details.setMaximumSize(QSize(16777215, 22)) - self.app_tech_details.setAlignment(Qt.AlignLeading|Qt.AlignLeft|Qt.AlignVCenter) + self.app_tech_details.setAlignment( + Qt.AlignLeading | Qt.AlignLeft | Qt.AlignVCenter + ) self.app_tech_details.setWordWrap(True) self.verticalLayout_4.addWidget(self.app_tech_details) self.horizontalLayout_9 = QHBoxLayout() self.horizontalLayout_9.setSpacing(2) - self.horizontalLayout_9.setObjectName(u"horizontalLayout_9") + self.horizontalLayout_9.setObjectName("horizontalLayout_9") self.btn_documentation = QToolButton(self.scrollAreaWidgetContents) - self.btn_documentation.setObjectName(u"btn_documentation") + self.btn_documentation.setObjectName("btn_documentation") self.horizontalLayout_9.addWidget(self.btn_documentation) self.btn_support = QToolButton(self.scrollAreaWidgetContents) - self.btn_support.setObjectName(u"btn_support") + self.btn_support.setObjectName("btn_support") self.horizontalLayout_9.addWidget(self.btn_support) - self.horizontalSpacer_5 = QSpacerItem(0, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) + self.horizontalSpacer_5 = QSpacerItem( + 0, 20, QSizePolicy.Expanding, QSizePolicy.Minimum + ) self.horizontalLayout_9.addItem(self.horizontalSpacer_5) self.verticalLayout_4.addLayout(self.horizontalLayout_9) self.label_5 = QLabel(self.scrollAreaWidgetContents) - self.label_5.setObjectName(u"label_5") - self.label_5.setStyleSheet(u"font-size: 16px;\n" -"margin-top: 30px;") + self.label_5.setObjectName("label_5") + self.label_5.setStyleSheet("font-size: 16px;\n" "margin-top: 30px;") self.verticalLayout_4.addWidget(self.label_5) self.line = QFrame(self.scrollAreaWidgetContents) - self.line.setObjectName(u"line") + self.line.setObjectName("line") self.line.setFrameShape(QFrame.HLine) self.line.setFrameShadow(QFrame.Sunken) self.verticalLayout_4.addWidget(self.line) self.app_work_area_info = QLabel(self.scrollAreaWidgetContents) - self.app_work_area_info.setObjectName(u"app_work_area_info") + self.app_work_area_info.setObjectName("app_work_area_info") self.app_work_area_info.setMaximumSize(QSize(350, 16777215)) self.app_work_area_info.setWordWrap(True) @@ -305,51 +330,52 @@ def setupUi(self, TankDialog): self.horizontalLayout_10 = QHBoxLayout() self.horizontalLayout_10.setSpacing(2) - self.horizontalLayout_10.setObjectName(u"horizontalLayout_10") + self.horizontalLayout_10.setObjectName("horizontalLayout_10") self.btn_file_system = QToolButton(self.scrollAreaWidgetContents) - self.btn_file_system.setObjectName(u"btn_file_system") + self.btn_file_system.setObjectName("btn_file_system") self.horizontalLayout_10.addWidget(self.btn_file_system) self.btn_shotgun = QToolButton(self.scrollAreaWidgetContents) - self.btn_shotgun.setObjectName(u"btn_shotgun") + self.btn_shotgun.setObjectName("btn_shotgun") self.horizontalLayout_10.addWidget(self.btn_shotgun) - self.horizontalSpacer_6 = QSpacerItem(0, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) + self.horizontalSpacer_6 = QSpacerItem( + 0, 20, QSizePolicy.Expanding, QSizePolicy.Minimum + ) self.horizontalLayout_10.addItem(self.horizontalSpacer_6) self.verticalLayout_4.addLayout(self.horizontalLayout_10) self.app_work_area_info_2 = QLabel(self.scrollAreaWidgetContents) - self.app_work_area_info_2.setObjectName(u"app_work_area_info_2") + self.app_work_area_info_2.setObjectName("app_work_area_info_2") self.app_work_area_info_2.setMaximumSize(QSize(350, 16777215)) self.app_work_area_info_2.setWordWrap(True) self.verticalLayout_4.addWidget(self.app_work_area_info_2) self.btn_reload = QToolButton(self.scrollAreaWidgetContents) - self.btn_reload.setObjectName(u"btn_reload") + self.btn_reload.setObjectName("btn_reload") self.verticalLayout_4.addWidget(self.btn_reload) self.config_header = QLabel(self.scrollAreaWidgetContents) - self.config_header.setObjectName(u"config_header") - self.config_header.setStyleSheet(u"font-size: 16px;\n" -"margin-top: 30px;") + self.config_header.setObjectName("config_header") + self.config_header.setStyleSheet("font-size: 16px;\n" "margin-top: 30px;") self.verticalLayout_4.addWidget(self.config_header) self.config_line = QFrame(self.scrollAreaWidgetContents) - self.config_line.setObjectName(u"config_line") + self.config_line.setObjectName("config_line") self.config_line.setFrameShape(QFrame.HLine) self.config_line.setFrameShadow(QFrame.Sunken) self.verticalLayout_4.addWidget(self.config_line) self.config_label = QLabel(self.scrollAreaWidgetContents) - self.config_label.setObjectName(u"config_label") + self.config_label.setObjectName("config_label") self.config_label.setMaximumSize(QSize(350, 16777215)) self.config_label.setWordWrap(True) @@ -357,11 +383,13 @@ def setupUi(self, TankDialog): self.config_layout = QVBoxLayout() self.config_layout.setSpacing(20) - self.config_layout.setObjectName(u"config_layout") + self.config_layout.setObjectName("config_layout") self.verticalLayout_4.addLayout(self.config_layout) - self.verticalSpacer_2 = QSpacerItem(328, 0, QSizePolicy.Minimum, QSizePolicy.Expanding) + self.verticalSpacer_2 = QSpacerItem( + 328, 0, QSizePolicy.Minimum, QSizePolicy.Expanding + ) self.verticalLayout_4.addItem(self.verticalSpacer_2) @@ -380,41 +408,94 @@ def setupUi(self, TankDialog): self.stackedWidget.setCurrentIndex(0) QMetaObject.connectSlotsByName(TankDialog) + # setupUi def retranslateUi(self, TankDialog): - TankDialog.setWindowTitle(QCoreApplication.translate("TankDialog", u"Dialog", None)) + TankDialog.setWindowTitle( + QCoreApplication.translate("TankDialog", "Dialog", None) + ) self.top_group.setTitle("") self.tank_logo.setText("") - self.label.setText(QCoreApplication.translate("TankDialog", u"TextLabel", None)) -#if QT_CONFIG(tooltip) - self.lbl_context.setToolTip(QCoreApplication.translate("TankDialog", u"foo bar", None)) -#endif // QT_CONFIG(tooltip) - self.lbl_context.setText(QCoreApplication.translate("TankDialog", u"Current Work Area:\n" -"TextLabel", None)) -#if QT_CONFIG(tooltip) - self.details_show.setToolTip(QCoreApplication.translate("TankDialog", u"Click for App Details", None)) -#endif // QT_CONFIG(tooltip) + self.label.setText(QCoreApplication.translate("TankDialog", "TextLabel", None)) + # if QT_CONFIG(tooltip) + self.lbl_context.setToolTip( + QCoreApplication.translate("TankDialog", "foo bar", None) + ) + # endif // QT_CONFIG(tooltip) + self.lbl_context.setText( + QCoreApplication.translate( + "TankDialog", "Current Work Area:\n" "TextLabel", None + ) + ) + # if QT_CONFIG(tooltip) + self.details_show.setToolTip( + QCoreApplication.translate("TankDialog", "Click for App Details", None) + ) + # endif // QT_CONFIG(tooltip) self.details_show.setText("") -#if QT_CONFIG(tooltip) - self.details_hide.setToolTip(QCoreApplication.translate("TankDialog", u"Hide App Details", None)) -#endif // QT_CONFIG(tooltip) + # if QT_CONFIG(tooltip) + self.details_hide.setToolTip( + QCoreApplication.translate("TankDialog", "Hide App Details", None) + ) + # endif // QT_CONFIG(tooltip) self.details_hide.setText("") self.page_2_group.setTitle("") self.label_3.setText("") self.gradient.setTitle("") self.app_icon.setText("") - self.app_name.setText(QCoreApplication.translate("TankDialog", u"Publish And Snapshot", None)) - self.app_description.setText(QCoreApplication.translate("TankDialog", u"Tools to see what is out of date in your scene etc etc.", None)) - self.app_tech_details.setText(QCoreApplication.translate("TankDialog", u"tk-multi-snapshot, v1.2.3", None)) - self.btn_documentation.setText(QCoreApplication.translate("TankDialog", u"Documentation", None)) - self.btn_support.setText(QCoreApplication.translate("TankDialog", u"Help && Support", None)) - self.label_5.setText(QCoreApplication.translate("TankDialog", u"Your Current Work Area", None)) - self.app_work_area_info.setText(QCoreApplication.translate("TankDialog", u"TextLabel", None)) - self.btn_file_system.setText(QCoreApplication.translate("TankDialog", u"Jump to File System", None)) - self.btn_shotgun.setText(QCoreApplication.translate("TankDialog", u"Jump to Flow Production Tracking", None)) - self.app_work_area_info_2.setText(QCoreApplication.translate("TankDialog", u"If you are making changes to configuration or code, use the reload button to quickly load your changes in without having to restart:", None)) - self.btn_reload.setText(QCoreApplication.translate("TankDialog", u"Reload Engine and Apps", None)) - self.config_header.setText(QCoreApplication.translate("TankDialog", u"Configuration", None)) - self.config_label.setText(QCoreApplication.translate("TankDialog", u"Below is a list of all the configuration settings for this app, as defined in your environment file:", None)) + self.app_name.setText( + QCoreApplication.translate("TankDialog", "Publish And Snapshot", None) + ) + self.app_description.setText( + QCoreApplication.translate( + "TankDialog", + "Tools to see what is out of date in your scene etc etc.", + None, + ) + ) + self.app_tech_details.setText( + QCoreApplication.translate("TankDialog", "tk-multi-snapshot, v1.2.3", None) + ) + self.btn_documentation.setText( + QCoreApplication.translate("TankDialog", "Documentation", None) + ) + self.btn_support.setText( + QCoreApplication.translate("TankDialog", "Help && Support", None) + ) + self.label_5.setText( + QCoreApplication.translate("TankDialog", "Your Current Work Area", None) + ) + self.app_work_area_info.setText( + QCoreApplication.translate("TankDialog", "TextLabel", None) + ) + self.btn_file_system.setText( + QCoreApplication.translate("TankDialog", "Jump to File System", None) + ) + self.btn_shotgun.setText( + QCoreApplication.translate( + "TankDialog", "Jump to Flow Production Tracking", None + ) + ) + self.app_work_area_info_2.setText( + QCoreApplication.translate( + "TankDialog", + "If you are making changes to configuration or code, use the reload button to quickly load your changes in without having to restart:", + None, + ) + ) + self.btn_reload.setText( + QCoreApplication.translate("TankDialog", "Reload Engine and Apps", None) + ) + self.config_header.setText( + QCoreApplication.translate("TankDialog", "Configuration", None) + ) + self.config_label.setText( + QCoreApplication.translate( + "TankDialog", + "Below is a list of all the configuration settings for this app, as defined in your environment file:", + None, + ) + ) + # retranslateUi diff --git a/python/tk-core/python/tank/platform/software_launcher.py b/python/tk-core/python/tank/platform/software_launcher.py index 39764c4e..96340d05 100644 --- a/python/tk-core/python/tank/platform/software_launcher.py +++ b/python/tk-core/python/tank/platform/software_launcher.py @@ -13,20 +13,18 @@ should implement. """ -import os -import sys import glob +import os import pprint +import sys from ..errors import TankError from ..log import LogManager +from ..util import ShotgunPath, is_windows +from ..util import sgre as re from ..util.loader import load_plugin from ..util.version import is_version_older -from ..util import ShotgunPath, is_windows, sgre as re - -from . import constants -from . import validation - +from . import constants, validation from .bundle import resolve_setting_value from .engine import get_env_and_descriptor_for_engine @@ -79,9 +77,7 @@ def create_engine_launcher(tk, context, engine_name, versions=None, products=Non on disk. """ # Get the engine environment and descriptor using engine.py code - (env, engine_descriptor) = get_env_and_descriptor_for_engine( - engine_name, tk, context - ) + env, engine_descriptor = get_env_and_descriptor_for_engine(engine_name, tk, context) # Make sure it exists locally if not engine_descriptor.exists_local(): diff --git a/python/tk-core/python/tank/platform/util.py b/python/tk-core/python/tank/platform/util.py index 903d918b..be642720 100644 --- a/python/tk-core/python/tank/platform/util.py +++ b/python/tk-core/python/tank/platform/util.py @@ -10,11 +10,11 @@ import logging -from .import_stack import ImportStack from ..errors import TankError -from .errors import TankContextChangeNotSupportedError, TankCurrentModuleNotFoundError -from .engine import current_engine, _restart_engine from ..log import LogManager +from .engine import _restart_engine, current_engine +from .errors import TankContextChangeNotSupportedError, TankCurrentModuleNotFoundError +from .import_stack import ImportStack def _get_current_bundle(): diff --git a/python/tk-core/python/tank/platform/validation.py b/python/tk-core/python/tank/platform/validation.py index e4a219fb..fb73ceed 100644 --- a/python/tk-core/python/tank/platform/validation.py +++ b/python/tk-core/python/tank/platform/validation.py @@ -12,15 +12,16 @@ App configuration and schema validation. """ + import os import sys -from . import constants from ..errors import TankError, TankNoDefaultValueError +from ..log import LogManager from ..template import TemplateString +from ..util.version import is_version_number, is_version_older +from . import constants from .bundle import resolve_default_value -from ..util.version import is_version_older, is_version_number -from ..log import LogManager # We're potentially running here in an environment with # no engine available via current_engine(), so we'll have @@ -354,7 +355,7 @@ def __validate_schema_type(self, settings_key, data_type): % (settings_key, self._display_name) ) - if not data_type in constants.TANK_SCHEMA_VALID_TYPES: + if data_type not in constants.TANK_SCHEMA_VALID_TYPES: params = (data_type, settings_key, self._display_name) raise TankError("Invalid type '%s' in schema '%s' for '%s'!" % params) @@ -406,14 +407,14 @@ def __validate_schema_value(self, settings_key, schema): def __validate_schema_list(self, settings_key, schema): # Check that the schema contains "values" - if not "values" in schema or type(schema["values"]) != dict: + if "values" not in schema or not isinstance(schema["values"], dict): params = (settings_key, self._display_name) raise TankError( "Missing or invalid 'values' dict in schema '%s' for '%s'!" % params ) # If there's an "allows_empty" key, it should be a bool - if "allows_empty" in schema and type(schema["allows_empty"]) != bool: + if "allows_empty" in schema and not isinstance(schema["allows_empty"], bool): params = (settings_key, self._display_name) raise TankError( "Invalid 'allows_empty' bool in schema '%s' for '%s'!" % params @@ -423,13 +424,13 @@ def __validate_schema_list(self, settings_key, schema): def __validate_schema_dict(self, settings_key, schema): # Check that if the schema contains "items" then it must be a dict - if "items" in schema and type(schema["items"]) != dict: + if "items" in schema and not isinstance(schema["items"], dict): params = (settings_key, self._display_name) raise TankError("Invalid 'items' dict in schema '%s' for '%s'!" % params) for key, value_schema in schema.get("items", {}).items(): # Check that the value is a dict, and validate it... - if type(value_schema) != dict: + if not isinstance(value_schema, dict): params = (key, settings_key, self._display_name) raise TankError("Invalid '%s' dict in schema '%s' for '%s'" % params) @@ -438,19 +439,21 @@ def __validate_schema_dict(self, settings_key, schema): def __validate_schema_template(self, settings_key, schema): # new style template def: if there is a fields key, it should be a str - if "fields" in schema and type(schema["fields"]) != str: + if "fields" in schema and not isinstance(schema["fields"], str): params = (settings_key, self._display_name) raise TankError("Invalid 'fields' string in schema '%s' for '%s'!" % params) # old-style - if there's a required_fields key, it should contain a list of strs. - if "required_fields" in schema and type(schema["required_fields"]) != list: + if "required_fields" in schema and not isinstance( + schema["required_fields"], list + ): params = (settings_key, self._display_name) raise TankError( "Invalid 'required_fields' list in schema '%s' for '%s'!" % params ) for field in schema.get("required_fields", []): - if type(field) != str: + if not isinstance(field, str): params = (field, settings_key, self._display_name) raise TankError( "Invalid 'required_fields' value '%s' in schema '%s' for '%s'!" @@ -458,9 +461,9 @@ def __validate_schema_template(self, settings_key, schema): ) # old-style - if there's an optional_fields key, it should contain a list of strs or be "*" - if "optional_fields" in schema and type(schema["optional_fields"]) == list: + if "optional_fields" in schema and isinstance(schema["optional_fields"], list): for field in schema.get("optional_fields", []): - if type(field) != str: + if not isinstance(field, str): params = (field, settings_key, self._display_name) raise TankError( "Invalid 'optional_fields' value '%s' in schema '%s' for '%s'!" @@ -473,7 +476,7 @@ def __validate_schema_template(self, settings_key, schema): ) # If there's an "allows_empty" key, it should be a bool - if "allows_empty" in schema and type(schema["allows_empty"]) != bool: + if "allows_empty" in schema and not isinstance(schema["allows_empty"], bool): params = (settings_key, self._display_name) raise TankError( "Invalid 'allows_empty' bool in schema '%s' for '%s'!" % params @@ -542,7 +545,7 @@ def __validate_settings_value(self, settings_key, schema, value): data_type = schema.get("type") # functor values which refer to a hook are never validated - if type(value) == str and value.startswith("hook:"): + if isinstance(value, str) and value.startswith("hook:"): return # shotgun filters can be a variety of formats so assume it is @@ -604,7 +607,7 @@ def __validate_settings_dict(self, settings_key, schema, value): items = schema.get("items", {}) for key, value_schema in items.items(): # Check for required keys - if not key in value: + if key not in value: params = (key, settings_key, self._display_name) raise TankError( "Missing required key '%s' in setting '%s' for '%s'" % params @@ -890,7 +893,7 @@ def __validate_new_style_template(self, cur_template, fields_str): # one or more mandatory issues. No point checking further return problems - if star == True: + if star: # means an open ended number of fields can be used. # no need to do more validation return problems diff --git a/python/tk-core/python/tank/template.py b/python/tk-core/python/tank/template.py index d5f41f04..5a76a62e 100644 --- a/python/tk-core/python/tank/template.py +++ b/python/tk-core/python/tank/template.py @@ -16,11 +16,12 @@ import os import sys -from . import templatekey +from tank.util import is_linux, is_macos, is_windows +from tank.util import sgre as re + +from . import constants, templatekey from .errors import TankError -from . import constants from .template_path_parser import TemplatePathParser -from tank.util import is_linux, is_macos, is_windows, sgre as re class Template(object): @@ -474,7 +475,7 @@ def validate(self, path, fields=None, skip_keys=None): :returns: True if the path is valid for this template :rtype: Bool """ - return self.validate_and_get_fields(path, fields, skip_keys) != None + return self.validate_and_get_fields(path, fields, skip_keys) is not None def get_fields(self, input_path, skip_keys=None): """ @@ -503,7 +504,7 @@ def get_fields(self, input_path, skip_keys=None): for ordered_keys, static_tokens in zip(self._ordered_keys, self._static_tokens): path_parser = TemplatePathParser(ordered_keys, static_tokens) fields = path_parser.parse_path(input_path, skip_keys) - if fields != None: + if fields is not None: break if fields is None: diff --git a/python/tk-core/python/tank/template_includes.py b/python/tk-core/python/tank/template_includes.py index 18e3e082..dc4d7a1c 100644 --- a/python/tk-core/python/tank/template_includes.py +++ b/python/tk-core/python/tank/template_includes.py @@ -28,10 +28,8 @@ """ -import os - -from .errors import TankError from . import constants +from .errors import TankError from .util import yaml_cache from .util.includes import resolve_include diff --git a/python/tk-core/python/tank/template_path_parser.py b/python/tk-core/python/tank/template_path_parser.py index f703617a..bab1e1ca 100644 --- a/python/tk-core/python/tank/template_path_parser.py +++ b/python/tk-core/python/tank/template_path_parser.py @@ -13,6 +13,7 @@ """ import os + from .errors import TankError diff --git a/python/tk-core/python/tank/templatekey.py b/python/tk-core/python/tank/templatekey.py index b165eaf3..c3276b9f 100644 --- a/python/tk-core/python/tank/templatekey.py +++ b/python/tk-core/python/tank/templatekey.py @@ -12,9 +12,9 @@ Classes for fields on TemplatePaths and TemplateStrings """ -import sys import collections.abc import datetime + from . import constants from .errors import TankError from .util import sgre as re @@ -514,7 +514,7 @@ def _as_string(self, value): if match is None: # no match. return empty string # validate should prevent this from happening - resolved_value = u"" + resolved_value = "" elif self._subset_format: # we have an explicit format string we want to apply to the match. @@ -1099,9 +1099,7 @@ def validate(self, value): error_msg += "Valid frame specs: %s\n" % str(self._frame_specs) error_msg += "Valid format strings: %s\n" % full_format_strings - if isinstance(value, str) and value.startswith( - self.FRAMESPEC_FORMAT_INDICATOR - ): + if isinstance(value, str) and value.startswith(self.FRAMESPEC_FORMAT_INDICATOR): # FORMAT: YXZ string - check that XYZ is in VALID_FORMAT_STRINGS pattern = self._extract_format_string(value) if pattern in self.VALID_FORMAT_STRINGS: @@ -1110,9 +1108,7 @@ def validate(self, value): self._last_error = error_msg return False - elif isinstance(value, str) and re.match( - self.FLAME_PATTERN_REGEX, value - ): + elif isinstance(value, str) and re.match(self.FLAME_PATTERN_REGEX, value): # value is matching the flame-style sequence pattern # [1234-5678] return True @@ -1131,16 +1127,12 @@ def validate(self, value): def _as_string(self, value): - if isinstance(value, str) and value.startswith( - self.FRAMESPEC_FORMAT_INDICATOR - ): + if isinstance(value, str) and value.startswith(self.FRAMESPEC_FORMAT_INDICATOR): # this is a FORMAT: XYZ - convert it to the proper resolved frame spec pattern = self._extract_format_string(value) return self._resolve_frame_spec(pattern, self.format_spec) - if isinstance(value, str) and re.match( - self.FLAME_PATTERN_REGEX, value - ): + if isinstance(value, str) and re.match(self.FLAME_PATTERN_REGEX, value): # this is a flame style sequence token [1234-56773] return value @@ -1167,9 +1159,7 @@ def _extract_format_string(self, value): """ Returns XYZ given the string "FORMAT: XYZ" """ - if isinstance(value, str) and value.startswith( - self.FRAMESPEC_FORMAT_INDICATOR - ): + if isinstance(value, str) and value.startswith(self.FRAMESPEC_FORMAT_INDICATOR): pattern = value.replace(self.FRAMESPEC_FORMAT_INDICATOR, "").strip() else: # passthrough diff --git a/python/tk-core/python/tank/util/__init__.py b/python/tk-core/python/tank/util/__init__.py index 80a7e284..6b304350 100644 --- a/python/tk-core/python/tank/util/__init__.py +++ b/python/tk-core/python/tank/util/__init__.py @@ -8,51 +8,42 @@ # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights # not expressly granted therein are reserved by Shotgun Software Inc. -from .platforms import is_windows, is_linux, is_macos -from .shotgun import register_publish -from .shotgun import resolve_publish_path -from .shotgun import find_publish -from .shotgun import download_url -from .shotgun import create_event_log_entry -from .shotgun import get_entity_type_display_name -from .shotgun import get_published_file_entity_type +from . import filesystem, json, pickle +from .environment import append_path_to_env_var, prepend_path_to_env_var +from .errors import ( + EnvironmentVariableFileLookupError, + PublishPathNotDefinedError, + PublishPathNotSupported, + PublishResolveError, + ShotgunAttachmentDownloadError, + ShotgunPublishError, + UnresolvableCoreConfigurationError, +) +from .local_file_storage import LocalFileStorageManager +from .login import get_current_user, get_shotgun_user +# DO keep the following two log_user_*_metric to preserve retro +# compatibility and prevent exception in legacy engine code. +from .metrics import EventMetric, log_user_activity_metric, log_user_attribute_metric +from .platforms import is_linux, is_macos, is_windows +from .shotgun import ( + create_event_log_entry, + download_url, + find_publish, + get_entity_type_display_name, + get_published_file_entity_type, + register_publish, + resolve_publish_path, +) +from .shotgun_entity import get_sg_entity_name_field +from .shotgun_path import ShotgunPath +from .storage_roots import StorageRoots +from .user_settings import UserSettings from .version import ( - is_version_older, is_version_newer, - is_version_older_or_equal, is_version_newer_or_equal, + is_version_older, + is_version_older_or_equal, suppress_known_deprecation, version_parse, ) - -from .shotgun_entity import get_sg_entity_name_field - -from .environment import append_path_to_env_var -from .environment import prepend_path_to_env_var - -from .login import get_shotgun_user -from .login import get_current_user - -# DO keep the following two log_user_*_metric to preserve retro -# compatibility and prevent exception in legacy engine code. -from .metrics import log_user_activity_metric -from .metrics import log_user_attribute_metric -from .metrics import EventMetric -from .shotgun_path import ShotgunPath - -from . import filesystem -from . import pickle -from . import json - -from .local_file_storage import LocalFileStorageManager - -from .errors import PublishResolveError -from .errors import UnresolvableCoreConfigurationError, ShotgunAttachmentDownloadError -from .errors import EnvironmentVariableFileLookupError, ShotgunPublishError -from .errors import PublishResolveError -from .errors import PublishPathNotDefinedError, PublishPathNotSupported - -from .user_settings import UserSettings - -from .storage_roots import StorageRoots diff --git a/python/tk-core/python/tank/util/environment.py b/python/tk-core/python/tank/util/environment.py index b572555d..a0cef9b6 100644 --- a/python/tk-core/python/tank/util/environment.py +++ b/python/tk-core/python/tank/util/environment.py @@ -13,6 +13,7 @@ """ import os + from .platforms import is_windows diff --git a/python/tk-core/python/tank/util/filesystem.py b/python/tk-core/python/tank/util/filesystem.py index 6a36d6e0..ab2c1742 100644 --- a/python/tk-core/python/tank/util/filesystem.py +++ b/python/tk-core/python/tank/util/filesystem.py @@ -11,15 +11,15 @@ Utility methods for manipulating files and folders """ +import datetime +import errno +import functools import os import re -import sys -import errno -import stat import shutil -import datetime -import functools +import stat import subprocess +import sys from contextlib import contextmanager from .. import LogManager diff --git a/python/tk-core/python/tank/util/includes.py b/python/tk-core/python/tank/util/includes.py index 1b3708f7..d9c39ede 100644 --- a/python/tk-core/python/tank/util/includes.py +++ b/python/tk-core/python/tank/util/includes.py @@ -8,14 +8,13 @@ # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights # not expressly granted therein are reserved by Shotgun Software Inc. +import ntpath import os -import sys import posixpath -import ntpath -from .shotgun_path import ShotgunPath -from .platforms import is_windows from ..errors import TankError +from .platforms import is_windows +from .shotgun_path import ShotgunPath def _is_abs(path): diff --git a/python/tk-core/python/tank/util/json.py b/python/tk-core/python/tank/util/json.py index 896cd4e7..2d303766 100644 --- a/python/tk-core/python/tank/util/json.py +++ b/python/tk-core/python/tank/util/json.py @@ -14,8 +14,9 @@ # We need to add this to the file or the import json will reimport this # module instead of importing the global json module. from __future__ import absolute_import -import sys + import json +import sys from .unicode import ensure_contains_str diff --git a/python/tk-core/python/tank/util/loader.py b/python/tk-core/python/tank/util/loader.py index 3f81a0be..4b30b84b 100644 --- a/python/tk-core/python/tank/util/loader.py +++ b/python/tk-core/python/tank/util/loader.py @@ -13,13 +13,13 @@ """ -import sys import importlib.util -import traceback import inspect +import sys +import traceback -from ..errors import TankError from .. import LogManager +from ..errors import TankError log = LogManager.get_logger(__name__) @@ -57,8 +57,8 @@ def load_plugin(plugin_file, valid_base_class, alternate_base_classes=None): try: plugin_spec = importlib.util.spec_from_file_location(module_uid, plugin_file) module = importlib.util.module_from_spec(plugin_spec) - plugin_spec.loader.exec_module(module) sys.modules[module.__name__] = module + plugin_spec.loader.exec_module(module) except Exception: # log the full callstack to make sure that whatever the # calling code is doing, this error is logged to help @@ -66,7 +66,7 @@ def load_plugin(plugin_file, valid_base_class, alternate_base_classes=None): log.exception("Cannot load plugin file '%s'" % plugin_file) # dump out the callstack for this one -- to help people get good messages when there is a plugin error - (exc_type, exc_value, exc_traceback) = sys.exc_info() + exc_type, exc_value, exc_traceback = sys.exc_info() message = "" message += ( "Failed to load plugin %s. The following error was reported:\n" diff --git a/python/tk-core/python/tank/util/local_file_storage.py b/python/tk-core/python/tank/util/local_file_storage.py index 6215586e..ff805a6c 100644 --- a/python/tk-core/python/tank/util/local_file_storage.py +++ b/python/tk-core/python/tank/util/local_file_storage.py @@ -57,10 +57,10 @@ class LocalFileStorageManager(object): """ # generation of path structures - (CORE_V17, CORE_V18) = range(2) + CORE_V17, CORE_V18 = range(2) # supported types of paths - (LOGGING, CACHE, PERSISTENT, PREFERENCES) = range(4) + LOGGING, CACHE, PERSISTENT, PREFERENCES = range(4) @classmethod def get_global_root(cls, path_type, generation=CORE_V18): diff --git a/python/tk-core/python/tank/util/login.py b/python/tk-core/python/tank/util/login.py index 442cb377..31e99232 100644 --- a/python/tk-core/python/tank/util/login.py +++ b/python/tk-core/python/tank/util/login.py @@ -14,7 +14,6 @@ """ import os -import sys from . import constants from .platforms import is_windows @@ -34,7 +33,7 @@ def get_login_name(): pwd_entry = pwd.getpwuid(os.geteuid()) return pwd_entry[0] - except: + except Exception: return None diff --git a/python/tk-core/python/tank/util/metrics.py b/python/tk-core/python/tank/util/metrics.py index 022de787..521f183c 100644 --- a/python/tk-core/python/tank/util/metrics.py +++ b/python/tk-core/python/tank/util/metrics.py @@ -72,7 +72,7 @@ def get_darwin_version(cls): # For macOS / OSX we keep only the Major.minor os_version = re.findall(r"\d*\.\d*", raw_version_str)[0] - except: + except Exception: pass return os_version @@ -96,7 +96,7 @@ def get_linux_version(cls): major_version_str = re.findall(r"\d*", raw_version_str)[0] os_version = "%s %s" % (distribution, major_version_str) - except: + except Exception: pass return os_version @@ -116,7 +116,7 @@ def get_windows_version(cls): # as it returns a friendly name e.g: XP, 7, 10 etc. os_version = platform.release() - except: + except Exception: pass return os_version @@ -161,7 +161,7 @@ def get_platform_info(cls): else: os_info["OS"] = "Unsupported system: (%s)" % (system) - except: + except Exception: # On any exception we fallback to default value pass @@ -243,7 +243,7 @@ def log(self, metric, log_once=False): # remember that we've logged this one already self.__logged_metrics.add(metric_identifier) - except: + except Exception: pass finally: self._lock.release() @@ -275,7 +275,7 @@ def get_metrics(self, count=None): # would be nice to be able to pop N from deque. oh well. metrics = [self._queue.popleft() for i in range(0, count)] - except: + except Exception: pass finally: self._lock.release() @@ -434,7 +434,7 @@ def run(self): else: break - except Exception as e: + except Exception: pass finally: # wait, checking for halt event before more processing @@ -790,7 +790,7 @@ def log(cls, group, name, properties=None, log_once=False, bundle=None): from sgtk.platform.util import current_bundle bundle = current_bundle() - except: + except Exception: pass if not bundle: @@ -800,7 +800,7 @@ def log(cls, group, name, properties=None, log_once=False, bundle=None): from ..platform.engine import current_engine bundle = current_engine() - except: + except Exception: # Bailing out trying to guess bundle pass diff --git a/python/tk-core/python/tank/util/metrics_cache.py b/python/tk-core/python/tank/util/metrics_cache.py index 4e5ec77d..9693addd 100644 --- a/python/tk-core/python/tank/util/metrics_cache.py +++ b/python/tk-core/python/tank/util/metrics_cache.py @@ -29,9 +29,8 @@ import json import os -from . import metrics - from .. import LogManager +from . import metrics logger = LogManager.get_logger(__name__) @@ -97,7 +96,7 @@ def consume(): logger.debug("Invalid cached metric format") continue - (args, kwargs) = data + args, kwargs = data if not isinstance(args, list) or not isinstance(kwargs, dict): logger.debug("Invalid cached metric format") continue diff --git a/python/tk-core/python/tank/util/process.py b/python/tk-core/python/tank/util/process.py index 34fd5e86..6fd585e0 100644 --- a/python/tk-core/python/tank/util/process.py +++ b/python/tk-core/python/tank/util/process.py @@ -8,11 +8,11 @@ # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights # not expressly granted therein are reserved by Shotgun Software Inc. -import subprocess import pprint +import subprocess -from .platforms import is_windows from ..log import LogManager +from .platforms import is_windows logger = LogManager.get_logger(__name__) diff --git a/python/tk-core/python/tank/util/pyside2_patcher.py b/python/tk-core/python/tank/util/pyside2_patcher.py index 7ef8ae83..974269fc 100644 --- a/python/tk-core/python/tank/util/pyside2_patcher.py +++ b/python/tk-core/python/tank/util/pyside2_patcher.py @@ -12,10 +12,10 @@ PySide 2 backwards compatibility layer for use with PySide 1 code. """ -import os -import sys import functools +import os import subprocess +import sys import types import warnings import webbrowser @@ -328,7 +328,7 @@ def openUrl(cls, url): # returns False or raises some error. try: return webbrowser.open_new_tab(url.toString().encode("utf-8")) - except: + except Exception: return False @classmethod diff --git a/python/tk-core/python/tank/util/pyside6_patcher.py b/python/tk-core/python/tank/util/pyside6_patcher.py index 5aed21d8..0cef38f0 100644 --- a/python/tk-core/python/tank/util/pyside6_patcher.py +++ b/python/tk-core/python/tank/util/pyside6_patcher.py @@ -91,7 +91,7 @@ def _patch_QTextCodec(cls, QtCore): QTextCodec has been removed in Qt6. Using this class will do nothing. """ - class QTextCodec(): + class QTextCodec: @staticmethod def codecForName(name): return None @@ -135,7 +135,6 @@ def grabWindow(window=0, x=0, y=0, width=-1, height=-1): screen = QtGui.QApplication.primaryScreen() return screen.grabWindow(window, x, y, width, height) - QtGui.QPixmap = QPixmap @classmethod @@ -153,7 +152,6 @@ def pixmap(self, *args, **kwargs): QtGui.QIcon.pixmap = pixmap - @classmethod def _patch_QLabel(cls, QtGui): """ @@ -229,11 +227,11 @@ def emit(screen_index): screens = QtGui.QGuiApplication.screens() screen = screens[screen_index] screen.geometryChanged.emit() - except: + except Exception: pass - original_QScreen_availableGeometry = QtGui.QScreen.availableGeometry + def availableGeometry(self, arg__1=None): """ Patch QScreen to also act as QDesktopWidget. @@ -298,7 +296,9 @@ def _patch_QOpenGLContext(cls, QtGui): def versionFunctions(self, version_profile=None): if version_profile: - return QtGui.QOpenGLVersionFunctionsFactory.get(versionProfile=version_profile, context=self) + return QtGui.QOpenGLVersionFunctionsFactory.get( + versionProfile=version_profile, context=self + ) return QtGui.QOpenGLVersionFunctionsFactory.get(context=self) QtGui.QOpenGLContext.versionFunctions = versionFunctions @@ -352,18 +352,39 @@ def __init__(self, *args, **kwargs): if case_sensitivity is None: original_QRegularExpression.__init__(self, args[0]) else: - if case_sensitivity == original_QRegularExpression.CaseInsensitiveOption: + if ( + case_sensitivity + == original_QRegularExpression.CaseInsensitiveOption + ): opts = original_QRegularExpression.CaseInsensitiveOption else: opts = original_QRegularExpression.NoPatternOption - original_QRegularExpression.__init__(self, args[0], options=opts) + original_QRegularExpression.__init__( + self, args[0], options=opts + ) - self.isEmpty = lambda *args, **kwargs: QRegularExpression.isEmpty(self, *args, **kwargs) - self.indexIn = lambda *args, **kwargs: QRegularExpression.indexIn(self, *args, **kwargs) - self.matchedLength = lambda *args, **kwargs: QRegularExpression.matchedLength(self, *args, **kwargs) - self.setCaseSensitivity = lambda *args, **kwargs: QRegularExpression.setCaseSensitivity(self, *args, **kwargs) - self.pos = lambda *args, **kwargs: QRegularExpression.pos(self, *args, **kwargs) - self.cap = lambda *args, **kwargs: QRegularExpression.cap(self, *args, **kwargs) + self.isEmpty = lambda *args, **kwargs: QRegularExpression.isEmpty( + self, *args, **kwargs + ) + self.indexIn = lambda *args, **kwargs: QRegularExpression.indexIn( + self, *args, **kwargs + ) + self.matchedLength = ( + lambda *args, **kwargs: QRegularExpression.matchedLength( + self, *args, **kwargs + ) + ) + self.setCaseSensitivity = ( + lambda *args, **kwargs: QRegularExpression.setCaseSensitivity( + self, *args, **kwargs + ) + ) + self.pos = lambda *args, **kwargs: QRegularExpression.pos( + self, *args, **kwargs + ) + self.cap = lambda *args, **kwargs: QRegularExpression.cap( + self, *args, **kwargs + ) @staticmethod def isEmpty(re): @@ -426,7 +447,9 @@ def cap(re, n): QtCore.QRegularExpression.isEmpty = QRegularExpression.isEmpty QtCore.QRegularExpression.indexIn = QRegularExpression.indexIn QtCore.QRegularExpression.matchedLength = QRegularExpression.matchedLength - QtCore.QRegularExpression.setCaseSensitivity = QRegularExpression.setCaseSensitivity + QtCore.QRegularExpression.setCaseSensitivity = ( + QRegularExpression.setCaseSensitivity + ) QtCore.QRegularExpression.pos = QRegularExpression.pos # This pattern matching flag is obsolete now. @@ -452,9 +475,9 @@ def flush(): @classmethod def patch( - cls, - QtWebEngineWidgets, - QtWebEngineCore, + cls, + QtWebEngineWidgets, + QtWebEngineCore, ): """ Patch the PySide6 modules, classes and function to conform to the PySide interface. @@ -475,8 +498,8 @@ def patch( from PySide6 import ( QtCore, QtGui, - QtWidgets, QtOpenGL, + QtWidgets, ) # First create new modules to act as the PySide modules @@ -546,8 +569,12 @@ def patch( cls._patch_QRegularExpression(qt_core_shim) qt_core_shim.QRegExp = qt_core_shim.QRegularExpression # Rename RegExp functions to RegularExpression - qt_gui_shim.QSortFilterProxyModel.filterRegExp = qt_gui_shim.QSortFilterProxyModel.filterRegularExpression - qt_gui_shim.QSortFilterProxyModel.setFilterRegExp = qt_gui_shim.QSortFilterProxyModel.setFilterRegularExpression + qt_gui_shim.QSortFilterProxyModel.filterRegExp = ( + qt_gui_shim.QSortFilterProxyModel.filterRegularExpression + ) + qt_gui_shim.QSortFilterProxyModel.setFilterRegExp = ( + qt_gui_shim.QSortFilterProxyModel.setFilterRegularExpression + ) # Patch the QCoreApplication.flush() method to ensure compatibility with code # that expects this method, which is marked as obsolete. @@ -588,7 +615,9 @@ def patch( # The default timeout parameter removed. This param, if given, will be ignored. It will # always timeout after 100 ms # https://doc.qt.io/qt-6/widgets-changes-qt6.html#the-qabstractbutton-class - qt_gui_shim.QAbstractButton.animateClick = lambda self, msec=0: self.animateClick() + qt_gui_shim.QAbstractButton.animateClick = ( + lambda self, msec=0: self.animateClick() + ) # Changes to QFont # https://doc.qt.io/qt-6/gui-changes-qt6.html#the-qfont-class @@ -596,7 +625,9 @@ def patch( qt_gui_shim.QFont.setWeight = qt_gui_shim.QFont.setLegacyWeight # QHeaderView method rename - qt_gui_shim.QHeaderView.setResizeMode = qt_gui_shim.QHeaderView.setSectionResizeMode + qt_gui_shim.QHeaderView.setResizeMode = ( + qt_gui_shim.QHeaderView.setSectionResizeMode + ) # QPainter HighQualityAntialiasing is obsolete. Use Antiasliasing instead. # https://doc.qt.io/qt-5/qpainter.html#RenderHint-enum @@ -609,6 +640,8 @@ def patch( if qt_web_engine_widgets_shim: # QtWwebEngineWidgets qt_web_engine_widgets_shim.QWebEnginePage = QtWebEngineCore.QWebEnginePage - qt_web_engine_widgets_shim.QWebEngineProfile = QtWebEngineCore.QWebEngineProfile + qt_web_engine_widgets_shim.QWebEngineProfile = ( + QtWebEngineCore.QWebEngineProfile + ) return qt_core_shim, qt_gui_shim, qt_web_engine_widgets_shim diff --git a/python/tk-core/python/tank/util/qt_importer.py b/python/tk-core/python/tank/util/qt_importer.py index b0fc282e..297059a3 100644 --- a/python/tk-core/python/tank/util/qt_importer.py +++ b/python/tk-core/python/tank/util/qt_importer.py @@ -148,7 +148,6 @@ def _import_module_by_name(self, parent_module_name, module_name): except Exception as e: logger.debug("Unable to import module '%s': %s", module_name, e) - def _import_pyside2(self): """ This will be called at initialization to discover every PySide 2 modules. @@ -159,8 +158,8 @@ def _import_pyside2(self): # throw an import error which will be handled by the calling code. Note that PySide2 can be # imported even if the Qt binaries are missing, so it's better to try importing QtCore for # testing. - from PySide2 import QtCore import shiboken2 + from PySide2 import QtCore # List of all Qt 5 modules. sub_modules = [ @@ -234,8 +233,9 @@ def _import_pyside2_as_pyside(self): :returns: The (binding name, binding version, modules) tuple. """ import PySide2 - from PySide2 import QtCore, QtGui, QtWidgets import shiboken2 + from PySide2 import QtCore, QtGui, QtWidgets + from .pyside2_patcher import PySide2Patcher QtCore, QtGui = PySide2Patcher.patch(QtCore, QtGui, QtWidgets, PySide2) @@ -269,6 +269,7 @@ def _import_pyside6_as_pyside(self): # pragma: no cover import PySide6 import shiboken6 + from .pyside6_patcher import PySide6Patcher QtWebEngineWidgets, QtWebEngineCore = None, None diff --git a/python/tk-core/python/tank/util/resources/no_preview.jpg b/python/tk-core/python/tank/util/resources/no_preview.jpg deleted file mode 100644 index c523937604004686e8f789d34c887f6277ad96d7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7628 zcmbW630zF;-@wnzG)q%jm==jPYI4QBE$*afp;T`BLJRjssQhDEXhhnKuDMssr9GLZ zl9VWE;ks#3BGINu(qc;7Xr+>RpL5R46nA<5?>q51XU;t5JkM``e%~|qbgmncvNE?c zhcFlnWC8x5xgIFojN!8nf~>8f)er=cAUtLV
zronx4PLt6q4Y|IVGZajB2-q7JhP}NG_sqin5$>T(*ga?$n2*AvdYcvF!Y(KrZE!=t
zZq;Him7a?+rWF3i<@iYC(FR-2lH8On1>ZicJ=R)l*m688BaC~|-a(d^nWys3wk6f%
zQX&1a?V#Nn{Xtu)fh)GfKNoEK%nsyw9ZOZ{DYWQ>s_JU=e!4dYUCy!KKB2C Authentication successful. You can close this window. s?5a!O-oq1O`UchsDs
z`LRq)XdIMyPFq^EwuVxq5(+BYIOR!dVdsdR_bO$}YWqZ28J#_$HUSf$)vE3#)9ifa
zZ}7| g7( X>}~HcMPfGYz5^HXzV(leloMi1
zbTp=>jw)yuRAk7FEr(zsXncZXI~2$mqz}=^(Hlln7_A?W1J#gL$({je4v7ysu=>v;
zu!=KM10bbT6ZHP^Pn|viRdPJwm%wPjP5LXVgyZgr^Beh?Y;|OG>8UG7|KSoO3#YvQ
zA?`-GZ=wQTh9;;yhPuRJ#Y=HIrA}19!mZK!%ISl#my01UH!o+K4}RcSMqwwYqQ+9n
z=IJb*Gk8(>Wpn8~SKrUU`+Wp`fzoe5gtq;lo`iXOTQUqGUGt0UR?v{8p`G6
z0_4vAe!w59s|*Yj+G##qP>WS3b+E
ipopyme#{q1NPE;3W1|(p$8Y~c}M&g5|u{P5nz+m_t;+C0syVm
zaeDd#{l>9mI ebwhLqJr>>GpH)I_2}R<@fpi
z8~Sy2+{phmzwehb;3tQmG0YCjCqTV$S!6P*f+qsMQ-yI_MI{=mar)@C02H!=%hd5|
zviCF`y-GlEV9H4TG$3}911bpceRsK)-JN?H5aa?p0}W!DA0T{rG~MEA@OzxV8Md>k
z3%YL%)*VVK$CgGP=iHt{&MrO;npg|a3oQq=;FDV0@de8h?HWMIXcXnie9Pk|TjD4=
z4^*g;_Kxpk4^*{+bb(p+ETfU|Q~bRyZ4*rR)_X9JvHs&&-gruWq5T0Mw^I6~%-+{n
zhipj>+~u0K@P1lmy5PGN_ce5bWXGEoct>c-{qp|fgg