From d06407dd69b847a2fbf862783f3ff85de403ef86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Boismenu?= Date: Fri, 23 Mar 2018 08:25:56 -0400 Subject: [PATCH 1/4] adds support for a None entity link during sub-entity searches. --- shotgun_api3/lib/mockgun/mockgun.py | 2 ++ tests/test_mockgun.py | 13 +++++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/shotgun_api3/lib/mockgun/mockgun.py b/shotgun_api3/lib/mockgun/mockgun.py index 1122c413..170bda23 100644 --- a/shotgun_api3/lib/mockgun/mockgun.py +++ b/shotgun_api3/lib/mockgun/mockgun.py @@ -641,6 +641,8 @@ def _get_field_from_row(self, entity_type, row, field): sub_field_value = self._get_field_from_row(entity_type2, entity, field3) values.append(sub_field_value) return values + elif field_value is None: + return None # not multi entity, must be entity. elif not isinstance(field_value, dict): raise ShotgunError("Invalid deep query field %s.%s" % (entity_type, field)) diff --git a/tests/test_mockgun.py b/tests/test_mockgun.py index 5a5d3411..05380952 100644 --- a/tests/test_mockgun.py +++ b/tests/test_mockgun.py @@ -148,12 +148,12 @@ def setUp(self): """ self._mockgun = Mockgun("https://test.shotgunstudio.com", login="user", password="1234") - self._project_link = self._mockgun.create("Project", {"name": "project"}) + self._project_link = self._mockgun.create("Project", {"name": "project", "archived": False}) # This entity will ensure that a populated link field will be comparable. self._mockgun.create( "PipelineConfiguration", - {"code": "with_project", "project": self._project_link} + {"code": "with_project", "project": self._project_link, } ) # This entity will ensure that an unpopulated link field will be comparable. @@ -180,6 +180,15 @@ def test_searching_for_initialized_entity_field(self): items = self._mockgun.find("PipelineConfiguration", [["project", "is_not", self._project_link]]) self.assertEqual(len(items), 1) + def test_find_entity_with_none_link(self): + """ + Make sure that we can search for sub entity fields on entities that have the field not set. + """ + # The pipeline configuration without_project doesn't have the project field set, so we're expecting + # it to not be returned here. + items = self._mockgun.find("PipelineConfiguration", [["project.Project.archived", "is", False]]) + self.assertEqual(len(items), 1) + class TestTextFieldOperators(TestBaseWithExceptionTests): """ From 46a9f72b594f58ffc22920c885e03d7efa56643d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Boismenu?= Date: Fri, 23 Mar 2018 08:29:25 -0400 Subject: [PATCH 2/4] added extra validation to test. --- tests/test_mockgun.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_mockgun.py b/tests/test_mockgun.py index 05380952..2bd4ae10 100644 --- a/tests/test_mockgun.py +++ b/tests/test_mockgun.py @@ -188,6 +188,7 @@ def test_find_entity_with_none_link(self): # it to not be returned here. items = self._mockgun.find("PipelineConfiguration", [["project.Project.archived", "is", False]]) self.assertEqual(len(items), 1) + self.assertEqual(items[0]["id"], self._project_link["id"]) class TestTextFieldOperators(TestBaseWithExceptionTests): From 36fe675e9770b85a8d9f0a2b25bdab6cf0b48c5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Boismenu?= Date: Fri, 23 Mar 2018 08:36:40 -0400 Subject: [PATCH 3/4] pepification --- shotgun_api3/lib/mockgun/mockgun.py | 90 +++++++++++++++++++---------- 1 file changed, 60 insertions(+), 30 deletions(-) diff --git a/shotgun_api3/lib/mockgun/mockgun.py b/shotgun_api3/lib/mockgun/mockgun.py index 170bda23..caa1413e 100644 --- a/shotgun_api3/lib/mockgun/mockgun.py +++ b/shotgun_api3/lib/mockgun/mockgun.py @@ -116,7 +116,7 @@ import datetime -from ... import sg_timezone, ShotgunError +from ... import ShotgunError from ...shotgun import _Config from .errors import MockgunError from .schema import SchemaFactory @@ -220,7 +220,6 @@ def __init__(self, ################################################################################################### # public API methods - def get_session_token(self): return "bogus_session_token" @@ -245,15 +244,17 @@ def schema_field_read(self, entity_type, field_name=None): else: return dict((k, v) for k, v in self._schema[entity_type].items() if k == field_name) - - def find(self, entity_type, filters, fields=None, order=None, filter_operator=None, limit=0, retired_only=False, page=0): - + def find( + self, + entity_type, filters, fields=None, + order=None, filter_operator=None, limit=0, retired_only=False, page=0 + ): self.finds += 1 self._validate_entity_type(entity_type) # do not validate custom fields - this makes it hard to mock up a field quickly - #self._validate_entity_fields(entity_type, fields) + # self._validate_entity_fields(entity_type, fields) # FIXME: This should be refactored so that we can use the complex filer # style in nested filter operations. @@ -271,10 +272,10 @@ def find(self, entity_type, filters, fields=None, order=None, filter_operator=No if len(f["values"]) != 1: # {'path': 'id', 'relation': 'in', 'values': [1,2,3]} --> ["id", "in", [1,2,3]] - resolved_filters.append([ f["path"], f["relation"], f["values"] ]) + resolved_filters.append([f["path"], f["relation"], f["values"]]) else: # {'path': 'id', 'relation': 'is', 'values': [3]} --> ["id", "is", 3] - resolved_filters.append([ f["path"], f["relation"], f["values"][0] ]) + resolved_filters.append([f["path"], f["relation"], f["values"][0]]) else: # traditiona style sg filters @@ -315,9 +316,15 @@ def find(self, entity_type, filters, fields=None, order=None, filter_operator=No return val - - def find_one(self, entity_type, filters, fields=None, order=None, filter_operator=None, retired_only=False): - results = self.find(entity_type, filters, fields=fields, order=order, filter_operator=filter_operator, retired_only=retired_only) + def find_one( + self, + entity_type, filters, fields=None, + order=None, filter_operator=None, retired_only=False + ): + results = self.find( + entity_type, filters, fields=fields, + order=order, filter_operator=filter_operator, retired_only=retired_only + ) return results[0] if results else None def batch(self, requests): @@ -440,22 +447,44 @@ def _validate_entity_data(self, entity_type, data): if field_info["data_type"]["value"] == "multi_entity": if not isinstance(item, list): - raise ShotgunError("%s.%s is of type multi_entity, but data %s is not a list" % (entity_type, field, item)) + raise ShotgunError( + "%s.%s is of type multi_entity, but data %s is not a list" % + (entity_type, field, item) + ) elif item and any(not isinstance(sub_item, dict) for sub_item in item): - raise ShotgunError("%s.%s is of type multi_entity, but data %s contains a non-dictionary" % (entity_type, field, item)) + raise ShotgunError( + "%s.%s is of type multi_entity, but data %s contains a non-dictionary" % + (entity_type, field, item) + ) elif item and any("id" not in sub_item or "type" not in sub_item for sub_item in item): - raise ShotgunError("%s.%s is of type multi-entity, but an item in data %s does not contain 'type' and 'id'" % (entity_type, field, item)) - elif item and any(sub_item["type"] not in field_info["properties"]["valid_types"]["value"] for sub_item in item): - raise ShotgunError("%s.%s is of multi-type entity, but an item in data %s has an invalid type (expected one of %s)" % (entity_type, field, item, field_info["properties"]["valid_types"]["value"])) - + raise ShotgunError( + "%s.%s is of type multi-entity, but an item in data %s does not contain 'type' and 'id'" % + (entity_type, field, item) + ) + elif item and any( + sub_item["type"] not in field_info["properties"]["valid_types"]["value"] for sub_item in item + ): + raise ShotgunError( + "%s.%s is of multi-type entity, but an item in data %s has an invalid type (expected one of %s)" + % (entity_type, field, item, field_info["properties"]["valid_types"]["value"]) + ) elif field_info["data_type"]["value"] == "entity": if not isinstance(item, dict): - raise ShotgunError("%s.%s is of type entity, but data %s is not a dictionary" % (entity_type, field, item)) + raise ShotgunError( + "%s.%s is of type entity, but data %s is not a dictionary" % + (entity_type, field, item) + ) elif "id" not in item or "type" not in item: - raise ShotgunError("%s.%s is of type entity, but data %s does not contain 'type' and 'id'" % (entity_type, field, item)) - #elif item["type"] not in field_info["properties"]["valid_types"]["value"]: - # raise ShotgunError("%s.%s is of type entity, but data %s has an invalid type (expected one of %s)" % (entity_type, field, item, field_info["properties"]["valid_types"]["value"])) + raise ShotgunError( + "%s.%s is of type entity, but data %s does not contain 'type' and 'id'" + % (entity_type, field, item) + ) + # elif item["type"] not in field_info["properties"]["valid_types"]["value"]: + # raise ShotgunError( + # "%s.%s is of type entity, but data %s has an invalid type (expected one of %s)" % + # (entity_type, field, item, field_info["properties"]["valid_types"]["value"]) + # ) else: try: @@ -472,10 +501,16 @@ def _validate_entity_data(self, entity_type, data): "status_list": basestring, "url": dict}[sg_type] except KeyError: - raise ShotgunError("Field %s.%s: Handling for Shotgun type %s is not implemented" % (entity_type, field, sg_type)) + raise ShotgunError( + "Field %s.%s: Handling for Shotgun type %s is not implemented" % + (entity_type, field, sg_type) + ) if not isinstance(item, python_type): - raise ShotgunError("%s.%s is of type %s, but data %s is not of type %s" % (entity_type, field, type(item), sg_type, python_type)) + raise ShotgunError( + "%s.%s is of type %s, but data %s is not of type %s" % + (entity_type, field, type(item), sg_type, python_type) + ) # TODO: add check for correct timezone @@ -641,6 +676,7 @@ def _get_field_from_row(self, entity_type, row, field): sub_field_value = self._get_field_from_row(entity_type2, entity, field3) values.append(sub_field_value) return values + # The field is not set, so return None. elif field_value is None: return None # not multi entity, must be entity. @@ -654,7 +690,7 @@ def _get_field_from_row(self, entity_type, row, field): # ok so looks like the value is an entity link # e.g. db contains: {"sg_sequence": {"type":"Sequence", "id": 123 } } - linked_row = self._db[ field_value["type"] ][ field_value["id"] ] + linked_row = self._db[field_value["type"]][field_value["id"]] return self._get_field_from_row(entity_type2, linked_row, field3) else: @@ -771,7 +807,6 @@ def _row_matches_filters(self, entity_type, row, filters, filter_operator, retir else: raise ShotgunError("%s is not a valid filter operator" % filter_operator) - def _update_row(self, entity_type, row, data): for field in data: field_type = self._get_field_type(entity_type, field) @@ -782,11 +817,6 @@ def _update_row(self, entity_type, row, data): else: row[field] = data[field] - def _validate_entity_exists(self, entity_type, entity_id): if entity_id not in self._db[entity_type]: raise ShotgunError("No entity of type %s exists with id %s" % (entity_type, entity_id)) - - - - From 51b05a57a7bf10ac9c31e5df60dfc1b611cd6e80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Boismenu?= Date: Fri, 23 Mar 2018 09:18:50 -0400 Subject: [PATCH 4/4] better formatting --- shotgun_api3/lib/mockgun/mockgun.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/shotgun_api3/lib/mockgun/mockgun.py b/shotgun_api3/lib/mockgun/mockgun.py index caa1413e..d3c73c86 100644 --- a/shotgun_api3/lib/mockgun/mockgun.py +++ b/shotgun_api3/lib/mockgun/mockgun.py @@ -245,9 +245,8 @@ def schema_field_read(self, entity_type, field_name=None): return dict((k, v) for k, v in self._schema[entity_type].items() if k == field_name) def find( - self, - entity_type, filters, fields=None, - order=None, filter_operator=None, limit=0, retired_only=False, page=0 + self, entity_type, filters, fields=None, order=None, filter_operator=None, + limit=0, retired_only=False, page=0 ): self.finds += 1 @@ -317,9 +316,8 @@ def find( return val def find_one( - self, - entity_type, filters, fields=None, - order=None, filter_operator=None, retired_only=False + self, entity_type, filters, fields=None, order=None, filter_operator=None, + retired_only=False ): results = self.find( entity_type, filters, fields=fields,