diff --git a/README.mdown b/README.mdown index 742ae2770..e5676c993 100644 --- a/README.mdown +++ b/README.mdown @@ -23,7 +23,11 @@ Some useful direct links within this section are below: * [Reference: Filter Syntax](https://github.com/shotgunsoftware/python-api/wiki/Reference%3A-Filter-Syntax) ## Changelog -**v3.0.6 - 2010 Jan 25** +**v3.0.7 - 2011 Apr 04** + + + fix: update() method should return a dict object not a list + +**v3.0.6 - 2011 Jan 25** + optimization: don't request paging_info unless required (and server support is available) diff --git a/shotgun_api3.py b/shotgun_api3.py index f8a7641bf..866b2b4ea 100755 --- a/shotgun_api3.py +++ b/shotgun_api3.py @@ -32,7 +32,7 @@ # https://support.shotgunsoftware.com/forums/48807-developer-api-info # --------------------------------------------------------------------------------------------- -__version__ = "3.0.6" +__version__ = "3.0.7" # --------------------------------------------------------------------------------------------- # SUMMARY @@ -51,14 +51,16 @@ - make file fields an http link to the file - add logging functionality - add scrubbing to text data sent to server to make sure it is all valid unicode - - support removing thumbnails / files (can only create or replace them now) """ # --------------------------------------------------------------------------------------------- # CHANGELOG # --------------------------------------------------------------------------------------------- """ -+v3.0.6 - 2010 Jan 25 ++v3.0.7 - 2011 Apr 04 + + fix: update() method should return a dict object not a list + ++v3.0.6 - 2011 Jan 25 + optimization: don't request paging_info unless required (and server support is available) +v3.0.5 - 2010 Dec 20 @@ -240,7 +242,7 @@ def _inject_field_values(self, records): for i,r in enumerate(records): # skip results that aren't entity dictionaries - if type(r) is not dict: + if not hasattr(r, '__getitem__') or not hasattr(r, 'items'): continue # iterate over each item and check each field for possible injection @@ -249,8 +251,9 @@ def _inject_field_values(self, records): if k == 'image' and v: records[i]['image'] = self._get_thumb_url(r['type'], r['id']) - if type(v) == dict and 'link_type' in v and v['link_type'] == 'local' \ - and self.platform and self.local_path_string in r[k]: + if (hasattr(v, '__iter__') and hasattr(v, '__getitem__') and + 'link_type' in v and v['link_type'] == 'local' and + self.platform and self.local_path_string in r[k]): records[i][k]['local_path'] = r[k][self.local_path_string] records[i][k]['url'] = "file://%s" % (r[k]['local_path']) @@ -357,7 +360,7 @@ def find(self, entity_type, filters, fields=None, order=None, filter_operator=No if order == None: order = [] - if type(filters) == type([]): + if hasattr(filters, '__iter__'): new_filters = {} if not filter_operator or filter_operator == "all": new_filters["logical_operator"] = "and" @@ -370,7 +373,7 @@ def find(self, entity_type, filters, fields=None, order=None, filter_operator=No filters = new_filters elif filter_operator: - raise ShotgunError("Deprecated: Use of filter_operator for find() is not valid any more. See the documention on find()") + raise ShotgunError("Deprecated: Use of filter_operator for find() is not valid in this context. See the documention on find()") if retired_only: return_only = 'retired' @@ -398,7 +401,7 @@ def find(self, entity_type, filters, fields=None, order=None, filter_operator=No sort['direction'] = 'asc' req['sorts'].append({'field_name': sort['field_name'],'direction' : sort['direction']}) - if type(limit) != int or limit < 0: + if not isinstance(limit, int) or limit < 0: raise ValueError("find() 'limit' parameter must be a positive integer") elif (limit and limit > 0 and limit <= self.records_per_page): req["paging"]["entities_per_page"] = limit @@ -411,12 +414,12 @@ def find(self, entity_type, filters, fields=None, order=None, filter_operator=No records = [] # if page is specified, then only return the page of records requested - if type(page) != int or page < 0: + if not isinstance(page, int) or page < 0: raise ValueError("find() 'page' parameter must be a positive integer") elif page != 0: # No paging_info needed, so optimize it out. if self.supports_paging_info: - req["return_paging_info"] = False + req["return_paging_info"] = False req["paging"]["current_page"] = page resp = self._api3.read(req) @@ -459,8 +462,8 @@ def _required_keys(self, message, required_keys, data): raise ShotgunError("%s missing required key: %s. Value was: %s." % (message, ", ".join(missing), data)) def batch(self, requests): - if type(requests) != type([]): - raise ShotgunError("batch() expects a list. Instead was sent a %s"%type(requests)) + if not hasattr(requests, '__iter__'): + raise ShotgunError("batch() expects an iterable. A %s is not iterable." % type(requests)) reqs = [] @@ -549,8 +552,8 @@ def update(self, entity_type, entity_id, data): args["fields"].append( {"field_name":f,"value":v} ) resp = self._api3.update(args) - records = self._inject_field_values([resp["results"]]) - return records + record = self._inject_field_values([resp["results"]])[0] + return record def delete(self, entity_type, entity_id): """ @@ -1488,13 +1491,21 @@ def dumps(self, values): return result def __dump(self, value, write): - try: - f = self.dispatch[type(value)] - except KeyError: - raise TypeError, "cannot marshal %s objects" % type(value) - else: - f(self, value, write) - + # Try to get the dispatch function directly by looking at the type + dispatchFunction = self.dispatch.get(type(value)) + + # If the exact type isn't in the dispatch table, identify subtypes + if dispatchFunction is None: + for dispatchType, dispatchFunction in self.dispatch.items(): + if isinstance(value, dispatchType): + break + else: + # The value isn't an instance of any registered types + raise TypeError, "cannot marshal %s objects" % type(value) + + # We found a function through type lookup or subtype search + dispatchFunction(self, value, write) + def dump_nil (self, value, write): if not self.allow_none: raise TypeError, "cannot marshal None unless allow_none is enabled" @@ -1568,8 +1579,8 @@ def dump_struct(self, value, write, escape=escape): write("\n") for k, v in value.items(): write("\n") - if type(k) is not StringType: - if unicode and type(k) is UnicodeType: + if not isinstance(k, StringType): + if unicode and isinstance(k, UnicodeType): k = k.encode(self.encoding) else: raise TypeError, "dictionary key must be string" @@ -2053,7 +2064,12 @@ def request(self, host, handler, request_body, verbose=0): try: return self.single_request(host, handler, request_body, verbose) except socket.error, e: - if i >= 10 or e.errno not in (errno.ECONNRESET, errno.ECONNABORTED, errno.EPIPE): + # not all socket errors have errno attributes. Specifically, if the socket times out + # the resulting error is a socket.sslerror which does not. + if hasattr(e, 'errno'): + if i >= 10 or e.errno not in (errno.ECONNRESET, errno.ECONNABORTED, errno.EPIPE): + raise + else: raise except httplib.BadStatusLine: #close after we sent request if i >= 10: