|
1 | | -from dataclasses import dataclass |
2 | 1 | from typing import ( |
3 | | - TYPE_CHECKING, |
4 | | - Any, |
5 | | - Dict, |
6 | | - List, |
7 | | - Literal, |
8 | 2 | Optional, |
9 | 3 | Type, |
10 | | - TypedDict, |
11 | | - Union, |
12 | 4 | ) |
13 | 5 |
|
14 | 6 | from fastapi import APIRouter, Request |
15 | 7 |
|
16 | | -from fastapi_jsonapi import RoutersJSONAPI |
| 8 | +from fastapi_jsonapi.atomic.atomic_handler import AtomicViewHandler |
17 | 9 | from fastapi_jsonapi.atomic.schemas import ( |
18 | 10 | AtomicOperationRequest, |
19 | 11 | AtomicResultResponse, |
20 | | - OperationItemInSchema, |
21 | | - OperationRelationshipSchema, |
22 | 12 | ) |
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} |
155 | 13 |
|
156 | 14 |
|
157 | 15 | class AtomicOperations: |
|
0 commit comments