External Configuration Management

Introduction

This module contains classes and methods for doing inspection of external environments. The typical scenario when this is helpful is if you are creating an environment with mixed contexts - for example a Shotgun ‘My Tasks’ tool which lists different items from different projects and presents a list of available commands, apps or similar for each one of them. Building this would require Toolkit to load up the different configurations for each task and introspect them.

The following classes contains a collection of operations useful when you want to inspect and execute across projects and configurations.

The classes are aggressively cached and asynchronous, fetching data in the background and using QT signals to signal when it is available.

Configurations are bootstrapped in separate background processes, thereby ensuring complete stability of the runtime environment - no context switching or core changes take place.

Class ExternalConfigurationLoader

class external_config.ExternalConfigurationLoader(interpreter, engine_name, plugin_id, base_config, bg_task_manager, parent)[source]

Class for loading configurations across contexts.

Signal Interface

Signal configurations_loaded(project_id, configs):

Gets emitted configurations have been loaded for the given project. The parameters passed is the project id and a list of ExternalConfiguration instances. If errors occurred while loading configurations, the error property will be set to a tuple containing the error message and the traceback, in that order.

Signal configurations_changed():

Gets emitted whenever the class has detected a change to the state of shotgun which could invalidate any existing ExternalConfiguration instances. This can be emitted at startup or typically after refresh_shotgun_global_state() has been called. Any implementation which caches ExternalConfiguration instances can use this signal to invalidate their caches.

Initialize the class with the following parameters:

Note

The interpreter needs to support the VFX Platform, e.g be able to import PySide or Pyside2.

Parameters:
  • interpreter (str) – Path to Python interpreter to use.

  • engine_name (str) – Engine to run.

  • plugin_id (str) – Plugin id to use when executing external requests.

  • base_config (str) – Default configuration URI to use if nothing else is provided via Shotgun overrides.

  • bg_task_manager (BackgroundTaskManager) – Background task manager to use for any asynchronous work.

  • parent (QObject) – QT parent object.

shut_down()[source]

Shut down and deallocate.

refresh_shotgun_global_state()[source]

Requests an async refresh. If the State of Shotgun has changed in a way which may affect configurations, this will result in a configurations_changed signal being emitted.

Examples of state changes which may affect configurations are any changes to related pipeline configuration, but also indirect changes such as a change to the list of software entities, since these can implicitly affect the list of commands associated with a project or entity.

property engine_name

The name of the engine associated with this external configuration loader.

property interpreter

The Python interpreter to when bootstrapping and loading external configurations.

property plugin_id

The plugin id which will be used when executing external requests.

property base_config_uri

Configuration URI string to be used when nothing is provided via Shotgun overrides.

property software_hash

Hash string representing the state of the software entity in Shotgun or None if not yet determined.

request_configurations(project_id)[source]

Requests a list of configuration objects for the given project.

Emits a configurations_loaded signal when the configurations have been loaded.

Note

If this method is called multiple times in quick succession, only a single configurations_loaded signal will be emitted, belonging to the last request.

Parameters:

project_id (int) – Project to request configurations for.

blockSignals(self, b: bool) bool
childEvent(self, event: PySide2.QtCore.QChildEvent) None
children(self) List[PySide2.QtCore.QObject]
static connect(arg__1: PySide2.QtCore.QObject, arg__2: bytes, arg__3: Callable, type: PySide2.QtCore.Qt.ConnectionType = PySide2.QtCore.Qt.ConnectionType.AutoConnection) bool
static connect(self, arg__1: bytes, arg__2: Callable, type: PySide2.QtCore.Qt.ConnectionType = PySide2.QtCore.Qt.ConnectionType.AutoConnection) bool
static connect(self, arg__1: bytes, arg__2: PySide2.QtCore.QObject, arg__3: bytes, type: PySide2.QtCore.Qt.ConnectionType = PySide2.QtCore.Qt.ConnectionType.AutoConnection) bool
static connect(self, sender: PySide2.QtCore.QObject, signal: bytes, member: bytes, type: PySide2.QtCore.Qt.ConnectionType = PySide2.QtCore.Qt.ConnectionType.AutoConnection) PySide2.QtCore.QMetaObject.Connection
static connect(sender: PySide2.QtCore.QObject, signal: PySide2.QtCore.QMetaMethod, receiver: PySide2.QtCore.QObject, method: PySide2.QtCore.QMetaMethod, type: PySide2.QtCore.Qt.ConnectionType = PySide2.QtCore.Qt.ConnectionType.AutoConnection) PySide2.QtCore.QMetaObject.Connection
static connect(sender: PySide2.QtCore.QObject, signal: bytes, receiver: PySide2.QtCore.QObject, member: bytes, type: PySide2.QtCore.Qt.ConnectionType = PySide2.QtCore.Qt.ConnectionType.AutoConnection) PySide2.QtCore.QMetaObject.Connection
connectNotify(self, signal: PySide2.QtCore.QMetaMethod) None
customEvent(self, event: PySide2.QtCore.QEvent) None
deleteLater(self) None
static disconnect(arg__1: PySide2.QtCore.QMetaObject.Connection) bool
static disconnect(arg__1: PySide2.QtCore.QObject, arg__2: bytes, arg__3: Callable) bool
static disconnect(self, arg__1: bytes, arg__2: Callable) bool
static disconnect(self, receiver: PySide2.QtCore.QObject, member: Optional[bytes] = None) bool
static disconnect(self, signal: bytes, receiver: PySide2.QtCore.QObject, member: bytes) bool
static disconnect(sender: PySide2.QtCore.QObject, signal: PySide2.QtCore.QMetaMethod, receiver: PySide2.QtCore.QObject, member: PySide2.QtCore.QMetaMethod) bool
static disconnect(sender: PySide2.QtCore.QObject, signal: bytes, receiver: PySide2.QtCore.QObject, member: bytes) bool
disconnectNotify(self, signal: PySide2.QtCore.QMetaMethod) None
dumpObjectInfo(self) None
dumpObjectTree(self) None
dynamicPropertyNames(self) List[PySide2.QtCore.QByteArray]
emit(self, arg__1: bytes, *args: None) bool
event(self, event: PySide2.QtCore.QEvent) bool
eventFilter(self, watched: PySide2.QtCore.QObject, event: PySide2.QtCore.QEvent) bool
findChild(self, arg__1: type, arg__2: str = '') object
findChildren(self, arg__1: type, arg__2: PySide2.QtCore.QRegExp) Iterable
findChildren(self, arg__1: type, arg__2: PySide2.QtCore.QRegularExpression) Iterable
findChildren(self, arg__1: type, arg__2: str = '') Iterable
inherits(self, classname: bytes) bool
installEventFilter(self, filterObj: PySide2.QtCore.QObject) None
isSignalConnected(self, signal: PySide2.QtCore.QMetaMethod) bool
isWidgetType(self) bool
isWindowType(self) bool
killTimer(self, id: int) None
metaObject(self) PySide2.QtCore.QMetaObject
moveToThread(self, thread: PySide2.QtCore.QThread) None
objectName(self) str
parent(self) PySide2.QtCore.QObject
property(self, name: bytes) Any
receivers(self, signal: bytes) int
static registerUserData() int
removeEventFilter(self, obj: PySide2.QtCore.QObject) None
sender(self) PySide2.QtCore.QObject
senderSignalIndex(self) int
setObjectName(self, name: str) None
setParent(self, parent: PySide2.QtCore.QObject) None
setProperty(self, name: bytes, value: Any) bool
signalsBlocked(self) bool
startTimer(self, interval: int, timerType: PySide2.QtCore.Qt.TimerType = PySide2.QtCore.Qt.TimerType.CoarseTimer) int
thread(self) PySide2.QtCore.QThread
timerEvent(self, event: PySide2.QtCore.QTimerEvent) None
tr(self, arg__1: bytes, arg__2: bytes = b'', arg__3: int = -1) str

Class ExternalConfiguration

class external_config.ExternalConfiguration(parent, bg_task_manager, plugin_id, engine_name, interpreter, software_hash, pipeline_config_uri, status=1)[source]

Object wrapping an external pipeline configuration.

Signals

Signal commands_loaded(project_id, config, commands):

Gets emitted after request_commands() has been called and once commands have been loaded for the configuration. The commands parameter contains a list of ExternalCommand instances.

Signal commands_load_failed(project_id, config, reason):

Gets emitted after request_commands() has been called if command loading fails for some reason. The reason string parameter contains a message signfiying why the load failed.

Note

This class is constructed by ExternalConfigurationLoader. Do not construct objects by hand.

Constructor parameters:

Parameters:
  • parent (QObject) – QT parent object.

  • bg_task_manager (BackgroundTaskManager) – Background task manager to use for any asynchronous work.

  • plugin_id (str) – Associated bootstrap plugin id

  • engine_name (str) – Associated engine name

  • interpreter (str) – Associated Python interpreter

  • software_hash (str) – Hash representing the state of the Shotgun software entity

  • pipeline_config_uri (str) – Descriptor URI string for the config

  • status (int) – The status of the configuration. This is defined as a enum value provided by ExternalConfiguration.

property plugin_id

The plugin id associated with the configuration.

property engine_name

The engine name associated with the configuration.

property interpreter

The Python interpreter to use when accessing this configuration

property software_hash

A hash of the state of the software entity associated with this configuration.

property is_primary

Returns True if this is the primary configuration, False if not.

property is_valid

Returns True if this configuration contains valid data that can be used in the current environment, and False if the configuration is inaccessible for some reason.

property status

The current status of the configuration. This will be returned as an enum value provided by ExternalConfiguration.

property pipeline_configuration_id

The associated pipeline configuration id or None if not defined.

property pipeline_configuration_name

The name of the associated pipeline configuration or None if not defined.

property descriptor_uri

The descriptor URI associated with this pipeline configuration.

property tracking_latest

Returns True if this configuration is tracking an external ‘latest version’. This means that we cannot rely on any caches - because a remote process may release a new “latest” version, we cannot know simply by computing a cache key or looking at a local state on disk whether a cached configuration is up to date or not. The only way to determine this is by actually fully resolve the configuration.

Note

External configurations with this property returning True will have their commands memoized; The first call to request_commands() will resolve the associated commands and subsequent requests will simply return that result. In order do perform a new evaluation of the list of associated commands, instantiate a new External Configuration instance.

request_commands(project_id, entity_type, entity_id, link_entity_type, engine_fallback=None)[source]

Request commands for the given shotgun entity.

A commands_loaded signal will be emitted once the commands are available.

Parameters:
  • project_id (int) – Associated project id

  • entity_type (str) – Associated entity type

  • entity_id (int) – Associated entity id. If this is set to None, a best guess for a generic listing will be carried out.

  • link_entity_type (str) – Entity type that the item is linked to. This is typically provided for things such as task, versions or notes, where having different values it per linked type can be beneficial.

  • engine_fallback (str) – If the main engine isn’t available for the given entity id and project, request generate commands for the fallback engine specified. This can be useful in backwards compatibility scenarios.

Raises:

RuntimeError if this configuration’s status does not allow for commands requests.

blockSignals(self, b: bool) bool
childEvent(self, event: PySide2.QtCore.QChildEvent) None
children(self) List[PySide2.QtCore.QObject]
static connect(arg__1: PySide2.QtCore.QObject, arg__2: bytes, arg__3: Callable, type: PySide2.QtCore.Qt.ConnectionType = PySide2.QtCore.Qt.ConnectionType.AutoConnection) bool
static connect(self, arg__1: bytes, arg__2: Callable, type: PySide2.QtCore.Qt.ConnectionType = PySide2.QtCore.Qt.ConnectionType.AutoConnection) bool
static connect(self, arg__1: bytes, arg__2: PySide2.QtCore.QObject, arg__3: bytes, type: PySide2.QtCore.Qt.ConnectionType = PySide2.QtCore.Qt.ConnectionType.AutoConnection) bool
static connect(self, sender: PySide2.QtCore.QObject, signal: bytes, member: bytes, type: PySide2.QtCore.Qt.ConnectionType = PySide2.QtCore.Qt.ConnectionType.AutoConnection) PySide2.QtCore.QMetaObject.Connection
static connect(sender: PySide2.QtCore.QObject, signal: PySide2.QtCore.QMetaMethod, receiver: PySide2.QtCore.QObject, method: PySide2.QtCore.QMetaMethod, type: PySide2.QtCore.Qt.ConnectionType = PySide2.QtCore.Qt.ConnectionType.AutoConnection) PySide2.QtCore.QMetaObject.Connection
static connect(sender: PySide2.QtCore.QObject, signal: bytes, receiver: PySide2.QtCore.QObject, member: bytes, type: PySide2.QtCore.Qt.ConnectionType = PySide2.QtCore.Qt.ConnectionType.AutoConnection) PySide2.QtCore.QMetaObject.Connection
connectNotify(self, signal: PySide2.QtCore.QMetaMethod) None
customEvent(self, event: PySide2.QtCore.QEvent) None
deleteLater(self) None
static disconnect(arg__1: PySide2.QtCore.QMetaObject.Connection) bool
static disconnect(arg__1: PySide2.QtCore.QObject, arg__2: bytes, arg__3: Callable) bool
static disconnect(self, arg__1: bytes, arg__2: Callable) bool
static disconnect(self, receiver: PySide2.QtCore.QObject, member: Optional[bytes] = None) bool
static disconnect(self, signal: bytes, receiver: PySide2.QtCore.QObject, member: bytes) bool
static disconnect(sender: PySide2.QtCore.QObject, signal: PySide2.QtCore.QMetaMethod, receiver: PySide2.QtCore.QObject, member: PySide2.QtCore.QMetaMethod) bool
static disconnect(sender: PySide2.QtCore.QObject, signal: bytes, receiver: PySide2.QtCore.QObject, member: bytes) bool
disconnectNotify(self, signal: PySide2.QtCore.QMetaMethod) None
dumpObjectInfo(self) None
dumpObjectTree(self) None
dynamicPropertyNames(self) List[PySide2.QtCore.QByteArray]
emit(self, arg__1: bytes, *args: None) bool
event(self, event: PySide2.QtCore.QEvent) bool
eventFilter(self, watched: PySide2.QtCore.QObject, event: PySide2.QtCore.QEvent) bool
findChild(self, arg__1: type, arg__2: str = '') object
findChildren(self, arg__1: type, arg__2: PySide2.QtCore.QRegExp) Iterable
findChildren(self, arg__1: type, arg__2: PySide2.QtCore.QRegularExpression) Iterable
findChildren(self, arg__1: type, arg__2: str = '') Iterable
inherits(self, classname: bytes) bool
installEventFilter(self, filterObj: PySide2.QtCore.QObject) None
isSignalConnected(self, signal: PySide2.QtCore.QMetaMethod) bool
isWidgetType(self) bool
isWindowType(self) bool
killTimer(self, id: int) None
metaObject(self) PySide2.QtCore.QMetaObject
moveToThread(self, thread: PySide2.QtCore.QThread) None
objectName(self) str
parent(self) PySide2.QtCore.QObject
property(self, name: bytes) Any
receivers(self, signal: bytes) int
static registerUserData() int
removeEventFilter(self, obj: PySide2.QtCore.QObject) None
sender(self) PySide2.QtCore.QObject
senderSignalIndex(self) int
setObjectName(self, name: str) None
setParent(self, parent: PySide2.QtCore.QObject) None
setProperty(self, name: bytes, value: Any) bool
signalsBlocked(self) bool
startTimer(self, interval: int, timerType: PySide2.QtCore.Qt.TimerType = PySide2.QtCore.Qt.TimerType.CoarseTimer) int
thread(self) PySide2.QtCore.QThread
timerEvent(self, event: PySide2.QtCore.QTimerEvent) None
tr(self, arg__1: bytes, arg__2: bytes = b'', arg__3: int = -1) str

Class ExternalCommand

class external_config.ExternalCommand(callback_name, display_name, tooltip, group, is_group_default, plugin_id, interpreter, engine_name, descriptor_uri, pipeline_config_id, entity_type, entity_id, pipeline_config_name, sg_deny_permissions, sg_supports_multiple_selection, icon)[source]

Represents an external Toolkit command (e.g. menu option).

These objects are emitted by ExternalConfiguration and are independent, decoupled, light weight objects that can be serialized and brought back easily.

A command is executed via its execute() method, which will launch it in the given engine.

Note

This class is constructed by ExternalConfigurationLoader. Do not construct objects by hand.

Parameters:
  • callback_name (str) – Name of the associated Toolkit command callback

  • display_name (str) – Display name for command

  • tooltip (str) – Tooltip

  • group (str) – Group that this command belongs to

  • is_group_default (bool) – Indicates that this is a group default

  • plugin_id (str) – Plugin id

  • interpreter (str) – Associated Python interpreter

  • engine_name (str) – Engine name to execute command in

  • descriptor_uri (str) – Associated descriptor URI

  • pipeline_config_id (int) – Associated pipeline configuration id

  • entity_type (str) – Associated entity type

  • entity_id (int) – Associated entity id

  • pipeline_config_name (str) – Associated pipeline configuration name

  • sg_deny_permissions (list) – (Shotgun specific) List of permission groups to exclude this action from.

  • sg_supports_multiple_selection (bool) – (Shotgun specific) Action supports multiple selection.

  • icon (str) – The path to a square png icon file representing this item

classmethod is_compatible(data)[source]

Determines if the given data is compatible.

Parameters:

data (dict) – Serialized data

Returns:

True if the given data can be loaded, False if not.

classmethod is_valid_data(data)[source]

Tests whether key components of the data contained in the cache are still valid. This helps protect against file paths that might have been cached that no longer exist.

Parameters:

data (dict) – Serialized data

Returns:

True if the given data passes validation, False if not.

classmethod deserialize(data)[source]

Creates a ExternalCommand instance given some serialized data.

Parameters:

data (str) – Data created by serialize()

Returns:

External Command instance.

Return type:

ExternalCommand

Raises:

RuntimeError if data is not valid

serialize()[source]

Serializes the current object into a string.

For use with deserialize().

Returns:

String representing the current instance.

Return type:

str

property pipeline_configuration_name

The name of the Shotgun pipeline configuration this command is associated with, or None if no association exists.

property system_name

The system name for the command

property engine_name

The name of the engine associated with the command

property display_name

Display name, suitable for display in a menu.

property icon

The path to a square png icon file representing this item

property group

Group command belongs to or None if not defined.

This is used in conjunction with the group() property and is a hint to engines how commands should be grouped together.

Engines which implement support for grouping will group commands which share the same group() name into a group of associated items (typically as a submenu). The group_default() boolean property is used to indicate which item in the group should be considered the default one to represent the group as a whole.

property is_group_default

True if this command is a default action for a group.

This is used in conjunction with the group() property and is a hint to engines how commands should be grouped together.

Engines which implement support for grouping will group commands which share the same group() name into a group of associated items (typically as a submenu). The group_default() boolean property is used to indicate which item in the group should be considered the default one to represent the group as a whole.

property excluded_permission_groups_hint

Legacy option used by some older Shotgun toolkit apps. Apps may hint a list of permission groups for which the app command should not be displayed.

Returns a list of Shotgun permission groups (as strings) where this command is not appropriate.

property support_shotgun_multiple_selection

Legacy flag indicated by some older Toolkit apps, indicating that the app can accept a list of entity ids to operate on rather than a single item.

property tooltip

Associated help text tooltip.

property interpreter

The Python interpreter path to use when executing the command.

execute(pre_cache=False)[source]

Executes the external command in a separate process.

Note

The process will be launched in an synchronous way. It is recommended that this command is executed in a worker thread:

# execute external command in a thread to not block
# main thread execution
worker = threading.Thread(target=action.execute)
# if the python environment shuts down, no need
# to wait for this thread
worker.daemon = True
# launch external process
worker.start()
Parameters:

pre_cache (bool) – If set to True, starting up the command will also include a full caching of all necessary dependencies for all contexts and engines. If set to False, caching will only be carried as needed in order to run the given command. This is an advanced setting that can be useful to set to true when launching older engines which don’t launch via a bootstrap process. In that case, the engine simply assumes that all necessary app dependencies already exists in the bundle cache search path and without a pre-cache, apps may not initialize correctly.

Raises:

RuntimeError on execution failure.

Returns:

Output from execution session.

execute_on_multiple_entities(pre_cache=False, entity_ids=None)[source]

Executes the external command in a separate process. This method provides support for executing commands that support being run on multiple entities as part of a single execution.

Parameters:
  • pre_cache (bool) – If set to True, starting up the command will also include a full caching of all necessary dependencies for all contexts and engines. If set to False, caching will only be carried as needed in order to run the given command. This is an advanced setting that can be useful to set to true when launching older engines which don’t launch via a bootstrap process. In that case, the engine simply assumes that all necessary app dependencies already exists in the bundle cache search path and without a pre-cache, apps may not initialize correctly.

  • entity_ids (list) – A list of entity ids to use when executing the command. This is only required when running legacy commands that support being run on multiple entities at the same time. If not given, a list will be built on the fly containing only the entity id associated with this command.

Raises:

RuntimeError on execution failure.

Returns:

Output from execution session.