ciocore.package_tree
A class to provide available packages as DAG structure. In reality however, the structure is just two levels deep: hosts and plugins.
- DCCs such as Maya and Cinema4D are top-level host packages.
- Renderers and other plugins are children of those hosts.
Methods are provided to traverse the tree to find packages by name, version, platform and so on. If you are writing submission tools there's no need to create a Package tree directly. It is recommended to use the singleton module: ciocore.data.
The only functions you should need from this module are:
PackageTree
Source code in ciocore/package_tree.py
class PackageTree(object):
def __init__(self, packages, *host_products, **kwargs):
"""Build the tree with a list of packages.
Args:
packages (list): List of packages direct from the [Conductor packages endpoint](https://dashboard.conductortech.com/api/v1/ee/packages).
*host_products: Filter the tree to contain only top-level host packages of products specified in this list and their plugins. If there are no host_products specified, and the product keyword is omitted, the tree contains all packages.
Keyword Args:
product (str): Build the tree from versions of a single product and its compatible plugins. Defaults to `None`, in which case the tree is built from all packages. It is an error to specify both host_products and product. If a nonexistent product is given, the PackageTree is empty. By specifying `product`, you can build the object based on a single plugin product.
platforms (set): Build the tree from versions for a specific platform. Defaults to the set `{"linux", "windows"}`.
Raises:
KeyError: An invalid platform was provided.
ValueError: Cannot choose both product and host_products.
Example:
>>> from ciocore import api_client, package_tree
# Request packages as a flat list from Conductor.
>>> packages = api_client.request_software_packages()
# Build tree of dependencies from packages list
>>> pt = package_tree.PackageTree(packages, "cinema4d", "maya-io")
>>> for path in pt.to_path_list():
>>> print(path)
cinema4d 22.118.RB320081 linux
cinema4d 22.118.RB320081 linux/redshift-cinema4d 3.0.43 linux
cinema4d 22.118.RB320081 linux/redshift-cinema4d 3.0.45 linux
maya-io 2022.SP3 linux
"""
platforms = kwargs.get("platforms",PLATFORMS)
product=kwargs.get("product")
unknown_platforms = set(platforms) - PLATFORMS
if unknown_platforms:
raise KeyError("Unrecognized platform {}".format(" ".join(unknown_platforms)))
if product and host_products:
raise ValueError("You cannot choose both product and host_products.")
packages = [_clean_package(p) for p in packages if p["platform"] in platforms]
root_ids = []
if product:
root_ids = [p["package_id"] for p in packages if p["product"] == product]
else:
for p in packages:
if not p["plugin_host_product"]:
if p["product"] in host_products or not host_products:
root_ids.append(p["package_id"])
self._tree = _build_tree(packages, {"children": [], "plugins": root_ids})
def supported_host_names(self):
"""
All host names from the software tree.
These names can be used to populate a dropdown menu. Then a single selection from that menu
can be used to retrieve the complete package in order to generate an environment dictionary
and get package IDs.
Returns:
list(str): Fully qualified DCC hosts of the form: `product version platform`.
Example:
>>> from ciocore import api_client, package_tree
>>> packages = api_client.request_software_packages()
>>> pt = package_tree.PackageTree(packages, product="cinema4d")
>>> pt.supported_host_names()
cinema4d 21.209.RB305619 linux
cinema4d 22.116.RB316423 linux
cinema4d 22.118.RB320081 linux
cinema4d 23.110.RB330286 linux
cinema4d 24.111 linux
cinema4d 24.111 windows
"""
paths = []
for pkg in self._tree["children"]:
paths.append(to_name(pkg))
return sorted(paths)
def supported_plugins(self, host):
"""
Find the plugins that are children of the given DCC host.
The result does not contain platform information since we assume that plugins are compatible with the DCC host that was used to request them.
Args:
host (str): Name of the DCC host, typically one of the entries returned by [supported_host_names()](/package_tree/#ciocore.package_tree.PackageTree.supported_host_names).
Returns:
list(dict): Each entry contains a plugin product and a list of versions.
Example:
>>> from ciocore import api_client, package_tree
>>> packages = api_client.request_software_packages()
>>> pt = package_tree.PackageTree(packages, product="cinema4d")
>>> name = pt.supported_host_names()[0]
>>> pt.supported_plugins(name)
[
{
"plugin": "arnold-cinema4d",
"versions": [
"3.3.2.100",
"3.3.3.0"
]
},
{
"plugin": "redshift-cinema4d",
"versions": [
"2.6.54",
"2.6.56",
"3.0.21",
"3.0.22",
],
},
]
"""
try:
subtree = self.find_by_name(host)
plugin_versions = _to_path_list(subtree)
except TypeError:
return []
if not plugin_versions:
return []
plugin_dict = {}
for plugin, version, _ in [pv.split(" ") for pv in plugin_versions]:
if plugin not in plugin_dict:
plugin_dict[plugin] = []
plugin_dict[plugin].append(version)
# convert to list so it can be sorted
plugins = []
for key in plugin_dict:
plugins.append({"plugin": key, "versions": sorted(plugin_dict[key])})
return sorted(plugins, key=lambda k: k["plugin"])
def find_by_name(self, name, limit=None):
"""
Search the tree for a product with the given name.
Args:
name (str): The name constructed from the package using to_name(). It must be an exact match with product, version, and platform. For example: `maya 2018.0 windows`
Keyword Args:
limit (int): Limit the search depth. Defaults to `None`.
Returns:
object: The package that matches.
Example:
>>> from ciocore import api_client, package_tree
>>> packages = api_client.request_software_packages()
>>> pt = package_tree.PackageTree(packages, product="cinema4d")
>>> pt.find_by_name("redshift-cinema4d 3.0.64 linux")
{
'platform': 'linux',
'plugin_host_product': 'cinema4d',
'product': 'redshift-cinema4d',
'major_version': '3',
'release_version': '64',
'vendor': 'maxon',
'children': [],
...
}
"""
return _find_by_name(self._tree, name, limit, 0)
def find_by_path(self, path):
"""
Find the package uniquely described by the given path.
The path is of the form returned by the to_path_list() method.
Args:
path (str): The path
Returns:
object: The package or None if no package exists with the given path.
Example:
>>> from ciocore import api_client, package_tree, package_environment
>>> packages = api_client.request_software_packages()
>>> pt = package_tree.PackageTree(packages, product="cinema4d")
>>> pt.find_by_path("cinema4d 24.111 linux/redshift-cinema4d 3.0.62 linux")
{
'platform': 'linux',
'plugin_host_product': 'cinema4d',
'product': 'redshift-cinema4d',
'major_version': '3',
'release_version': '62',
'vendor': 'maxon',
'children': [],
'plugin_host_version': "24",
...
}
"""
return _find_by_path(self._tree, path)
def to_path_list(self, name=None):
"""
Get paths to all nodes.
This is useful for populating a chooser to choose packages fully qualified by path.
Houdini's tree widget, for example, takes the below format unchanged and generates the
appropriate UI.
Args:
name (str): Get paths below the tree represented by the name. Defaults to None (root node).
Returns:
list(str): Paths to all nodes in the tree.
Example:
>>> from ciocore import api_client, package_tree
>>> packages = api_client.request_software_packages()
>>> pt = package_tree.PackageTree(packages, product="cinema4d")
>>> pt.to_path_list()
cinema4d 22.118.RB320081 linux
cinema4d 22.118.RB320081 linux/redshift-cinema4d 3.0.43 linux
cinema4d 22.118.RB320081 linux/redshift-cinema4d 3.0.45 linux
cinema4d 22.118.RB320081 linux/redshift-cinema4d 3.0.22 linux
cinema4d 22.118.RB320081 linux/arnold-cinema4d 3.3.2.100 linux
...
>>> pt.to_path_list(name="cinema4d 24.111 linux")
redshift-cinema4d 3.0.57 linux
redshift-cinema4d 3.0.62 linux
redshift-cinema4d 3.0.45 linux
redshift-cinema4d 3.0.64 linux
"""
if name:
subtree = self.find_by_name(name)
return _to_path_list(subtree)
return _to_path_list(self._tree)
def platforms(self):
"""
Get the platforms represented by packages in the tree.
Returns:
set: The set of platforms.
"""
# No need to recurse. Plugins are assumed to be compatible with the host.
return set([host["platform"] for host in self._tree["children"]])
def json(self):
"""
The whole tree of softwware as json.
Returns:
str: JSON.
"""
return json.dumps(self._tree)
def __bool__(self):
return True if self._tree["children"] else False
def __nonzero__(self):
# Python 2.7
return self.__bool__()
def as_dict(self):
"""
Returns:
dict: The underlying software dictionary.
"""
return self._tree
__init__(self, packages, *host_products, **kwargs)
special
Build the tree with a list of packages.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
packages |
list |
List of packages direct from the Conductor packages endpoint. |
required |
*host_products |
Filter the tree to contain only top-level host packages of products specified in this list and their plugins. If there are no host_products specified, and the product keyword is omitted, the tree contains all packages. |
() |
Keyword arguments:
Name | Type | Description |
---|---|---|
product |
str |
Build the tree from versions of a single product and its compatible plugins. Defaults to |
platforms |
set |
Build the tree from versions for a specific platform. Defaults to the set |
Exceptions:
Type | Description |
---|---|
KeyError |
An invalid platform was provided. |
ValueError |
Cannot choose both product and host_products. |
Examples:
>>> from ciocore import api_client, package_tree
# Request packages as a flat list from Conductor.
>>> packages = api_client.request_software_packages()
# Build tree of dependencies from packages list
>>> pt = package_tree.PackageTree(packages, "cinema4d", "maya-io")
>>> for path in pt.to_path_list():
>>> print(path)
cinema4d 22.118.RB320081 linux
cinema4d 22.118.RB320081 linux/redshift-cinema4d 3.0.43 linux
cinema4d 22.118.RB320081 linux/redshift-cinema4d 3.0.45 linux
maya-io 2022.SP3 linux
Source code in ciocore/package_tree.py
def __init__(self, packages, *host_products, **kwargs):
"""Build the tree with a list of packages.
Args:
packages (list): List of packages direct from the [Conductor packages endpoint](https://dashboard.conductortech.com/api/v1/ee/packages).
*host_products: Filter the tree to contain only top-level host packages of products specified in this list and their plugins. If there are no host_products specified, and the product keyword is omitted, the tree contains all packages.
Keyword Args:
product (str): Build the tree from versions of a single product and its compatible plugins. Defaults to `None`, in which case the tree is built from all packages. It is an error to specify both host_products and product. If a nonexistent product is given, the PackageTree is empty. By specifying `product`, you can build the object based on a single plugin product.
platforms (set): Build the tree from versions for a specific platform. Defaults to the set `{"linux", "windows"}`.
Raises:
KeyError: An invalid platform was provided.
ValueError: Cannot choose both product and host_products.
Example:
>>> from ciocore import api_client, package_tree
# Request packages as a flat list from Conductor.
>>> packages = api_client.request_software_packages()
# Build tree of dependencies from packages list
>>> pt = package_tree.PackageTree(packages, "cinema4d", "maya-io")
>>> for path in pt.to_path_list():
>>> print(path)
cinema4d 22.118.RB320081 linux
cinema4d 22.118.RB320081 linux/redshift-cinema4d 3.0.43 linux
cinema4d 22.118.RB320081 linux/redshift-cinema4d 3.0.45 linux
maya-io 2022.SP3 linux
"""
platforms = kwargs.get("platforms",PLATFORMS)
product=kwargs.get("product")
unknown_platforms = set(platforms) - PLATFORMS
if unknown_platforms:
raise KeyError("Unrecognized platform {}".format(" ".join(unknown_platforms)))
if product and host_products:
raise ValueError("You cannot choose both product and host_products.")
packages = [_clean_package(p) for p in packages if p["platform"] in platforms]
root_ids = []
if product:
root_ids = [p["package_id"] for p in packages if p["product"] == product]
else:
for p in packages:
if not p["plugin_host_product"]:
if p["product"] in host_products or not host_products:
root_ids.append(p["package_id"])
self._tree = _build_tree(packages, {"children": [], "plugins": root_ids})
supported_host_names(self)
All host names from the software tree.
These names can be used to populate a dropdown menu. Then a single selection from that menu can be used to retrieve the complete package in order to generate an environment dictionary and get package IDs.
Returns:
Type | Description |
---|---|
list(str) |
Fully qualified DCC hosts of the form: |
Examples:
>>> from ciocore import api_client, package_tree
>>> packages = api_client.request_software_packages()
>>> pt = package_tree.PackageTree(packages, product="cinema4d")
>>> pt.supported_host_names()
cinema4d 21.209.RB305619 linux
cinema4d 22.116.RB316423 linux
cinema4d 22.118.RB320081 linux
cinema4d 23.110.RB330286 linux
cinema4d 24.111 linux
cinema4d 24.111 windows
Source code in ciocore/package_tree.py
def supported_host_names(self):
"""
All host names from the software tree.
These names can be used to populate a dropdown menu. Then a single selection from that menu
can be used to retrieve the complete package in order to generate an environment dictionary
and get package IDs.
Returns:
list(str): Fully qualified DCC hosts of the form: `product version platform`.
Example:
>>> from ciocore import api_client, package_tree
>>> packages = api_client.request_software_packages()
>>> pt = package_tree.PackageTree(packages, product="cinema4d")
>>> pt.supported_host_names()
cinema4d 21.209.RB305619 linux
cinema4d 22.116.RB316423 linux
cinema4d 22.118.RB320081 linux
cinema4d 23.110.RB330286 linux
cinema4d 24.111 linux
cinema4d 24.111 windows
"""
paths = []
for pkg in self._tree["children"]:
paths.append(to_name(pkg))
return sorted(paths)
supported_plugins(self, host)
Find the plugins that are children of the given DCC host.
The result does not contain platform information since we assume that plugins are compatible with the DCC host that was used to request them.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
host |
str |
Name of the DCC host, typically one of the entries returned by supported_host_names(). |
required |
Returns:
Type | Description |
---|---|
list(dict) |
Each entry contains a plugin product and a list of versions. |
Examples:
>>> from ciocore import api_client, package_tree
>>> packages = api_client.request_software_packages()
>>> pt = package_tree.PackageTree(packages, product="cinema4d")
>>> name = pt.supported_host_names()[0]
>>> pt.supported_plugins(name)
[
{
"plugin": "arnold-cinema4d",
"versions": [
"3.3.2.100",
"3.3.3.0"
]
},
{
"plugin": "redshift-cinema4d",
"versions": [
"2.6.54",
"2.6.56",
"3.0.21",
"3.0.22",
],
},
]
Source code in ciocore/package_tree.py
def supported_plugins(self, host):
"""
Find the plugins that are children of the given DCC host.
The result does not contain platform information since we assume that plugins are compatible with the DCC host that was used to request them.
Args:
host (str): Name of the DCC host, typically one of the entries returned by [supported_host_names()](/package_tree/#ciocore.package_tree.PackageTree.supported_host_names).
Returns:
list(dict): Each entry contains a plugin product and a list of versions.
Example:
>>> from ciocore import api_client, package_tree
>>> packages = api_client.request_software_packages()
>>> pt = package_tree.PackageTree(packages, product="cinema4d")
>>> name = pt.supported_host_names()[0]
>>> pt.supported_plugins(name)
[
{
"plugin": "arnold-cinema4d",
"versions": [
"3.3.2.100",
"3.3.3.0"
]
},
{
"plugin": "redshift-cinema4d",
"versions": [
"2.6.54",
"2.6.56",
"3.0.21",
"3.0.22",
],
},
]
"""
try:
subtree = self.find_by_name(host)
plugin_versions = _to_path_list(subtree)
except TypeError:
return []
if not plugin_versions:
return []
plugin_dict = {}
for plugin, version, _ in [pv.split(" ") for pv in plugin_versions]:
if plugin not in plugin_dict:
plugin_dict[plugin] = []
plugin_dict[plugin].append(version)
# convert to list so it can be sorted
plugins = []
for key in plugin_dict:
plugins.append({"plugin": key, "versions": sorted(plugin_dict[key])})
return sorted(plugins, key=lambda k: k["plugin"])
find_by_name(self, name, limit=None)
Search the tree for a product with the given name.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
name |
str |
The name constructed from the package using to_name(). It must be an exact match with product, version, and platform. For example: |
required |
Keyword arguments:
Name | Type | Description |
---|---|---|
limit |
int |
Limit the search depth. Defaults to |
Returns:
Type | Description |
---|---|
object |
The package that matches. |
Examples:
>>> from ciocore import api_client, package_tree
>>> packages = api_client.request_software_packages()
>>> pt = package_tree.PackageTree(packages, product="cinema4d")
>>> pt.find_by_name("redshift-cinema4d 3.0.64 linux")
{
'platform': 'linux',
'plugin_host_product': 'cinema4d',
'product': 'redshift-cinema4d',
'major_version': '3',
'release_version': '64',
'vendor': 'maxon',
'children': [],
...
}
Source code in ciocore/package_tree.py
def find_by_name(self, name, limit=None):
"""
Search the tree for a product with the given name.
Args:
name (str): The name constructed from the package using to_name(). It must be an exact match with product, version, and platform. For example: `maya 2018.0 windows`
Keyword Args:
limit (int): Limit the search depth. Defaults to `None`.
Returns:
object: The package that matches.
Example:
>>> from ciocore import api_client, package_tree
>>> packages = api_client.request_software_packages()
>>> pt = package_tree.PackageTree(packages, product="cinema4d")
>>> pt.find_by_name("redshift-cinema4d 3.0.64 linux")
{
'platform': 'linux',
'plugin_host_product': 'cinema4d',
'product': 'redshift-cinema4d',
'major_version': '3',
'release_version': '64',
'vendor': 'maxon',
'children': [],
...
}
"""
return _find_by_name(self._tree, name, limit, 0)
find_by_path(self, path)
Find the package uniquely described by the given path.
The path is of the form returned by the to_path_list() method.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
path |
str |
The path |
required |
Returns:
Type | Description |
---|---|
object |
The package or None if no package exists with the given path. |
Examples:
>>> from ciocore import api_client, package_tree, package_environment
>>> packages = api_client.request_software_packages()
>>> pt = package_tree.PackageTree(packages, product="cinema4d")
>>> pt.find_by_path("cinema4d 24.111 linux/redshift-cinema4d 3.0.62 linux")
{
'platform': 'linux',
'plugin_host_product': 'cinema4d',
'product': 'redshift-cinema4d',
'major_version': '3',
'release_version': '62',
'vendor': 'maxon',
'children': [],
'plugin_host_version': "24",
...
}
Source code in ciocore/package_tree.py
def find_by_path(self, path):
"""
Find the package uniquely described by the given path.
The path is of the form returned by the to_path_list() method.
Args:
path (str): The path
Returns:
object: The package or None if no package exists with the given path.
Example:
>>> from ciocore import api_client, package_tree, package_environment
>>> packages = api_client.request_software_packages()
>>> pt = package_tree.PackageTree(packages, product="cinema4d")
>>> pt.find_by_path("cinema4d 24.111 linux/redshift-cinema4d 3.0.62 linux")
{
'platform': 'linux',
'plugin_host_product': 'cinema4d',
'product': 'redshift-cinema4d',
'major_version': '3',
'release_version': '62',
'vendor': 'maxon',
'children': [],
'plugin_host_version': "24",
...
}
"""
return _find_by_path(self._tree, path)
to_path_list(self, name=None)
Get paths to all nodes.
This is useful for populating a chooser to choose packages fully qualified by path. Houdini's tree widget, for example, takes the below format unchanged and generates the appropriate UI.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
name |
str |
Get paths below the tree represented by the name. Defaults to None (root node). |
None |
Returns:
Type | Description |
---|---|
list(str) |
Paths to all nodes in the tree. |
Examples:
>>> from ciocore import api_client, package_tree
>>> packages = api_client.request_software_packages()
>>> pt = package_tree.PackageTree(packages, product="cinema4d")
>>> pt.to_path_list()
cinema4d 22.118.RB320081 linux
cinema4d 22.118.RB320081 linux/redshift-cinema4d 3.0.43 linux
cinema4d 22.118.RB320081 linux/redshift-cinema4d 3.0.45 linux
cinema4d 22.118.RB320081 linux/redshift-cinema4d 3.0.22 linux
cinema4d 22.118.RB320081 linux/arnold-cinema4d 3.3.2.100 linux
...
>>> pt.to_path_list(name="cinema4d 24.111 linux")
redshift-cinema4d 3.0.57 linux
redshift-cinema4d 3.0.62 linux
redshift-cinema4d 3.0.45 linux
redshift-cinema4d 3.0.64 linux
Source code in ciocore/package_tree.py
def to_path_list(self, name=None):
"""
Get paths to all nodes.
This is useful for populating a chooser to choose packages fully qualified by path.
Houdini's tree widget, for example, takes the below format unchanged and generates the
appropriate UI.
Args:
name (str): Get paths below the tree represented by the name. Defaults to None (root node).
Returns:
list(str): Paths to all nodes in the tree.
Example:
>>> from ciocore import api_client, package_tree
>>> packages = api_client.request_software_packages()
>>> pt = package_tree.PackageTree(packages, product="cinema4d")
>>> pt.to_path_list()
cinema4d 22.118.RB320081 linux
cinema4d 22.118.RB320081 linux/redshift-cinema4d 3.0.43 linux
cinema4d 22.118.RB320081 linux/redshift-cinema4d 3.0.45 linux
cinema4d 22.118.RB320081 linux/redshift-cinema4d 3.0.22 linux
cinema4d 22.118.RB320081 linux/arnold-cinema4d 3.3.2.100 linux
...
>>> pt.to_path_list(name="cinema4d 24.111 linux")
redshift-cinema4d 3.0.57 linux
redshift-cinema4d 3.0.62 linux
redshift-cinema4d 3.0.45 linux
redshift-cinema4d 3.0.64 linux
"""
if name:
subtree = self.find_by_name(name)
return _to_path_list(subtree)
return _to_path_list(self._tree)
platforms(self)
Get the platforms represented by packages in the tree.
Returns:
Type | Description |
---|---|
set |
The set of platforms. |
Source code in ciocore/package_tree.py
def platforms(self):
"""
Get the platforms represented by packages in the tree.
Returns:
set: The set of platforms.
"""
# No need to recurse. Plugins are assumed to be compatible with the host.
return set([host["platform"] for host in self._tree["children"]])
json(self)
The whole tree of softwware as json.
Returns:
Type | Description |
---|---|
str |
JSON. |
Source code in ciocore/package_tree.py
def json(self):
"""
The whole tree of softwware as json.
Returns:
str: JSON.
"""
return json.dumps(self._tree)
as_dict(self)
Returns:
Type | Description |
---|---|
dict |
The underlying software dictionary. |
Source code in ciocore/package_tree.py
def as_dict(self):
"""
Returns:
dict: The underlying software dictionary.
"""
return self._tree
to_name(pkg)
Generate a name like houdini 16.5.323 linux
or maya 2016.SP3 linux
.
This name is derived from the product and version fields in a package. Note: It is not necessarily possible to go the other way and extract version fields from the name.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
pkg |
object |
An object with product, platform, and all version fields. |
required |
Returns:
Type | Description |
---|---|
str |
The package name. |
Examples:
>>> from ciocore import api_client, package_tree
>>> packages = api_client.request_software_packages()
>>> package_tree.to_name(packages[0])
redshift-maya 3.0.64 linux
Source code in ciocore/package_tree.py
def to_name(pkg):
"""
Generate a name like `houdini 16.5.323 linux` or `maya 2016.SP3 linux`.
This name is derived from the product and version fields in a package. Note: It is not
necessarily possible to go the other way and extract version fields from the name.
Args:
pkg (object): An object with product, platform, and all version fields.
Returns:
str: The package name.
Examples:
>>> from ciocore import api_client, package_tree
>>> packages = api_client.request_software_packages()
>>> package_tree.to_name(packages[0])
redshift-maya 3.0.64 linux
"""
version_parts = [
pkg["major_version"],
pkg["minor_version"],
pkg["release_version"],
pkg["build_version"],
]
version_string = (".").join([p for p in version_parts if p])
if pkg["platform"] not in PLATFORMS:
raise KeyError("Invalid platform: {}".format(pkg["platform"]))
return " ".join(filter(None, [pkg["product"], version_string, pkg["platform"]]))