Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 60 additions & 30 deletions shotgun_api3/lib/mockgun/mockgun.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -220,7 +220,6 @@ def __init__(self,
###################################################################################################
# public API methods


def get_session_token(self):
return "bogus_session_token"

Expand All @@ -245,15 +244,16 @@ 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.
Expand All @@ -271,10 +271,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
Expand Down Expand Up @@ -315,9 +315,14 @@ 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):
Expand Down Expand Up @@ -440,22 +445,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"]:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any reason why this is commented out? maybe make a comment to say why it has been left in?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it's not obvious from the diff, but it was already commented out. I only reformatted it because it was longer than 120 chars. Unfortunately we don't know why it was commented out...

# 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:
Expand All @@ -472,10 +499,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

Expand Down Expand Up @@ -641,6 +674,9 @@ 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.
elif not isinstance(field_value, dict):
raise ShotgunError("Invalid deep query field %s.%s" % (entity_type, field))
Expand All @@ -652,7 +688,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:
Expand Down Expand Up @@ -769,7 +805,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)
Expand All @@ -780,11 +815,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))




14 changes: 12 additions & 2 deletions tests/test_mockgun.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -180,6 +180,16 @@ 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)
self.assertEqual(items[0]["id"], self._project_link["id"])


class TestTextFieldOperators(TestBaseWithExceptionTests):
"""
Expand Down