Skip to content

Commit e85eb80

Browse files
author
Jackie Greenbaum
committed
Implement lid support
1 parent 8d3ee50 commit e85eb80

File tree

2 files changed

+67
-8
lines changed

2 files changed

+67
-8
lines changed

atomic_operations/parsers.py

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,24 @@ class AtomicOperationParser(JSONParser):
4343
renderer_class = renderers.JSONRenderer
4444

4545
def check_resource_identifier_object(self, idx: int, resource_identifier_object: Dict, operation_code: str):
46-
if operation_code in ["update", "remove"] and not resource_identifier_object.get("id"):
47-
raise JsonApiParseError(
48-
id="missing-id",
49-
detail="The resource identifier object must contain an `id` member",
50-
pointer=f"/{ATOMIC_OPERATIONS}/{idx}/{'data' if operation_code == 'update' else 'ref'}"
51-
)
46+
if operation_code in ["update", "remove"]:
47+
resource_id = resource_identifier_object.get("id")
48+
resource_lid = resource_identifier_object.get("lid")
49+
50+
if not (resource_id or resource_lid):
51+
raise JsonApiParseError(
52+
id="missing-id",
53+
detail="The resource identifier object must contain an `id` member or a `lid` member",
54+
pointer=f"/{ATOMIC_OPERATIONS}/{idx}/{'data' if operation_code == 'update' else 'ref'}"
55+
)
56+
57+
if resource_id and resource_lid:
58+
raise JsonApiParseError(
59+
id="multiple-id-fields",
60+
detail="Only one of `id`, `lid` may be specified",
61+
pointer=f"/{ATOMIC_OPERATIONS}/{idx}/{'data' if operation_code == 'update' else 'ref'}"
62+
)
63+
5264
if not resource_identifier_object.get("type"):
5365
raise JsonApiParseError(
5466
id="missing-type",
@@ -150,10 +162,14 @@ def check_operation(self, idx: int, operation: Dict):
150162
pointer=f"/{ATOMIC_OPERATIONS}/{idx}/op"
151163
)
152164

153-
def parse_id_and_type(self, resource_identifier_object):
165+
def parse_id_lid_and_type(self, resource_identifier_object):
154166
parsed_data = {"id": resource_identifier_object.get(
155167
"id")} if "id" in resource_identifier_object else {}
156168
parsed_data["type"] = resource_identifier_object.get("type")
169+
170+
if lid := resource_identifier_object.get("lid", None):
171+
parsed_data["lid"] = lid
172+
157173
return parsed_data
158174

159175
def check_root(self, result):
@@ -173,7 +189,7 @@ def check_root(self, result):
173189
)
174190

175191
def parse_operation(self, resource_identifier_object, result):
176-
_parsed_data = self.parse_id_and_type(resource_identifier_object)
192+
_parsed_data = self.parse_id_lid_and_type(resource_identifier_object)
177193
_parsed_data.update(self.parse_attributes(resource_identifier_object))
178194
_parsed_data.update(self.parse_relationships(resource_identifier_object))
179195
_parsed_data.update(self.parse_metadata(result))

atomic_operations/views.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from typing import Dict, List
2+
from collections import defaultdict
23

34
from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist
45
from django.db.transaction import atomic
@@ -27,6 +28,8 @@ class AtomicOperationView(APIView):
2728
sequential = True
2829
response_data: List[Dict] = []
2930

31+
lid_to_id = defaultdict(dict)
32+
3033
# TODO: proof how to check permissions for all operations
3134
# permission_classes = TODO
3235
# call def check_permissions for `add` operation
@@ -94,8 +97,15 @@ def post(self, request, *args, **kwargs):
9497

9598
def handle_sequential(self, serializer, operation_code):
9699
if operation_code in ["add", "update", "update-relationship"]:
100+
lid = serializer.initial_data.get("lid", None)
101+
97102
serializer.is_valid(raise_exception=True)
98103
serializer.save()
104+
105+
if operation_code == "add" and lid:
106+
resource_type = serializer.initial_data["type"]
107+
self.lid_to_id[resource_type][lid] = serializer.data["id"]
108+
99109
if operation_code != "update-relationship":
100110
self.response_data.append(serializer.data)
101111
else:
@@ -139,6 +149,36 @@ def handle_bulk(self, serializer, current_operation_code, bulk_operation_data):
139149
bulk_operation_data["serializer_collection"][0], current_operation_code)
140150
bulk_operation_data["serializer_collection"] = []
141151

152+
def substitute_lids(self, data, idx, should_raise_unknown_lid_error):
153+
if not isinstance(data, dict):
154+
return
155+
156+
try:
157+
lid = data.get("lid", None)
158+
if lid:
159+
resource_type = data["type"]
160+
data["id"] = self.lid_to_id[resource_type][lid]
161+
except KeyError:
162+
if should_raise_unknown_lid_error:
163+
raise UnprocessableEntity([
164+
{
165+
"id": "unknown-lid",
166+
"detail": f'Object with lid `{lid}` received for operation with index `{idx}` does not exist',
167+
"source": {
168+
"pointer": f"/{ATOMIC_OPERATIONS}/{idx}/data/lid"
169+
},
170+
"status": "422"
171+
}
172+
])
173+
174+
for _, value in data.items():
175+
if isinstance(value, dict):
176+
self.substitute_lids(value, idx, should_raise_unknown_lid_error=True)
177+
elif isinstance(value, list):
178+
[self.substitute_lids(value, idx, should_raise_unknown_lid_error=True) for value in value]
179+
180+
return data
181+
142182
def perform_operations(self, parsed_operations: List[Dict]):
143183
self.response_data = [] # reset local response data storage
144184

@@ -154,6 +194,9 @@ def perform_operations(self, parsed_operations: List[Dict]):
154194
operation_code = next(iter(operation))
155195
obj = operation[operation_code]
156196

197+
should_raise_unknown_lid_error = operation_code != "add"
198+
self.substitute_lids(obj, idx, should_raise_unknown_lid_error)
199+
157200
serializer = self.get_serializer(
158201
idx=idx,
159202
data=obj,

0 commit comments

Comments
 (0)