Source code for tank.util.shotgun_path

# Copyright (c) 2016 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.

import sys
from .platforms import is_linux, is_macos, is_windows


[docs]class ShotgunPath(object): """ Helper class that handles a path on multiple operating systems. Contains methods to easily cast multi-os path between shotgun and os representations and mappings. The ShotgunPath object automatically sanitizes any path that it is given. When working with local storages in Shotgun, roots are keyed by the tokens ``windows_path``, ``linux_path`` and ``mac_path``. When using ``sys.platform`` in python, you get back ``win32``, ``darwin`` and ``linux2`` depending on platform. This class makes it easy to perform operations and cast between representations and platforms. Usage example:: >>> ShotgunPath.SHOTGUN_PATH_FIELDS ["windows_path", "linux_path", "mac_path"] # construction >>> p = ShotgunPath("C:\\temp", "/tmp", "/tmp") >>> p = ShotgunPath.from_shotgun_dict({ "windows_path": "C:\\temp", "mac_path": None, "linux_path": "/tmp"}) >>> p = ShotgunPath.from_system_dict({ "win32": "C:\\temp", "darwin": None, "linux2": "/tmp"}) >>> p = ShotgunPath.from_current_os_path("/tmp") # access >>> p.macosx None >>> p.windows "C:\\temp" >>> p.linux '/tmp >>> p.current_os '/tmp' # boolean operations >>> if p: print "a path value defined for windows, linux or mac" # equality >>> if p1 == p2: print "paths are same" # multi-platform access >>> p.as_shotgun_dict() { "windows_path": "C:\\temp", "mac_path": None, "linux_path": "/tmp"} >>> p.as_system_dict() { "win32": "C:\\temp", "darwin": None, "linux2": "/tmp"} # descriptor uri conversion >>> p.as_descriptor_uri() 'sgtk:descriptor:path?linux_path=/tmp/foo' # path manipulation >>> p2 = p.join('foo') >>> p2 <Path win:'c:\\temp\\foo', linux:'/tmp/foo', macosx:'/tmp/foo'> """ SHOTGUN_PATH_FIELDS = ["windows_path", "linux_path", "mac_path"] """ A list of the standard path fields used by Shotgun. """
[docs] @staticmethod def get_file_name_from_template(template, platform=sys.platform): """ Returns the complete file name for the current platform based on file name template passed in. :param str template: Template for a file name with a ``%s`` to indicate where the platform name should be inserted. :returns: Path with the OS name substituted in. """ if is_windows(platform): os_name = "Windows" elif is_macos(platform): os_name = "Darwin" elif is_linux(platform): os_name = "Linux" else: raise ValueError( "Cannot resolve file name - unsupported " "os platform '%s'" % platform ) return template % os_name
[docs] @staticmethod def get_shotgun_storage_key(platform=sys.platform): """ Given a ``sys.platform`` constant, resolve a Shotgun storage key Shotgun local storages handle operating systems using the three keys 'windows_path, 'mac_path' and 'linux_path', also defined as ``ShotgunPath.SHOTGUN_PATH_FIELDS`` This method resolves the right key given a std. python sys.platform:: >>> p.get_shotgun_storage_key('win32') 'windows_path' # if running on a mac >>> p.get_shotgun_storage_key() 'mac_path' :param platform: sys.platform style string, e.g 'linux2', 'win32' or 'darwin'. :returns: Shotgun storage path as string. """ if is_windows(platform): return "windows_path" elif is_macos(platform): return "mac_path" elif is_linux(platform): return "linux_path" else: raise ValueError( "Cannot resolve PTR storage - unsupported os platform '%s'" % platform )
[docs] @classmethod def from_shotgun_dict(cls, sg_dict): """ Creates a path from data contained in a std shotgun data dict, containing the paths windows_path, mac_path and linux_path :param sg_dict: Shotgun query resultset with possible keys windows_path, mac_path and linux_path. :return: :class:`ShotgunPath` instance """ windows_path = sg_dict.get("windows_path") linux_path = sg_dict.get("linux_path") macosx_path = sg_dict.get("mac_path") return cls(windows_path, linux_path, macosx_path)
[docs] @classmethod def from_system_dict(cls, system_dict): """ Creates a path from data contained in a dictionary keyed by sys.platform constants. :param system_dict: Dictionary with possible keys win32, darwin and linux2. :return: :class:`ShotgunPath` instance """ windows_path = system_dict.get("win32") linux_path = system_dict.get("linux2") macosx_path = system_dict.get("darwin") return cls(windows_path, linux_path, macosx_path)
[docs] @classmethod def from_current_os_path(cls, path): """ Creates a path object for a path on the current platform only. :param path: Path on the current os platform. :return: :class:`ShotgunPath` instance """ windows_path = None linux_path = None macosx_path = None if is_windows(): windows_path = path elif is_linux(): linux_path = path elif is_macos(): macosx_path = path else: raise ValueError("Unsupported platform '%s'." % sys.platform) return cls(windows_path, linux_path, macosx_path)
[docs] @classmethod def normalize(cls, path): """ Convenience method that normalizes the given path by running it through the :class:`ShotgunPath` normalization logic. ``ShotgunPath.normalize(path)`` is equivalent to executing ``ShotgunPath.from_current_os_path(path).current_os``. Normalization include checking that separators are matching the current operating system, removal of trailing separators and removal of double separators. This is done automatically for all :class:`ShotgunPath`, but sometimes it is useful to just perform the normalization quickly on a local path. :param str path: Local operating system path to normalize :return: Normalized path string. """ return cls.from_current_os_path(path).current_os
def __init__(self, windows_path=None, linux_path=None, macosx_path=None): """ :param windows_path: Path on windows to associate with this path object :param linux_path: Path on linux to associate with this path object :param macosx_path: Path on macosx to associate with this path object """ self._windows_path = self._sanitize_path(windows_path, "\\") self._linux_path = self._sanitize_path(linux_path, "/") self._macosx_path = self._sanitize_path(macosx_path, "/") def __nonzero__(self): """ Checks if one or more of the OSes have a path specified. :returns: True if one or more of the OSes has a path specified. False if all are None. """ # If we're different than an empty path, we're not zero! return True if self.windows or self.linux or self.macosx else False def __bool__(self): """ Checks if one or more of the OSes have a path specified. :returns: True if one or more of the OSes has a path specified. False if all are None. """ # In python 3 __bool__ replaces __nonzero__. For compatiblity we will define # both, and return the result of __nonzero__ here. return self.__nonzero__() def __repr__(self): return "<Path win:'%s', linux:'%s', macosx:'%s'>" % ( self._windows_path, self._linux_path, self._macosx_path, ) def __eq__(self, other): """ Test if this ShotgunPath instance is equal to the other ShotgunPath instance :param other: The other ShotgunPath instance to compare with :returns: True if path is same is other, false otherwise """ if not isinstance(other, ShotgunPath): return NotImplemented return ( self.macosx == other.macosx and self.windows == other.windows and self.linux == other.linux ) def __hash__(self): """ Creates an hash from this ShotgunPath. """ return hash((self.macosx, self.windows, self.linux)) def __ne__(self, other): """ Test if this path is not equal to the given path :param other: Other ShotgunPath instance to compare with :returns: True if self != other, False otherwise """ is_equal = self.__eq__(other) if is_equal is NotImplemented: return NotImplemented return not is_equal def _sanitize_path(self, path, separator): r""" Multi-platform sanitize and clean up of paths. The following modifications will be carried out: None returns None Trailing slashes are removed: 1. /foo/bar - unchanged 2. /foo/bar/ - /foo/bar 3. z:/foo/ - z:\foo 4. z:/ - z:\ 5. z:\ - z:\ 6. \\foo\bar\ - \\foo\bar Double slashes are removed: 1. //foo//bar - /foo/bar 2. \\foo\\bar - \\foo\bar Leading and trailing spaces are removed: 1. " Z:\foo " - "Z:\foo" :param path: the path to clean up :param separator: the os.sep to adjust the path for. / on nix, \ on win. :returns: cleaned up path """ if path is None: return None # ensure there is no white space around the path path = path.strip() # get rid of any slashes at the end # after this step, path value will be "/foo/bar", "c:" or "\\hello" path = path.rstrip("/\\") # add slash for drive letters: c: --> c:/ if len(path) == 2 and path.endswith(":"): path += "/" # and convert to the right separators # after this we have a path with the correct slashes and no end slash local_path = path.replace("\\", separator).replace("/", separator) # now weed out any duplicated slashes. iterate until done while True: new_path = local_path.replace("//", "/") if new_path == local_path: break else: local_path = new_path # for windows, remove duplicated backslashes, except if they are # at the beginning of the path while True: new_path = local_path[0] + local_path[1:].replace("\\\\", "\\") if new_path == local_path: break else: local_path = new_path return local_path def _get_macosx(self): """ The macosx representation of the path """ return self._macosx_path def _set_macosx(self, value): """ The macosx representation of the path """ self._macosx_path = self._sanitize_path(value, "/") macosx = property(_get_macosx, _set_macosx) def _get_windows(self): """ The Windows representation of the path """ return self._windows_path def _set_windows(self, value): """ The Windows representation of the path """ self._windows_path = self._sanitize_path(value, "\\") windows = property(_get_windows, _set_windows) def _get_linux(self): """ The Linux representation of the path """ return self._linux_path def _set_linux(self, value): """ The Windows representation of the path """ self._linux_path = self._sanitize_path(value, "/") linux = property(_get_linux, _set_linux) def _get_current_os(self): """ The path on the current os """ if is_windows(): return self.windows elif is_linux(): return self.linux elif is_macos(): return self.macosx else: raise ValueError("Unsupported platform '%s'." % sys.platform) def _set_current_os(self, value): """ The path on the current os """ # Please note that we're using the property setters to set the path, so they # will be sanitized by the setter. if is_windows(): self.windows = value elif is_linux(): self.linux = value elif is_macos(): self.macosx = value else: raise ValueError("Unsupported platform '%s'." % sys.platform) current_os = property(_get_current_os, _set_current_os)
[docs] def as_shotgun_dict(self, include_empty=True): """ The path as a shotgun dictionary. With ``include_empty`` set to True:: { "windows_path": "C:\\temp", "mac_path": None, "linux_path": "/tmp"} With ``include_empty`` set to False:: { "windows_path": "C:\\temp", "linux_path": "/tmp"} :param include_empty: Controls whether keys should be included for empty path values :return: dictionary of paths keyed by standard shotgun keys. """ d = {} if self._windows_path or include_empty: d["windows_path"] = self._windows_path if self._macosx_path or include_empty: d["mac_path"] = self._macosx_path if self._linux_path or include_empty: d["linux_path"] = self._linux_path return d
[docs] def as_system_dict(self, include_empty=True): """ The path as a dictionary keyed by sys.platform. With ``include_empty`` set to True:: { "win32": "C:\\temp", "darwin": None, "linux2": "/tmp"} With ``include_empty`` set to False:: { "win32": "C:\\temp", "linux2": "/tmp"} :param include_empty: Controls whether keys should be included for empty path values :return: dictionary of paths keyed by sys.platform. """ d = {} if self._windows_path or include_empty: d["win32"] = self._windows_path if self._macosx_path or include_empty: d["darwin"] = self._macosx_path if self._linux_path or include_empty: d["linux2"] = self._linux_path return d
[docs] def as_descriptor_uri(self, for_development=False): """ Translates the path to a descriptor uri. For more information about descriptors, see the :ref:`reference documentation<descriptor>`. This method will either return a dev or a path descriptor uri path string, suitable for use with for example pipeline configurations in Shotgun. :param bool for_development: Set to true for a dev descriptor :returns: Dev or Path descriptor uri string representing the path :raises: ValueError if the path object has no paths defined """ # local import to avoid cycles from ..descriptor import descriptor_dict_to_uri if not self: # no paths defined raise ValueError( "%s does not have any paths defined and " "cannot be converted to a descriptor uri." % self ) # build up dictionary based decriptor descriptor_dict = {} if for_development: descriptor_dict["type"] = "dev" else: descriptor_dict["type"] = "path" # add paths descriptor_dict.update(self.as_shotgun_dict(include_empty=False)) # convert to string based uri return descriptor_dict_to_uri(descriptor_dict)
[docs] def join(self, folder): """ Appends a single folder to the path. :param folder: folder name as sting :returns: :class:`ShotgunPath` object containing the new path """ # get rid of any slashes at the end # so value is "/foo/bar", "c:" or "\\hello" # then append separator and new folder linux_path = ( "%s/%s" % (self._linux_path.rstrip("/\\"), folder) if self._linux_path else None ) macosx_path = ( "%s/%s" % (self._macosx_path.rstrip("/\\"), folder) if self._macosx_path else None ) win_path = ( "%s\\%s" % (self._windows_path.rstrip("/\\"), folder) if self._windows_path else None ) return ShotgunPath(win_path, linux_path, macosx_path)