Source code for python.shotgun_model.util

# Copyright (c) 2013 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 tank
from tank.platform.qt import QtCore, QtGui

import urlparse

# precalculated for performance
HAS_QVARIANT = hasattr(QtCore, "QVariant")
HAS_QSTRING = hasattr(QtCore, "QString")
HAS_QBYTEARRAY = hasattr(QtCore, "QByteArray")


[docs]def get_sg_data(item): """ Helper method. Retrieves the shotgun data associated with the object passed in. The object passed in is typically a QStandardItem or a QModelIndex or any other object which implements a data(ROLE) method signature. :param item: QStandardItem or QModelIndex or similar :returns: Shotgun data or None if no data was associated """ from .shotgun_model import ShotgunModel return get_sanitized_data(item, ShotgunModel.SG_DATA_ROLE)
[docs]def get_sanitized_data(item, role): """ Alternative method to the data() methods offered on QStandardItem and QModelIndex. This helper method ensures that complex data is returned in a correct and consistent fashion. All string data is returned as utf-8 encoded byte streams and complex data structures are returned as python native objects (rather than QVariants). Using this method whenever working with complex model data ensures that the code behaves consistently across pyside and pyqt and is using utf-8 encoded strings rather than unicode. :param item: QStandardItem or QModelIndex or similar :param role: Role identifier to be passed to the item object's data() method. :returns: native python objects """ try: return sanitize_qt(item.data(role)) except AttributeError: return None
[docs]def sanitize_for_qt_model(val): """ Useful when you have shotgun (or other) data and want to prepare it for storage as role data in a model. Qt/pyside/pyqt automatically changes the data to be unicode according to internal rules of its own, sometimes resulting in unicode errors. A safe strategy for storing unicode data inside Qt model roles is therefore to ensure everything is converted to unicode prior to insertion into the model. This method ensures that. All string values will be coonverted to unicode. UTF-8 is assumed for all strings: in: {"a":"aaa", "b": 123, "c": {"x":"y", "z":"aa"}, "d": [ {"x":"y", "z":"aa"} ] } out: {'a': u'aaa', 'c': {'x': u'y', 'z': u'aa'}, 'b': 123, 'd': [{'x': u'y', 'z': u'aa'}]} This method is the counterpart to sanitize_qt() which is the reciprocal of this operation. When working with Qt models and shotgun data, we recommend the following best practices: - when sg data is inserted into a role in model, run it through sanitize_for_qt_model() first - When taking it back out again, run it through sanitize_qt() :param val: value to convert :returns: sanitized data """ if isinstance(val, list): return [sanitize_for_qt_model(d) for d in val] elif isinstance(val, dict): new_val = {} for (k, v) in val.iteritems(): # go through dictionary and convert each value separately new_val[k] = sanitize_for_qt_model(v) return new_val elif isinstance(val, str): return val.decode("UTF-8") # for everything else, just pass through return val
[docs]def sanitize_qt(val): """ Converts a value to a tk friendly and consistent representation. - QVariants are converted to native python structures - QStrings are coverted to utf-8 encoded strs - unicode objets are converted to utf-8 encoded strs :param val: input object :returns: cleaned up data """ # test things in order of probable occurrence for speed if val is None: return None elif isinstance(val, unicode): return val.encode("UTF-8") elif HAS_QSTRING and isinstance(val, QtCore.QString): # convert any QStrings to utf-8 encoded strings # note the cast to str because pyqt returns a QByteArray return str(val.toUtf8()) elif HAS_QBYTEARRAY and isinstance(val, QtCore.QByteArray): # convert byte arrays to strs return str(val) elif HAS_QVARIANT and isinstance(val, QtCore.QVariant): # convert any QVariant to their python native equivalents val = val.toPyObject() # and then sanitize this return sanitize_qt(val) elif isinstance(val, list): return [sanitize_qt(d) for d in val] elif isinstance(val, dict): new_val = {} for (k, v) in val.iteritems(): # both keys and values can be bad safe_key = sanitize_qt(k) safe_val = sanitize_qt(v) new_val[safe_key] = safe_val return new_val # QT Version: 5.9.5 # PySide Version: 5.9.0a1 # The value should be `int` but it is `long`. elif isinstance(val, long): val = int(val) return val else: return val
[docs]def compare_shotgun_data(a, b): """ Compares two shotgun data structures. Both inputs are assumed to contain utf-8 encoded data. :returns: True if a is same as b, false otherwise """ if isinstance(a, dict): # input is a dictionary if isinstance(a, dict) and isinstance(b, dict) and len(a) == len(b): # dicts are symmetrical. Compare items recursively. for a_key in a.keys(): if not compare_shotgun_data(a.get(a_key), b.get(a_key)): return False else: # dicts are misaligned return False elif isinstance(a, list): # input is a list if isinstance(a, list) and isinstance(b, list) and len(a) == len(b): # lists are symmetrical. Compare items recursively. for idx in xrange(len(a)): if not compare_shotgun_data(a[idx], b[idx]): return False else: # list items are misaligned return False # handle thumbnail fields as a special case # thumbnail urls are (typically, there seem to be several standards!) # on the form: # https://sg-media-usor-01.s3.amazonaws.com/xxx/yyy/ # filename.ext?lots_of_authentication_headers # # the query string changes all the times, so when we check if an item # is out of date, omit it. elif (isinstance(a, str) and isinstance(b, str) and a.startswith("http") and b.startswith("http") and ("amazonaws" in a or "AccessKeyId" in a)): # attempt to parse values are urls and eliminate the querystring # compare hostname + path only url_obj_a = urlparse.urlparse(a) url_obj_b = urlparse.urlparse(b) compare_str_a = "%s/%s" % (url_obj_a.netloc, url_obj_a.path) compare_str_b = "%s/%s" % (url_obj_b.netloc, url_obj_b.path) if compare_str_a != compare_str_b: # url has changed return False elif a != b: # compare all other values using simple equality return False return True