apexdevkit 1.3.10__tar.gz → 1.3.12__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {apexdevkit-1.3.10 → apexdevkit-1.3.12}/PKG-INFO +1 -1
- {apexdevkit-1.3.10 → apexdevkit-1.3.12}/apexdevkit/error.py +9 -0
- {apexdevkit-1.3.10 → apexdevkit-1.3.12}/apexdevkit/fastapi/router.py +114 -17
- {apexdevkit-1.3.10 → apexdevkit-1.3.12}/pyproject.toml +1 -1
- {apexdevkit-1.3.10 → apexdevkit-1.3.12}/LICENSE +0 -0
- {apexdevkit-1.3.10 → apexdevkit-1.3.12}/README.md +0 -0
- {apexdevkit-1.3.10 → apexdevkit-1.3.12}/apexdevkit/__init__.py +0 -0
- {apexdevkit-1.3.10 → apexdevkit-1.3.12}/apexdevkit/annotation/__init__.py +0 -0
- {apexdevkit-1.3.10 → apexdevkit-1.3.12}/apexdevkit/annotation/deprecate.py +0 -0
- {apexdevkit-1.3.10 → apexdevkit-1.3.12}/apexdevkit/fastapi/__init__.py +0 -0
- {apexdevkit-1.3.10 → apexdevkit-1.3.12}/apexdevkit/fastapi/builder.py +0 -0
- {apexdevkit-1.3.10 → apexdevkit-1.3.12}/apexdevkit/fastapi/dependable.py +0 -0
- {apexdevkit-1.3.10 → apexdevkit-1.3.12}/apexdevkit/fastapi/docs.py +0 -0
- {apexdevkit-1.3.10 → apexdevkit-1.3.12}/apexdevkit/fastapi/response.py +0 -0
- {apexdevkit-1.3.10 → apexdevkit-1.3.12}/apexdevkit/fastapi/schema.py +0 -0
- {apexdevkit-1.3.10 → apexdevkit-1.3.12}/apexdevkit/fastapi/service.py +0 -0
- {apexdevkit-1.3.10 → apexdevkit-1.3.12}/apexdevkit/http/__init__.py +0 -0
- {apexdevkit-1.3.10 → apexdevkit-1.3.12}/apexdevkit/http/fake.py +0 -0
- {apexdevkit-1.3.10 → apexdevkit-1.3.12}/apexdevkit/http/fluent.py +0 -0
- {apexdevkit-1.3.10 → apexdevkit-1.3.12}/apexdevkit/http/httpx.py +0 -0
- {apexdevkit-1.3.10 → apexdevkit-1.3.12}/apexdevkit/http/json.py +0 -0
- {apexdevkit-1.3.10 → apexdevkit-1.3.12}/apexdevkit/http/url.py +0 -0
- {apexdevkit-1.3.10 → apexdevkit-1.3.12}/apexdevkit/py.typed +0 -0
- {apexdevkit-1.3.10 → apexdevkit-1.3.12}/apexdevkit/repository/__init__.py +0 -0
- {apexdevkit-1.3.10 → apexdevkit-1.3.12}/apexdevkit/repository/connector.py +0 -0
- {apexdevkit-1.3.10 → apexdevkit-1.3.12}/apexdevkit/repository/database.py +0 -0
- {apexdevkit-1.3.10 → apexdevkit-1.3.12}/apexdevkit/repository/in_memory.py +0 -0
- {apexdevkit-1.3.10 → apexdevkit-1.3.12}/apexdevkit/repository/interface.py +0 -0
- {apexdevkit-1.3.10 → apexdevkit-1.3.12}/apexdevkit/testing/__init__.py +0 -0
- {apexdevkit-1.3.10 → apexdevkit-1.3.12}/apexdevkit/testing/database.py +0 -0
- {apexdevkit-1.3.10 → apexdevkit-1.3.12}/apexdevkit/testing/fake.py +0 -0
- {apexdevkit-1.3.10 → apexdevkit-1.3.12}/apexdevkit/testing/rest.py +0 -0
|
@@ -38,3 +38,12 @@ class ExistsError(Exception):
|
|
|
38
38
|
@dataclass
|
|
39
39
|
class DoesNotExistError(Exception):
|
|
40
40
|
id: Any = "unknown"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass
|
|
44
|
+
class ForbiddenError(Exception):
|
|
45
|
+
item: Any = field(default_factory=UnknownItem)
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def id(self) -> Any:
|
|
49
|
+
return self.item.id
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
from dataclasses import dataclass, field
|
|
2
2
|
from functools import cached_property
|
|
3
|
-
from typing import Annotated, Any, Iterable, Self, TypeVar
|
|
3
|
+
from typing import Annotated, Any, Iterable, Protocol, Self, TypeVar
|
|
4
4
|
|
|
5
5
|
from fastapi import APIRouter, Depends, Path
|
|
6
6
|
from fastapi.responses import JSONResponse
|
|
7
7
|
|
|
8
|
-
from apexdevkit.error import DoesNotExistError, ExistsError
|
|
8
|
+
from apexdevkit.error import DoesNotExistError, ExistsError, ForbiddenError
|
|
9
9
|
from apexdevkit.fastapi.schema import DataclassFields, RestfulSchema, SchemaFields
|
|
10
10
|
from apexdevkit.fastapi.service import RawCollection, RawItem, RestfulService
|
|
11
11
|
from apexdevkit.testing import RestfulName
|
|
@@ -55,6 +55,13 @@ class RestfulResponse:
|
|
|
55
55
|
error=f"An item<{name}> with the {e} already exists.",
|
|
56
56
|
)
|
|
57
57
|
|
|
58
|
+
def forbidden(self, e: ForbiddenError) -> dict[str, Any]:
|
|
59
|
+
return self._response(
|
|
60
|
+
403,
|
|
61
|
+
data={"id": str(e.id)},
|
|
62
|
+
error="Forbidden",
|
|
63
|
+
)
|
|
64
|
+
|
|
58
65
|
def created_one(self, item: Any) -> dict[str, Any]:
|
|
59
66
|
return self._response(201, item)
|
|
60
67
|
|
|
@@ -71,14 +78,34 @@ class RestfulResponse:
|
|
|
71
78
|
T = TypeVar("T")
|
|
72
79
|
|
|
73
80
|
|
|
81
|
+
class RestfulServiceInfra(Protocol):
|
|
82
|
+
def service_for(self, parent_id: str) -> RestfulService:
|
|
83
|
+
pass
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@dataclass(frozen=True)
|
|
87
|
+
class SingleRestfulServiceInfra:
|
|
88
|
+
service: RestfulService
|
|
89
|
+
|
|
90
|
+
def service_for(self, parent_id: str) -> RestfulService:
|
|
91
|
+
return self.service
|
|
92
|
+
|
|
93
|
+
|
|
74
94
|
@dataclass
|
|
75
95
|
class RestfulRouter:
|
|
76
|
-
service: RestfulService
|
|
96
|
+
service: RestfulService | None = None
|
|
77
97
|
|
|
78
98
|
router: APIRouter = field(default_factory=APIRouter)
|
|
79
99
|
|
|
80
100
|
name: RestfulName = field(init=False)
|
|
81
101
|
fields: SchemaFields = field(init=False)
|
|
102
|
+
infra: RestfulServiceInfra = field(init=False)
|
|
103
|
+
|
|
104
|
+
parent: str = field(init=False, default="")
|
|
105
|
+
|
|
106
|
+
def __post_init__(self) -> None:
|
|
107
|
+
if self.service:
|
|
108
|
+
self.infra = SingleRestfulServiceInfra(self.service)
|
|
82
109
|
|
|
83
110
|
@cached_property
|
|
84
111
|
def response(self) -> RestfulResponse:
|
|
@@ -92,6 +119,10 @@ class RestfulRouter:
|
|
|
92
119
|
def id_alias(self) -> str:
|
|
93
120
|
return self.name.singular + "_id"
|
|
94
121
|
|
|
122
|
+
@property
|
|
123
|
+
def parent_id_alias(self) -> str:
|
|
124
|
+
return self.parent + "_id"
|
|
125
|
+
|
|
95
126
|
@property
|
|
96
127
|
def item_path(self) -> str:
|
|
97
128
|
return "/{" + self.id_alias + "}"
|
|
@@ -111,7 +142,27 @@ class RestfulRouter:
|
|
|
111
142
|
|
|
112
143
|
return self
|
|
113
144
|
|
|
145
|
+
def with_parent(self, name: str) -> Self:
|
|
146
|
+
self.parent = name
|
|
147
|
+
|
|
148
|
+
return self
|
|
149
|
+
|
|
150
|
+
def with_service(self, value: RestfulService) -> Self:
|
|
151
|
+
self.infra = SingleRestfulServiceInfra(value)
|
|
152
|
+
|
|
153
|
+
return self
|
|
154
|
+
|
|
155
|
+
def with_infra(self, value: RestfulServiceInfra) -> Self:
|
|
156
|
+
self.infra = value
|
|
157
|
+
|
|
158
|
+
return self
|
|
159
|
+
|
|
114
160
|
def with_create_one_endpoint(self, is_documented: bool = True) -> Self:
|
|
161
|
+
parent_id_type = Annotated[
|
|
162
|
+
str,
|
|
163
|
+
Path(alias=self.parent_id_alias, default_factory=str),
|
|
164
|
+
]
|
|
165
|
+
|
|
115
166
|
item_type = Annotated[
|
|
116
167
|
RawItem,
|
|
117
168
|
Depends(self.schema.for_create_one()),
|
|
@@ -124,9 +175,11 @@ class RestfulRouter:
|
|
|
124
175
|
response_model=self.schema.for_item(),
|
|
125
176
|
include_in_schema=is_documented,
|
|
126
177
|
)
|
|
127
|
-
def create_one(item: item_type) -> _Response:
|
|
178
|
+
def create_one(parent_id: parent_id_type, item: item_type) -> _Response:
|
|
179
|
+
service = self.infra.service_for(parent_id)
|
|
180
|
+
|
|
128
181
|
try:
|
|
129
|
-
item =
|
|
182
|
+
item = service.create_one(item)
|
|
130
183
|
except ExistsError as e:
|
|
131
184
|
return JSONResponse(self.response.exists(e), 409)
|
|
132
185
|
|
|
@@ -135,6 +188,11 @@ class RestfulRouter:
|
|
|
135
188
|
return self
|
|
136
189
|
|
|
137
190
|
def with_create_many_endpoint(self, is_documented: bool = True) -> Self:
|
|
191
|
+
parent_id_type = Annotated[
|
|
192
|
+
str,
|
|
193
|
+
Path(alias=self.parent_id_alias, default_factory=str),
|
|
194
|
+
]
|
|
195
|
+
|
|
138
196
|
collection_type = Annotated[
|
|
139
197
|
RawCollection,
|
|
140
198
|
Depends(self.schema.for_create_many()),
|
|
@@ -147,9 +205,11 @@ class RestfulRouter:
|
|
|
147
205
|
response_model=self.schema.for_collection(),
|
|
148
206
|
include_in_schema=is_documented,
|
|
149
207
|
)
|
|
150
|
-
def create_many(items: collection_type) -> _Response:
|
|
208
|
+
def create_many(parent_id: parent_id_type, items: collection_type) -> _Response:
|
|
209
|
+
service = self.infra.service_for(parent_id)
|
|
210
|
+
|
|
151
211
|
try:
|
|
152
|
-
return self.response.created_many(
|
|
212
|
+
return self.response.created_many(service.create_many(items))
|
|
153
213
|
except ExistsError as e:
|
|
154
214
|
return JSONResponse(self.response.exists(e), 409)
|
|
155
215
|
|
|
@@ -157,6 +217,10 @@ class RestfulRouter:
|
|
|
157
217
|
|
|
158
218
|
def with_read_one_endpoint(self, is_documented: bool = True) -> Self:
|
|
159
219
|
id_type = Annotated[str, Path(alias=self.id_alias)]
|
|
220
|
+
parent_id_type = Annotated[
|
|
221
|
+
str,
|
|
222
|
+
Path(alias=self.parent_id_alias, default_factory=str),
|
|
223
|
+
]
|
|
160
224
|
|
|
161
225
|
@self.router.get(
|
|
162
226
|
self.item_path,
|
|
@@ -165,15 +229,22 @@ class RestfulRouter:
|
|
|
165
229
|
response_model=self.schema.for_item(),
|
|
166
230
|
include_in_schema=is_documented,
|
|
167
231
|
)
|
|
168
|
-
def read_one(item_id: id_type) -> _Response:
|
|
232
|
+
def read_one(parent_id: parent_id_type, item_id: id_type) -> _Response:
|
|
233
|
+
service = self.infra.service_for(parent_id)
|
|
234
|
+
|
|
169
235
|
try:
|
|
170
|
-
return self.response.found_one(
|
|
236
|
+
return self.response.found_one(service.read_one(item_id))
|
|
171
237
|
except DoesNotExistError as e:
|
|
172
238
|
return JSONResponse(self.response.not_found(e), 404)
|
|
173
239
|
|
|
174
240
|
return self
|
|
175
241
|
|
|
176
242
|
def with_read_all_endpoint(self, is_documented: bool = True) -> Self:
|
|
243
|
+
parent_id_type = Annotated[
|
|
244
|
+
str,
|
|
245
|
+
Path(alias=self.parent_id_alias, default_factory=str),
|
|
246
|
+
]
|
|
247
|
+
|
|
177
248
|
@self.router.get(
|
|
178
249
|
"",
|
|
179
250
|
status_code=200,
|
|
@@ -181,12 +252,18 @@ class RestfulRouter:
|
|
|
181
252
|
response_model=self.schema.for_collection(),
|
|
182
253
|
include_in_schema=is_documented,
|
|
183
254
|
)
|
|
184
|
-
def read_all() -> _Response:
|
|
185
|
-
|
|
255
|
+
def read_all(parent_id: parent_id_type) -> _Response:
|
|
256
|
+
service = self.infra.service_for(parent_id)
|
|
257
|
+
|
|
258
|
+
return self.response.found_many(list(service.read_all()))
|
|
186
259
|
|
|
187
260
|
return self
|
|
188
261
|
|
|
189
262
|
def with_update_one_endpoint(self, is_documented: bool = True) -> Self:
|
|
263
|
+
parent_id_type = Annotated[
|
|
264
|
+
str,
|
|
265
|
+
Path(alias=self.parent_id_alias, default_factory=str),
|
|
266
|
+
]
|
|
190
267
|
id_type = Annotated[str, Path(alias=self.id_alias)]
|
|
191
268
|
update_type = Annotated[
|
|
192
269
|
RawItem,
|
|
@@ -200,17 +277,29 @@ class RestfulRouter:
|
|
|
200
277
|
response_model=self.schema.for_no_data(),
|
|
201
278
|
include_in_schema=is_documented,
|
|
202
279
|
)
|
|
203
|
-
def update_one(
|
|
280
|
+
def update_one(
|
|
281
|
+
parent_id: parent_id_type,
|
|
282
|
+
item_id: id_type,
|
|
283
|
+
updates: update_type,
|
|
284
|
+
) -> _Response:
|
|
285
|
+
service = self.infra.service_for(parent_id)
|
|
286
|
+
|
|
204
287
|
try:
|
|
205
|
-
|
|
288
|
+
service.update_one(item_id, **updates)
|
|
206
289
|
except DoesNotExistError as e:
|
|
207
290
|
return JSONResponse(self.response.not_found(e), 404)
|
|
291
|
+
except ForbiddenError as e:
|
|
292
|
+
return JSONResponse(self.response.forbidden(e), 403)
|
|
208
293
|
|
|
209
294
|
return self.response.ok()
|
|
210
295
|
|
|
211
296
|
return self
|
|
212
297
|
|
|
213
298
|
def with_update_many_endpoint(self, is_documented: bool = True) -> Self:
|
|
299
|
+
parent_id_type = Annotated[
|
|
300
|
+
str,
|
|
301
|
+
Path(alias=self.parent_id_alias, default_factory=str),
|
|
302
|
+
]
|
|
214
303
|
collection_type = Annotated[
|
|
215
304
|
RawCollection,
|
|
216
305
|
Depends(self.schema.for_update_many()),
|
|
@@ -223,14 +312,20 @@ class RestfulRouter:
|
|
|
223
312
|
response_model=self.schema.for_no_data(),
|
|
224
313
|
include_in_schema=is_documented,
|
|
225
314
|
)
|
|
226
|
-
def update_many(items: collection_type) -> _Response:
|
|
227
|
-
self.
|
|
315
|
+
def update_many(parent_id: parent_id_type, items: collection_type) -> _Response:
|
|
316
|
+
service = self.infra.service_for(parent_id)
|
|
317
|
+
|
|
318
|
+
service.update_many(items)
|
|
228
319
|
|
|
229
320
|
return self.response.ok()
|
|
230
321
|
|
|
231
322
|
return self
|
|
232
323
|
|
|
233
324
|
def with_delete_one_endpoint(self, is_documented: bool = True) -> Self:
|
|
325
|
+
parent_id_type = Annotated[
|
|
326
|
+
str,
|
|
327
|
+
Path(alias=self.parent_id_alias, default_factory=str),
|
|
328
|
+
]
|
|
234
329
|
id_type = Annotated[str, Path(alias=self.id_alias)]
|
|
235
330
|
|
|
236
331
|
@self.router.delete(
|
|
@@ -240,9 +335,11 @@ class RestfulRouter:
|
|
|
240
335
|
response_model=self.schema.for_no_data(),
|
|
241
336
|
include_in_schema=is_documented,
|
|
242
337
|
)
|
|
243
|
-
def delete_one(item_id: id_type) -> _Response:
|
|
338
|
+
def delete_one(parent_id: parent_id_type, item_id: id_type) -> _Response:
|
|
339
|
+
service = self.infra.service_for(parent_id)
|
|
340
|
+
|
|
244
341
|
try:
|
|
245
|
-
|
|
342
|
+
service.delete_one(item_id)
|
|
246
343
|
except DoesNotExistError as e:
|
|
247
344
|
return JSONResponse(self.response.not_found(e), 404)
|
|
248
345
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|