Skip to content

Commit b83b9e2

Browse files
SG-37548 Add payload optimization on update method (#363)
* Include the "in" and "not_in" for payload optimization * Use a decorator to mock environmental variable * Add payload optimization for update action * Change conditional order * Extract function to be reused. Improve testing. * Fix typo * Fix test * Support multi entity, Add tests * Apply CR feedback * Update shotgun_api3/shotgun.py Co-authored-by: Julien Langlois <[email protected]> --------- Co-authored-by: Julien Langlois <[email protected]>
1 parent f0451f5 commit b83b9e2

File tree

2 files changed

+188
-13
lines changed

2 files changed

+188
-13
lines changed

shotgun_api3/shotgun.py

Lines changed: 40 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1132,6 +1132,28 @@ def _add_project_param(self, params, project_entity):
11321132
params["project"] = project_entity
11331133

11341134
return params
1135+
1136+
def _translate_update_params(
1137+
self, entity_type, entity_id, data, multi_entity_update_modes
1138+
):
1139+
global SHOTGUN_API_ENABLE_ENTITY_OPTIMIZATION
1140+
1141+
def optimize_field(field_dict):
1142+
if SHOTGUN_API_ENABLE_ENTITY_OPTIMIZATION:
1143+
return {k: _get_type_and_id_from_value(v) for k, v in field_dict.items()}
1144+
return field_dict
1145+
1146+
full_fields = self._dict_to_list(
1147+
data,
1148+
extra_data=self._dict_to_extra_data(
1149+
multi_entity_update_modes, "multi_entity_update_mode"
1150+
),
1151+
)
1152+
return {
1153+
"type": entity_type,
1154+
"id": entity_id,
1155+
"fields": [optimize_field(field_dict) for field_dict in full_fields],
1156+
}
11351157

11361158
def summarize(self,
11371159
entity_type,
@@ -1463,14 +1485,7 @@ def update(self, entity_type, entity_id, data, multi_entity_update_modes=None):
14631485
upload_filmstrip_image = data.pop("filmstrip_image")
14641486

14651487
if data:
1466-
params = {
1467-
"type": entity_type,
1468-
"id": entity_id,
1469-
"fields": self._dict_to_list(
1470-
data,
1471-
extra_data=self._dict_to_extra_data(
1472-
multi_entity_update_modes, "multi_entity_update_mode"))
1473-
}
1488+
params = self._translate_update_params(entity_type, entity_id, data, multi_entity_update_modes)
14741489
record = self._call_rpc("update", params)
14751490
result = self._parse_records(record)[0]
14761491
else:
@@ -4485,10 +4500,7 @@ def _translate_filters_simple(sg_filter):
44854500
and condition["relation"] in ["is", "is_not", "in", "not_in"]
44864501
and isinstance(values[0], dict)
44874502
):
4488-
try:
4489-
values = [{"type": v["type"], "id": v["id"]} for v in values]
4490-
except KeyError:
4491-
pass
4503+
values = [_get_type_and_id_from_value(v) for v in values]
44924504

44934505
condition["values"] = values
44944506

@@ -4500,3 +4512,19 @@ def _version_str(version):
45004512
Convert a tuple of int's to a '.' separated str.
45014513
"""
45024514
return ".".join(map(str, version))
4515+
4516+
4517+
def _get_type_and_id_from_value(value):
4518+
"""
4519+
For an entity dictionary, returns a new dictionary with only the type and id keys.
4520+
If any of these keys are not present, the original dictionary is returned.
4521+
"""
4522+
try:
4523+
if isinstance(value, dict):
4524+
return {"type": value["type"], "id": value["id"]}
4525+
elif isinstance(value, list):
4526+
return [{"type": v["type"], "id": v["id"]} for v in value]
4527+
except (KeyError, TypeError):
4528+
LOG.debug(f"Could not optimize entity value {value}")
4529+
4530+
return value

tests/test_unit.py

Lines changed: 148 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,8 @@ def test_py_version(self, mock_sys):
290290

291291

292292
class TestFilters(unittest.TestCase):
293+
maxDiff = None
294+
293295
def test_empty(self):
294296
expected = {
295297
"logical_operator": "and",
@@ -463,6 +465,28 @@ def test_related_object_entity_optimization_is(self):
463465
result = api.shotgun._translate_filters(filters, "all")
464466
self.assertEqual(result, expected)
465467

468+
# Now test a non-related object. The expected result should not be optimized.
469+
filters = [
470+
[
471+
"something",
472+
"is",
473+
{"foo": "foo", "bar": "bar"},
474+
],
475+
]
476+
expected = {
477+
"logical_operator": "and",
478+
"conditions": [
479+
{
480+
"path": "something",
481+
"relation": "is",
482+
"values": [{'bar': 'bar', 'foo': 'foo'}],
483+
}
484+
],
485+
}
486+
api.Shotgun("http://server_path", "script_name", "api_key", connect=False)
487+
result = api.shotgun._translate_filters(filters, "all")
488+
self.assertEqual(result, expected)
489+
466490
@mock.patch.dict(os.environ, {"SHOTGUN_API_ENABLE_ENTITY_OPTIMIZATION": "1"})
467491
def test_related_object_entity_optimization_in(self):
468492
filters = [
@@ -471,7 +495,8 @@ def test_related_object_entity_optimization_in(self):
471495
"in",
472496
[
473497
{"foo1": "foo1", "bar1": "bar1", "id": 999, "baz1": "baz1", "type": "Anything"},
474-
{"foo2": "foo2", "bar2": "bar2", "id": 998, "baz2": "baz2", "type": "Anything"}
498+
{"foo2": "foo2", "bar2": "bar2", "id": 998, "baz2": "baz2", "type": "Anything"},
499+
{"foo3": "foo3", "bar3": "bar3"},
475500
],
476501
],
477502
]
@@ -489,6 +514,10 @@ def test_related_object_entity_optimization_in(self):
489514
{
490515
"id": 998,
491516
"type": "Anything",
517+
},
518+
{
519+
"foo3": "foo3",
520+
"bar3": "bar3",
492521
}
493522
],
494523
}
@@ -498,6 +527,124 @@ def test_related_object_entity_optimization_in(self):
498527
result = api.shotgun._translate_filters(filters, "all")
499528
self.assertEqual(result, expected)
500529

530+
def test_related_object_update_entity(self):
531+
entity_type = "Anything"
532+
entity_id = 999
533+
multi_entity_update_modes = {"link": "set", "name": "set"}
534+
data = {
535+
"name": "test",
536+
"link": {
537+
"name": "test",
538+
"url": "http://test.com",
539+
},
540+
}
541+
expected = {
542+
"id": 999,
543+
"type": "Anything",
544+
"fields": [
545+
{
546+
"field_name": "name",
547+
"value": "test",
548+
"multi_entity_update_mode": "set",
549+
},
550+
{
551+
"field_name": "link",
552+
"value": {
553+
"name": "test",
554+
"url": "http://test.com",
555+
},
556+
"multi_entity_update_mode": "set",
557+
},
558+
],
559+
}
560+
sg = api.Shotgun("http://server_path", "script_name", "api_key", connect=False)
561+
result = sg._translate_update_params(entity_type, entity_id, data, multi_entity_update_modes)
562+
self.assertEqual(result, expected)
563+
564+
@mock.patch.dict(os.environ, {"SHOTGUN_API_ENABLE_ENTITY_OPTIMIZATION": "1"})
565+
def test_related_object_update_optimization_entity(self):
566+
entity_type = "Anything"
567+
entity_id = 999
568+
multi_entity_update_modes = {"project": "set", "link": "set", "name": "set"}
569+
data = {
570+
"name": "test",
571+
"link": {
572+
"name": "test",
573+
"url": "http://test.com",
574+
},
575+
"project": {
576+
"foo1": "foo1",
577+
"bar1": "bar1",
578+
"id": 888,
579+
"baz1": "baz1",
580+
"type": "Project",
581+
},
582+
}
583+
expected = {
584+
"id": 999,
585+
"type": "Anything",
586+
"fields": [
587+
{
588+
"field_name": "name",
589+
"value": "test",
590+
"multi_entity_update_mode": "set",
591+
},
592+
{
593+
"field_name": "link",
594+
"value": {
595+
"name": "test",
596+
"url": "http://test.com",
597+
},
598+
"multi_entity_update_mode": "set",
599+
},
600+
{
601+
"field_name": "project",
602+
"multi_entity_update_mode": "set",
603+
"value": {
604+
# Entity is optimized with type/id fields.
605+
"id": 888,
606+
"type": "Project",
607+
},
608+
},
609+
],
610+
}
611+
sg = api.Shotgun("http://server_path", "script_name", "api_key", connect=False)
612+
result = sg._translate_update_params(entity_type, entity_id, data, multi_entity_update_modes)
613+
self.assertEqual(result, expected)
614+
615+
@mock.patch.dict(os.environ, {"SHOTGUN_API_ENABLE_ENTITY_OPTIMIZATION": "1"})
616+
def test_related_object_update_optimization_entity_multi(self):
617+
entity_type = "Asset"
618+
entity_id = 6626
619+
data = {
620+
"sg_status_list": "ip",
621+
"project": {"id": 70, "type": "Project", "name": "disposable name 70"},
622+
"sg_vvv": [
623+
{"id": 6441, "type": "Asset", "name": "disposable name 6441"},
624+
{"id": 6440, "type": "Asset"},
625+
],
626+
"sg_class": {"id": 1, "type": "CustomEntity53", "name": "disposable name 1"},
627+
}
628+
expected = {
629+
"type": "Asset",
630+
"id": 6626,
631+
"fields": [
632+
{"field_name": "sg_status_list", "value": "ip"},
633+
{"field_name": "project", "value": {"type": "Project", "id": 70}},
634+
{
635+
"field_name": "sg_vvv",
636+
"value": [
637+
{"id": 6441, "type": "Asset"},
638+
{"id": 6440, "type": "Asset"},
639+
],
640+
},
641+
{"field_name": "sg_class", "value": {"type": "CustomEntity53", "id": 1}},
642+
],
643+
}
644+
sg = api.Shotgun("http://server_path", "script_name", "api_key", connect=False)
645+
result = sg._translate_update_params(entity_type, entity_id, data, None)
646+
self.assertEqual(result, expected)
647+
501648

502649
class TestCerts(unittest.TestCase):
503650
# A dummy bad url provided by Amazon

0 commit comments

Comments
 (0)