Skip to content

Commit a117cd3

Browse files
committed
move atomic operations and atomic handler to separate modules
1 parent 2b7e776 commit a117cd3

File tree

3 files changed

+142
-143
lines changed

3 files changed

+142
-143
lines changed

fastapi_jsonapi/atomic/atomic.py

Lines changed: 1 addition & 143 deletions
Original file line numberDiff line numberDiff line change
@@ -1,157 +1,15 @@
1-
from dataclasses import dataclass
21
from typing import (
3-
TYPE_CHECKING,
4-
Any,
5-
Dict,
6-
List,
7-
Literal,
82
Optional,
93
Type,
10-
TypedDict,
11-
Union,
124
)
135

146
from fastapi import APIRouter, Request
157

16-
from fastapi_jsonapi import RoutersJSONAPI
8+
from fastapi_jsonapi.atomic.atomic_handler import AtomicViewHandler
179
from fastapi_jsonapi.atomic.schemas import (
1810
AtomicOperationRequest,
1911
AtomicResultResponse,
20-
OperationItemInSchema,
21-
OperationRelationshipSchema,
2212
)
23-
from fastapi_jsonapi.utils.dependency_helper import DependencyHelper
24-
from fastapi_jsonapi.views.detail_view import DetailViewBase
25-
from fastapi_jsonapi.views.list_view import ListViewBase
26-
from fastapi_jsonapi.views.utils import HTTPMethodConfig
27-
from fastapi_jsonapi.views.view_base import ViewBase
28-
29-
if TYPE_CHECKING:
30-
from fastapi_jsonapi.data_layers.base import BaseDataLayer
31-
32-
33-
@dataclass
34-
class PreparedOperation:
35-
action: Literal["add", "update", "remove"]
36-
data_layer: "BaseDataLayer"
37-
view: "ViewBase"
38-
jsonapi: RoutersJSONAPI
39-
data: Union[
40-
# from biggest to smallest!
41-
# any object creation
42-
OperationItemInSchema,
43-
# to-many relationship
44-
List[OperationRelationshipSchema],
45-
# to-one relationship
46-
OperationRelationshipSchema,
47-
# not required
48-
None,
49-
] = None
50-
51-
52-
#
53-
54-
AtomicResponseDict = TypedDict("AtomicResponseDict", {"atomic:results": list[Any]})
55-
56-
57-
class AtomicViewHandler:
58-
def __init__(
59-
self,
60-
request: Request,
61-
operations_request: AtomicOperationRequest,
62-
):
63-
self.request = request
64-
self.operations_request = operations_request
65-
66-
async def handle_view_dependencies(
67-
self,
68-
request: Request,
69-
jsonapi: RoutersJSONAPI,
70-
) -> Dict[str, Any]:
71-
method_config: HTTPMethodConfig = jsonapi.get_method_config_for_create()
72-
73-
def handle_dependencies(**dep_kwargs):
74-
return dep_kwargs
75-
76-
handle_dependencies.__signature__ = jsonapi.prepare_dependencies_handler_signature(
77-
custom_handler=handle_dependencies,
78-
method_config=method_config,
79-
)
80-
81-
dependencies_result: Dict[str, Any] = await DependencyHelper(request=request).run(handle_dependencies)
82-
return dependencies_result
83-
84-
async def prepare_operations(self) -> List[PreparedOperation]:
85-
prepared_operations: List[PreparedOperation] = []
86-
87-
for operation in self.operations_request.operations:
88-
jsonapi = RoutersJSONAPI.all_jsonapi_routers[operation.data.type]
89-
view_cls: Type["ViewBase"] = jsonapi.detail_view_resource
90-
if operation.op == "add":
91-
view_cls = jsonapi.list_view_resource
92-
view = view_cls(request=self.request, jsonapi=jsonapi)
93-
dependencies_result: Dict[str, Any] = await self.handle_view_dependencies(
94-
request=self.request,
95-
jsonapi=jsonapi,
96-
)
97-
dl: "BaseDataLayer" = await view.get_data_layer(dependencies_result)
98-
99-
one_operation = PreparedOperation(
100-
action=operation.op,
101-
data_layer=dl,
102-
view=view,
103-
jsonapi=jsonapi,
104-
data=operation.data,
105-
)
106-
prepared_operations.append(one_operation)
107-
108-
return prepared_operations
109-
110-
async def handle(self) -> Union[AtomicResponseDict, AtomicResultResponse]:
111-
prepared_operations = await self.prepare_operations()
112-
results = []
113-
114-
# TODO: try/except, catch schema ValidationError
115-
116-
previous_dl: Optional["BaseDataLayer"] = None
117-
for operation in prepared_operations:
118-
dl = operation.data_layer
119-
await dl.atomic_start(previous_dl=previous_dl)
120-
previous_dl = dl
121-
if operation.action == "add":
122-
data_in = operation.jsonapi.schema_in_post(data=operation.data)
123-
assert isinstance(operation.view, ListViewBase)
124-
view: "ListViewBase" = operation.view
125-
response = await view.process_create_object(dl=operation.data_layer, data_create=data_in.data)
126-
# response.data.id
127-
results.append({"data": response.data})
128-
elif operation.action == "update":
129-
data_in = operation.jsonapi.schema_in_patch(data=operation.data)
130-
assert isinstance(operation.view, DetailViewBase)
131-
view: "DetailViewBase" = operation.view
132-
response = await view.process_update_object(
133-
dl=dl,
134-
obj_id=data_in.data.id,
135-
data_update=data_in.data,
136-
)
137-
# response.data.id
138-
results.append({"data": response.data})
139-
elif operation.action == "remove":
140-
assert isinstance(operation.view, DetailViewBase)
141-
view: "DetailViewBase" = operation.view
142-
response = await view.process_delete_object(
143-
dl=dl,
144-
obj_id=operation.data.id,
145-
)
146-
results.append({"data": response.data})
147-
else:
148-
msg = f"unknown action {operation.action!r}"
149-
raise ValueError(msg)
150-
151-
if previous_dl:
152-
await previous_dl.atomic_end(success=True)
153-
154-
return {"atomic:results": results}
15513

15614

15715
class AtomicOperations:
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
from typing import Any, Dict, List, Optional, Type, TypedDict, Union
2+
3+
from starlette.requests import Request
4+
5+
from fastapi_jsonapi import RoutersJSONAPI
6+
from fastapi_jsonapi.atomic.prepared_atomic_operation import PreparedOperation
7+
from fastapi_jsonapi.atomic.schemas import AtomicOperationRequest, AtomicResultResponse
8+
from fastapi_jsonapi.data_layers.base import BaseDataLayer
9+
from fastapi_jsonapi.utils.dependency_helper import DependencyHelper
10+
from fastapi_jsonapi.views.detail_view import DetailViewBase
11+
from fastapi_jsonapi.views.list_view import ListViewBase
12+
from fastapi_jsonapi.views.utils import HTTPMethodConfig
13+
from fastapi_jsonapi.views.view_base import ViewBase
14+
15+
AtomicResponseDict = TypedDict("AtomicResponseDict", {"atomic:results": List[Any]})
16+
17+
18+
class AtomicViewHandler:
19+
def __init__(
20+
self,
21+
request: Request,
22+
operations_request: AtomicOperationRequest,
23+
):
24+
self.request = request
25+
self.operations_request = operations_request
26+
27+
async def handle_view_dependencies(
28+
self,
29+
request: Request,
30+
jsonapi: RoutersJSONAPI,
31+
) -> Dict[str, Any]:
32+
method_config: HTTPMethodConfig = jsonapi.get_method_config_for_create()
33+
34+
def handle_dependencies(**dep_kwargs):
35+
return dep_kwargs
36+
37+
handle_dependencies.__signature__ = jsonapi.prepare_dependencies_handler_signature(
38+
custom_handler=handle_dependencies,
39+
method_config=method_config,
40+
)
41+
42+
dependencies_result: Dict[str, Any] = await DependencyHelper(request=request).run(handle_dependencies)
43+
return dependencies_result
44+
45+
async def prepare_operations(self) -> List[PreparedOperation]:
46+
prepared_operations: List[PreparedOperation] = []
47+
48+
for operation in self.operations_request.operations:
49+
jsonapi = RoutersJSONAPI.all_jsonapi_routers[operation.data.type]
50+
view_cls: Type["ViewBase"] = jsonapi.detail_view_resource
51+
if operation.op == "add":
52+
view_cls = jsonapi.list_view_resource
53+
view = view_cls(request=self.request, jsonapi=jsonapi)
54+
dependencies_result: Dict[str, Any] = await self.handle_view_dependencies(
55+
request=self.request,
56+
jsonapi=jsonapi,
57+
)
58+
dl: "BaseDataLayer" = await view.get_data_layer(dependencies_result)
59+
60+
one_operation = PreparedOperation(
61+
action=operation.op,
62+
data_layer=dl,
63+
view=view,
64+
jsonapi=jsonapi,
65+
data=operation.data,
66+
)
67+
prepared_operations.append(one_operation)
68+
69+
return prepared_operations
70+
71+
async def handle(self) -> Union[AtomicResponseDict, AtomicResultResponse]:
72+
prepared_operations = await self.prepare_operations()
73+
results = []
74+
75+
# TODO: try/except, catch schema ValidationError
76+
77+
previous_dl: Optional["BaseDataLayer"] = None
78+
for operation in prepared_operations:
79+
dl = operation.data_layer
80+
await dl.atomic_start(previous_dl=previous_dl)
81+
previous_dl = dl
82+
if operation.action == "add":
83+
data_in = operation.jsonapi.schema_in_post(data=operation.data)
84+
assert isinstance(operation.view, ListViewBase)
85+
view: "ListViewBase" = operation.view
86+
response = await view.process_create_object(dl=operation.data_layer, data_create=data_in.data)
87+
# response.data.id
88+
results.append({"data": response.data})
89+
elif operation.action == "update":
90+
data_in = operation.jsonapi.schema_in_patch(data=operation.data)
91+
assert isinstance(operation.view, DetailViewBase)
92+
view: "DetailViewBase" = operation.view
93+
response = await view.process_update_object(
94+
dl=dl,
95+
obj_id=data_in.data.id,
96+
data_update=data_in.data,
97+
)
98+
# response.data.id
99+
results.append({"data": response.data})
100+
elif operation.action == "remove":
101+
assert isinstance(operation.view, DetailViewBase)
102+
view: "DetailViewBase" = operation.view
103+
response = await view.process_delete_object(
104+
dl=dl,
105+
obj_id=operation.data.id,
106+
)
107+
results.append({"data": response.data})
108+
else:
109+
msg = f"unknown action {operation.action!r}"
110+
raise ValueError(msg)
111+
112+
if previous_dl:
113+
await previous_dl.atomic_end(success=True)
114+
115+
return {"atomic:results": results}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
from dataclasses import dataclass
2+
from typing import List, Literal, Union
3+
4+
from fastapi_jsonapi import RoutersJSONAPI
5+
from fastapi_jsonapi.atomic.schemas import OperationItemInSchema, OperationRelationshipSchema
6+
from fastapi_jsonapi.data_layers.base import BaseDataLayer
7+
from fastapi_jsonapi.views.view_base import ViewBase
8+
9+
10+
@dataclass
11+
class PreparedOperation:
12+
action: Literal["add", "update", "remove"]
13+
data_layer: "BaseDataLayer"
14+
view: "ViewBase"
15+
jsonapi: RoutersJSONAPI
16+
data: Union[
17+
# from biggest to smallest!
18+
# any object creation
19+
OperationItemInSchema,
20+
# to-many relationship
21+
List[OperationRelationshipSchema],
22+
# to-one relationship
23+
OperationRelationshipSchema,
24+
# not required
25+
None,
26+
] = None

0 commit comments

Comments
 (0)