From 460b2c6e2413e62289287a6a6871892f7ca875e2 Mon Sep 17 00:00:00 2001 From: Patrick Boucher Date: Mon, 7 Mar 2011 12:11:17 -0500 Subject: [PATCH 1/4] Make type checking more permissive allowing for subclasses and objects that implement expected interfaces. --- shotgun_api3.py | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/shotgun_api3.py b/shotgun_api3.py index f8a7641bf..9ce2e9e55 100755 --- a/shotgun_api3.py +++ b/shotgun_api3.py @@ -240,7 +240,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 +249,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 +358,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" @@ -398,7 +399,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 +412,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 +460,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 = [] @@ -1488,12 +1489,12 @@ 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) + for dispatchType, dispatchFunction in self.dispatch.items(): + if isinstance(value, dispatchType): + dispatchFunction(self, value, write) + break else: - f(self, value, write) + raise TypeError, "cannot marshal %s objects" % type(value) def dump_nil (self, value, write): if not self.allow_none: @@ -1568,8 +1569,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" From 6d1b465a2666baef21d581d1703119b3dea78dbb Mon Sep 17 00:00:00 2001 From: Patrick Boucher Date: Mon, 7 Mar 2011 16:37:37 -0500 Subject: [PATCH 2/4] bugfix: Accelerate Marshaller lookup of type conversion functions and make sure basic types are looked up properly. --- shotgun_api3.py | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/shotgun_api3.py b/shotgun_api3.py index 9ce2e9e55..7ac0a631d 100755 --- a/shotgun_api3.py +++ b/shotgun_api3.py @@ -1489,13 +1489,21 @@ def dumps(self, values): return result def __dump(self, value, write): - for dispatchType, dispatchFunction in self.dispatch.items(): - if isinstance(value, dispatchType): - dispatchFunction(self, value, write) - break - else: - raise TypeError, "cannot marshal %s objects" % type(value) - + # 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" From fdb1f6e0135861fac67918fb6a8889ea511f96b8 Mon Sep 17 00:00:00 2001 From: KP Date: Mon, 4 Apr 2011 17:16:11 -0700 Subject: [PATCH 3/4] fix return value for update() method to return dict instead of list. --- README.mdown | 6 +++++- shotgun_api3.py | 14 ++++++++------ 2 files changed, 13 insertions(+), 7 deletions(-) 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..c57cfe96f 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 @@ -370,7 +372,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' @@ -549,8 +551,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): """ From 1335cb4028b15131d9d1e0fca7e412f4db6252fe Mon Sep 17 00:00:00 2001 From: KP Date: Thu, 21 Apr 2011 19:58:01 -0700 Subject: [PATCH 4/4] fix: check exceptions that don't have an errno attribute to avoid causing an additional exception --- shotgun_api3.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/shotgun_api3.py b/shotgun_api3.py index c57cfe96f..dca9eaef6 100755 --- a/shotgun_api3.py +++ b/shotgun_api3.py @@ -2055,7 +2055,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: