ciocore.package_tree - Conductor Core

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 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"}.

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: product version platform.

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: maya 2018.0 windows

required

Keyword arguments:

Name Type Description
limit int

Limit the search depth. Defaults to None.

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"]]))