Source code for tank.descriptor.descriptor_config

# Copyright (c) 2016 Shotgun Software Inc.
# 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 with_statement

import os

from ..errors import TankFileDoesNotExistError
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

log = LogManager.get_logger(__name__)

[docs]class ConfigDescriptor(Descriptor): """ Descriptor that describes a Toolkit Configuration """ def __init__( self, sg_connection, io_descriptor, bundle_cache_root_override, fallback_roots ): """ .. note:: Use the factory method :meth:`create_descriptor` when creating new descriptor objects. :param sg_connection: Connection to the current site. :param io_descriptor: Associated IO descriptor. :param bundle_cache_root_override: Override for root path to where downloaded apps are cached. :param fallback_roots: List of immutable fallback cache locations where apps will be searched for. """ super(ConfigDescriptor, self).__init__(io_descriptor) self._cached_core_descriptor = None self._sg_connection = sg_connection self._bundle_cache_root_override = bundle_cache_root_override self._fallback_roots = fallback_roots self._storage_roots = None @property def associated_core_descriptor(self): """ The descriptor dict or url required for this core or ``None`` if not defined. :returns: Core descriptor dict or uri or ``None`` if not defined """ raise NotImplementedError( "ConfigDescriptor.associated_core_descriptor is not implemented." ) @property def python_interpreter(self): """ Retrieves the Python interpreter for the current platform from the interpreter files. .. note:: Most runtime environments (Nuke, Maya, Houdini, etc.) provide their own python interpreter that needs to used when executing code. This property is useful if the engine you are running (e.g. ``tk-shell``) does not have an explicit interpreter associated. :raises: :class:`TankFileDoesNotExistError` If the interpreter file is missing. :raises: :class:`TankInvalidInterpreterLocationError` If the interpreter can't be found on disk. :returns: Path value stored in the interpreter file. """ raise NotImplementedError( "ConfigDescriptor.python_interpreter is not implemented." ) def resolve_core_descriptor(self): """ Resolves the :class:`CoreDescriptor` from :attr:`ConfigDescriptor.associated_core_descriptor`. :returns: The core descriptor if :attr:`ConfigDescriptor.associated_core_descriptor` is set, ``None`` otherwise. """ if not self.associated_core_descriptor: return None # When resolving the descriptor, we need to take into account that the config folder may be # holding a bundle cache with the core in it, so we're adding it to the list of fallback # roots. config_bundle_cache = os.path.join(self.get_config_folder(), "bundle_cache") if not self._cached_core_descriptor: self._cached_core_descriptor = create_descriptor( self._sg_connection, Descriptor.CORE, self.associated_core_descriptor, self._bundle_cache_root_override, [config_bundle_cache] + self._fallback_roots, resolve_latest=is_descriptor_version_missing( self.associated_core_descriptor ), ) return self._cached_core_descriptor
[docs] def get_associated_core_feature_info(self, feature_name, default_value=None): """ Retrieves information for a given feature in the manifest of the core. The ``default_value`` will be returned in the following cases: - a feature is missing from the manifest - the manifest is empty - the manifest is missing - there is no core associated with this configuration. :param str feature_name: Name of the feature to retrieve from the manifest. :param object default_value: Value to return if the feature is missing. :returns: The value for the feature if present, ``default_value`` otherwise. """ if self.resolve_core_descriptor(): return self.resolve_core_descriptor().get_feature_info( feature_name, default_value ) return default_value
@property def version_constraints(self): """ A dictionary with version constraints. The absence of a key indicates that there is no defined constraint. The following keys can be returned: min_sg, min_core, min_engine and min_desktop :returns: Dictionary with optional keys min_sg, min_core, min_engine and min_desktop """ constraints = {} manifest = self._get_manifest() if manifest.get("requires_shotgun_version") is not None: constraints["min_sg"] = manifest.get("requires_shotgun_version") if manifest.get("requires_core_version") is not None: constraints["min_core"] = manifest.get("requires_core_version") return constraints @property def readme_content(self): """ Associated readme content as a list. If not readme exists, an empty list is returned :returns: list of strings """ readme_content = [] readme_file = os.path.join( self.get_config_folder(), constants.CONFIG_README_FILE ) if os.path.exists(readme_file): with open(readme_file) as fh: for line in fh: readme_content.append(line.strip()) return readme_content
[docs] def associated_core_version_less_than(self, version_str): """ Attempt to determine if the associated core version is less than a given version. Returning True means that the associated core version is less than the given one, however returning False does not guarantee that the associated version is higher, it may also be an indication that a version number couldn't be determined. :param version_str: Version string, e.g. '0.18.123' :returns: true if core version is less, false otherwise """ core_desc = self.associated_core_descriptor result = False if core_desc: # (note: returning None means we are tracking latest version) if isinstance(core_desc, str): # convert to dict core_desc = descriptor_uri_to_dict(core_desc) if core_desc["type"] == "app_store": if is_version_older(core_desc["version"], version_str): result = True return result
def get_config_folder(self): """ Returns the folder in which the configuration files are located. Derived classes need to implement this method or a ``NotImplementedError`` will be raised. :returns: Path to the configuration files folder. """ raise NotImplementedError( "ConfigDescriptor.get_config_folder is not implemented." ) def _get_current_platform_interpreter_file_name(self, install_root): """ Retrieves the path to the interpreter file for a given install root. :param str install_root: This can be the root to a studio install for a core or a pipeline configuration root. :returns: Path for the current platform's interpreter file. :rtype: str """ return ShotgunPath.get_file_name_from_template( os.path.join(install_root, "core", "interpreter_%s.cfg") ) def _find_interpreter_location(self, path): """ Finds the interpreter file in a given ``config`` folder. This is a helper method for derived classes. :param path: Path to a config folder, which traditionally has ``core`` and ``env`` subfolders. :returns: Path to the Python interpreter. """ # Find the interpreter file for the current platform. interpreter_config_file = self._get_current_platform_interpreter_file_name(path) if os.path.exists(interpreter_config_file): with open(interpreter_config_file, "r") as f: path_to_python = os.path.expandvars( if not path_to_python or not os.path.exists(path_to_python): raise TankInvalidInterpreterLocationError( "Cannot find interpreter '%s' defined in " "config file '%s'." % (path_to_python, interpreter_config_file) ) else: return path_to_python else: raise TankFileDoesNotExistError( "No interpreter file for the current platform found at '%s'." % interpreter_config_file ) @property def required_storages(self): """ A list of storage names needed for this config. This may be an empty list if the configuration doesn't make use of the file system. :returns: List of storage names as strings """ # empty list if the described config does not define storage roots if not StorageRoots.file_exists(self.get_config_folder()): return [] return self.storage_roots.required_roots @property def storage_roots(self): """ A ``StorageRoots`` instance for this config descriptor. Returns None if the config does not define any storage roots. """ config_folder = self.get_config_folder() # defer StorageRoots instance creation until requested if not self._storage_roots: self._storage_roots = StorageRoots.from_config(config_folder) return self._storage_roots