apexdevkit 1.5.21__tar.gz → 1.5.23__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.5.21 → apexdevkit-1.5.23}/PKG-INFO +1 -1
- apexdevkit-1.5.23/apexdevkit/fastapi/__init__.py +10 -0
- {apexdevkit-1.5.21 → apexdevkit-1.5.23}/apexdevkit/fastapi/builder.py +23 -0
- apexdevkit-1.5.23/apexdevkit/fastapi/resource.py +342 -0
- apexdevkit-1.5.23/apexdevkit/fastapi/response.py +130 -0
- apexdevkit-1.5.23/apexdevkit/fastapi/router.py +348 -0
- {apexdevkit-1.5.21 → apexdevkit-1.5.23}/pyproject.toml +1 -1
- apexdevkit-1.5.21/apexdevkit/fastapi/__init__.py +0 -26
- apexdevkit-1.5.21/apexdevkit/fastapi/response.py +0 -64
- apexdevkit-1.5.21/apexdevkit/fastapi/router.py +0 -639
- {apexdevkit-1.5.21 → apexdevkit-1.5.23}/LICENSE +0 -0
- {apexdevkit-1.5.21 → apexdevkit-1.5.23}/README.md +0 -0
- {apexdevkit-1.5.21 → apexdevkit-1.5.23}/apexdevkit/__init__.py +0 -0
- {apexdevkit-1.5.21 → apexdevkit-1.5.23}/apexdevkit/annotation/__init__.py +0 -0
- {apexdevkit-1.5.21 → apexdevkit-1.5.23}/apexdevkit/annotation/deprecate.py +0 -0
- {apexdevkit-1.5.21 → apexdevkit-1.5.23}/apexdevkit/error.py +0 -0
- {apexdevkit-1.5.21 → apexdevkit-1.5.23}/apexdevkit/fastapi/dependable.py +0 -0
- {apexdevkit-1.5.21 → apexdevkit-1.5.23}/apexdevkit/fastapi/docs.py +0 -0
- {apexdevkit-1.5.21 → apexdevkit-1.5.23}/apexdevkit/fastapi/schema.py +0 -0
- {apexdevkit-1.5.21 → apexdevkit-1.5.23}/apexdevkit/fastapi/service.py +0 -0
- {apexdevkit-1.5.21 → apexdevkit-1.5.23}/apexdevkit/formatter.py +0 -0
- {apexdevkit-1.5.21 → apexdevkit-1.5.23}/apexdevkit/http/__init__.py +0 -0
- {apexdevkit-1.5.21 → apexdevkit-1.5.23}/apexdevkit/http/fake.py +0 -0
- {apexdevkit-1.5.21 → apexdevkit-1.5.23}/apexdevkit/http/fluent.py +0 -0
- {apexdevkit-1.5.21 → apexdevkit-1.5.23}/apexdevkit/http/httpx.py +0 -0
- {apexdevkit-1.5.21 → apexdevkit-1.5.23}/apexdevkit/http/json.py +0 -0
- {apexdevkit-1.5.21 → apexdevkit-1.5.23}/apexdevkit/http/url.py +0 -0
- {apexdevkit-1.5.21 → apexdevkit-1.5.23}/apexdevkit/py.typed +0 -0
- {apexdevkit-1.5.21 → apexdevkit-1.5.23}/apexdevkit/repository/__init__.py +0 -0
- {apexdevkit-1.5.21 → apexdevkit-1.5.23}/apexdevkit/repository/base.py +0 -0
- {apexdevkit-1.5.21 → apexdevkit-1.5.23}/apexdevkit/repository/connector.py +0 -0
- {apexdevkit-1.5.21 → apexdevkit-1.5.23}/apexdevkit/repository/database.py +0 -0
- {apexdevkit-1.5.21 → apexdevkit-1.5.23}/apexdevkit/repository/in_memory.py +0 -0
- {apexdevkit-1.5.21 → apexdevkit-1.5.23}/apexdevkit/repository/interface.py +0 -0
- {apexdevkit-1.5.21 → apexdevkit-1.5.23}/apexdevkit/testing/__init__.py +0 -0
- {apexdevkit-1.5.21 → apexdevkit-1.5.23}/apexdevkit/testing/database.py +0 -0
- {apexdevkit-1.5.21 → apexdevkit-1.5.23}/apexdevkit/testing/fake.py +0 -0
- {apexdevkit-1.5.21 → apexdevkit-1.5.23}/apexdevkit/testing/rest.py +0 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
from apexdevkit.fastapi.builder import FastApiBuilder, RestfulServiceBuilder
|
|
2
|
+
from apexdevkit.fastapi.dependable import inject
|
|
3
|
+
from apexdevkit.fastapi.service import RestfulRepositoryBuilder
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"FastApiBuilder",
|
|
7
|
+
"RestfulServiceBuilder",
|
|
8
|
+
"inject",
|
|
9
|
+
"RestfulRepositoryBuilder",
|
|
10
|
+
]
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
1
2
|
from dataclasses import dataclass, field
|
|
2
3
|
from typing import Any, Self
|
|
3
4
|
|
|
4
5
|
from fastapi import APIRouter, FastAPI
|
|
5
6
|
|
|
7
|
+
from apexdevkit.fastapi.service import RestfulService
|
|
8
|
+
|
|
6
9
|
|
|
7
10
|
@dataclass
|
|
8
11
|
class FastApiBuilder:
|
|
@@ -37,3 +40,23 @@ class FastApiBuilder:
|
|
|
37
40
|
self.app.include_router(value, prefix=f"/{key}", tags=[key.title()])
|
|
38
41
|
|
|
39
42
|
return self
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@dataclass
|
|
46
|
+
class RestfulServiceBuilder(ABC):
|
|
47
|
+
parent_id: str = field(init=False)
|
|
48
|
+
user: Any = field(init=False)
|
|
49
|
+
|
|
50
|
+
def with_user(self, user: Any) -> "RestfulServiceBuilder":
|
|
51
|
+
self.user = user
|
|
52
|
+
|
|
53
|
+
return self
|
|
54
|
+
|
|
55
|
+
def with_parent(self, identity: str) -> "RestfulServiceBuilder":
|
|
56
|
+
self.parent_id = identity
|
|
57
|
+
|
|
58
|
+
return self
|
|
59
|
+
|
|
60
|
+
@abstractmethod
|
|
61
|
+
def build(self) -> RestfulService: # pragma: no cover
|
|
62
|
+
pass
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from functools import cached_property
|
|
3
|
+
from typing import Annotated, Any, Callable
|
|
4
|
+
|
|
5
|
+
from fastapi import Path
|
|
6
|
+
from starlette.responses import JSONResponse
|
|
7
|
+
|
|
8
|
+
from apexdevkit.error import DoesNotExistError, ExistsError, ForbiddenError
|
|
9
|
+
from apexdevkit.fastapi.builder import RestfulServiceBuilder
|
|
10
|
+
from apexdevkit.fastapi.response import RestfulResponse
|
|
11
|
+
from apexdevkit.testing import RestfulName
|
|
12
|
+
|
|
13
|
+
_Response = JSONResponse | dict[str, Any]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class RestfulSubResource:
|
|
18
|
+
name: RestfulName
|
|
19
|
+
infra: RestfulServiceBuilder
|
|
20
|
+
parent: RestfulName
|
|
21
|
+
|
|
22
|
+
@property
|
|
23
|
+
def parent_id_alias(self) -> str:
|
|
24
|
+
return self.parent.singular + "_id"
|
|
25
|
+
|
|
26
|
+
@cached_property
|
|
27
|
+
def response(self) -> RestfulResponse:
|
|
28
|
+
return RestfulResponse(name=self.name)
|
|
29
|
+
|
|
30
|
+
def create_one(self, User, Item) -> Callable[..., _Response]: # type: ignore
|
|
31
|
+
ParentId = Annotated[str, Path(alias=self.parent_id_alias)]
|
|
32
|
+
|
|
33
|
+
def endpoint(user: User, parent_id: ParentId, item: Item) -> _Response:
|
|
34
|
+
try:
|
|
35
|
+
service = self.infra.with_user(user).with_parent(parent_id).build()
|
|
36
|
+
except DoesNotExistError as e:
|
|
37
|
+
return JSONResponse(RestfulResponse(self.parent).not_found(e), 404)
|
|
38
|
+
|
|
39
|
+
try:
|
|
40
|
+
item = service.create_one(item)
|
|
41
|
+
except ExistsError as e:
|
|
42
|
+
return JSONResponse(self.response.exists(e), 409)
|
|
43
|
+
except ForbiddenError as e:
|
|
44
|
+
return JSONResponse(self.response.forbidden(e), 403)
|
|
45
|
+
|
|
46
|
+
return self.response.created_one(item)
|
|
47
|
+
|
|
48
|
+
return endpoint
|
|
49
|
+
|
|
50
|
+
def create_many(self, User, Collection) -> Callable[..., _Response]: # type: ignore
|
|
51
|
+
ParentId = Annotated[str, Path(alias=self.parent_id_alias)]
|
|
52
|
+
|
|
53
|
+
def endpoint(user: User, parent_id: ParentId, items: Collection) -> _Response:
|
|
54
|
+
try:
|
|
55
|
+
service = self.infra.with_user(user).with_parent(parent_id).build()
|
|
56
|
+
except DoesNotExistError as e:
|
|
57
|
+
return JSONResponse(RestfulResponse(self.parent).not_found(e), 404)
|
|
58
|
+
|
|
59
|
+
try:
|
|
60
|
+
return self.response.created_many(service.create_many(items))
|
|
61
|
+
except ExistsError as e:
|
|
62
|
+
return JSONResponse(self.response.exists(e), 409)
|
|
63
|
+
except ForbiddenError as e:
|
|
64
|
+
return JSONResponse(self.response.forbidden(e), 403)
|
|
65
|
+
|
|
66
|
+
return endpoint
|
|
67
|
+
|
|
68
|
+
def read_one(self, User, ItemId) -> Callable[..., _Response]: # type: ignore
|
|
69
|
+
ParentId = Annotated[str, Path(alias=self.parent_id_alias)]
|
|
70
|
+
|
|
71
|
+
def endpoint(user: User, parent_id: ParentId, item_id: ItemId) -> _Response:
|
|
72
|
+
try:
|
|
73
|
+
service = self.infra.with_user(user).with_parent(parent_id).build()
|
|
74
|
+
except DoesNotExistError as e:
|
|
75
|
+
return JSONResponse(RestfulResponse(self.parent).not_found(e), 404)
|
|
76
|
+
|
|
77
|
+
try:
|
|
78
|
+
return self.response.found_one(service.read_one(item_id))
|
|
79
|
+
except DoesNotExistError as e:
|
|
80
|
+
return JSONResponse(self.response.not_found(e), 404)
|
|
81
|
+
except ForbiddenError as e:
|
|
82
|
+
return JSONResponse(self.response.forbidden(e), 403)
|
|
83
|
+
|
|
84
|
+
return endpoint
|
|
85
|
+
|
|
86
|
+
def read_all(self, User) -> Callable[..., _Response]: # type: ignore
|
|
87
|
+
ParentId = Annotated[str, Path(alias=self.parent_id_alias)]
|
|
88
|
+
|
|
89
|
+
def endpoint(user: User, parent_id: ParentId) -> _Response:
|
|
90
|
+
try:
|
|
91
|
+
service = self.infra.with_user(user).with_parent(parent_id).build()
|
|
92
|
+
except DoesNotExistError as e:
|
|
93
|
+
return JSONResponse(RestfulResponse(self.parent).not_found(e), 404)
|
|
94
|
+
|
|
95
|
+
try:
|
|
96
|
+
return self.response.found_many(list(service.read_all()))
|
|
97
|
+
except ForbiddenError as e:
|
|
98
|
+
return JSONResponse(self.response.forbidden(e), 403)
|
|
99
|
+
|
|
100
|
+
return endpoint
|
|
101
|
+
|
|
102
|
+
def update_one(self, User, ItemId, Updates) -> Callable[..., _Response]: # type: ignore
|
|
103
|
+
ParentId = Annotated[str, Path(alias=self.parent_id_alias)]
|
|
104
|
+
|
|
105
|
+
def endpoint(
|
|
106
|
+
user: User,
|
|
107
|
+
parent_id: ParentId,
|
|
108
|
+
item_id: ItemId,
|
|
109
|
+
updates: Updates,
|
|
110
|
+
) -> _Response:
|
|
111
|
+
try:
|
|
112
|
+
service = self.infra.with_user(user).with_parent(parent_id).build()
|
|
113
|
+
except DoesNotExistError as e:
|
|
114
|
+
return JSONResponse(RestfulResponse(self.parent).not_found(e), 404)
|
|
115
|
+
|
|
116
|
+
try:
|
|
117
|
+
service.update_one(item_id, **updates)
|
|
118
|
+
except DoesNotExistError as e:
|
|
119
|
+
return JSONResponse(self.response.not_found(e), 404)
|
|
120
|
+
except ForbiddenError as e:
|
|
121
|
+
return JSONResponse(self.response.forbidden(e), 403)
|
|
122
|
+
|
|
123
|
+
return self.response.ok()
|
|
124
|
+
|
|
125
|
+
return endpoint
|
|
126
|
+
|
|
127
|
+
def update_many(self, User, Collection) -> Callable[..., _Response]: # type: ignore
|
|
128
|
+
ParentId = Annotated[str, Path(alias=self.parent_id_alias)]
|
|
129
|
+
|
|
130
|
+
def endpoint(user: User, parent_id: ParentId, items: Collection) -> _Response:
|
|
131
|
+
try:
|
|
132
|
+
service = self.infra.with_user(user).with_parent(parent_id).build()
|
|
133
|
+
except DoesNotExistError as e:
|
|
134
|
+
return JSONResponse(RestfulResponse(self.parent).not_found(e), 404)
|
|
135
|
+
|
|
136
|
+
try:
|
|
137
|
+
service.update_many(items)
|
|
138
|
+
except DoesNotExistError as e:
|
|
139
|
+
return JSONResponse(self.response.not_found(e), 404)
|
|
140
|
+
except ForbiddenError as e:
|
|
141
|
+
return JSONResponse(self.response.forbidden(e), 403)
|
|
142
|
+
|
|
143
|
+
return self.response.ok()
|
|
144
|
+
|
|
145
|
+
return endpoint
|
|
146
|
+
|
|
147
|
+
def replace_one(self, User, Item) -> Callable[..., _Response]: # type: ignore
|
|
148
|
+
ParentId = Annotated[str, Path(alias=self.parent_id_alias)]
|
|
149
|
+
|
|
150
|
+
def endpoint(user: User, parent_id: ParentId, item: Item) -> _Response:
|
|
151
|
+
try:
|
|
152
|
+
service = self.infra.with_user(user).with_parent(parent_id).build()
|
|
153
|
+
except DoesNotExistError as e:
|
|
154
|
+
return JSONResponse(RestfulResponse(self.parent).not_found(e), 404)
|
|
155
|
+
|
|
156
|
+
try:
|
|
157
|
+
service.replace_one(item)
|
|
158
|
+
except DoesNotExistError as e:
|
|
159
|
+
return JSONResponse(self.response.not_found(e), 404)
|
|
160
|
+
except ForbiddenError as e:
|
|
161
|
+
return JSONResponse(self.response.forbidden(e), 403)
|
|
162
|
+
|
|
163
|
+
return self.response.ok()
|
|
164
|
+
|
|
165
|
+
return endpoint
|
|
166
|
+
|
|
167
|
+
def replace_many(self, User, Collection) -> Callable[..., _Response]: # type: ignore
|
|
168
|
+
ParentId = Annotated[str, Path(alias=self.parent_id_alias)]
|
|
169
|
+
|
|
170
|
+
def endpoint(user: User, parent_id: ParentId, items: Collection) -> _Response:
|
|
171
|
+
try:
|
|
172
|
+
service = self.infra.with_user(user).with_parent(parent_id).build()
|
|
173
|
+
except DoesNotExistError as e:
|
|
174
|
+
return JSONResponse(RestfulResponse(self.parent).not_found(e), 404)
|
|
175
|
+
|
|
176
|
+
try:
|
|
177
|
+
service.replace_many(items)
|
|
178
|
+
except DoesNotExistError as e:
|
|
179
|
+
return JSONResponse(self.response.not_found(e), 404)
|
|
180
|
+
except ForbiddenError as e:
|
|
181
|
+
return JSONResponse(self.response.forbidden(e), 403)
|
|
182
|
+
|
|
183
|
+
return self.response.ok()
|
|
184
|
+
|
|
185
|
+
return endpoint
|
|
186
|
+
|
|
187
|
+
def delete_one(self, User, ItemId) -> Callable[..., _Response]: # type: ignore
|
|
188
|
+
ParentId = Annotated[str, Path(alias=self.parent_id_alias)]
|
|
189
|
+
|
|
190
|
+
def endpoint(user: User, parent_id: ParentId, item_id: ItemId) -> _Response:
|
|
191
|
+
try:
|
|
192
|
+
service = self.infra.with_user(user).with_parent(parent_id).build()
|
|
193
|
+
except DoesNotExistError as e:
|
|
194
|
+
return JSONResponse(RestfulResponse(self.parent).not_found(e), 404)
|
|
195
|
+
|
|
196
|
+
try:
|
|
197
|
+
service.delete_one(item_id)
|
|
198
|
+
except DoesNotExistError as e:
|
|
199
|
+
return JSONResponse(self.response.not_found(e), 404)
|
|
200
|
+
except ForbiddenError as e:
|
|
201
|
+
return JSONResponse(self.response.forbidden(e), 403)
|
|
202
|
+
|
|
203
|
+
return self.response.ok()
|
|
204
|
+
|
|
205
|
+
return endpoint
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
@dataclass
|
|
209
|
+
class RestfulRootResource:
|
|
210
|
+
name: RestfulName
|
|
211
|
+
infra: RestfulServiceBuilder
|
|
212
|
+
|
|
213
|
+
@cached_property
|
|
214
|
+
def response(self) -> RestfulResponse:
|
|
215
|
+
return RestfulResponse(name=self.name)
|
|
216
|
+
|
|
217
|
+
def create_one(self, User, Item) -> Callable[..., _Response]: # type: ignore
|
|
218
|
+
def endpoint(user: User, item: Item) -> _Response:
|
|
219
|
+
service = self.infra.with_user(user).build()
|
|
220
|
+
|
|
221
|
+
try:
|
|
222
|
+
item = service.create_one(item)
|
|
223
|
+
except ExistsError as e:
|
|
224
|
+
return JSONResponse(self.response.exists(e), 409)
|
|
225
|
+
except ForbiddenError as e:
|
|
226
|
+
return JSONResponse(self.response.forbidden(e), 403)
|
|
227
|
+
|
|
228
|
+
return self.response.created_one(item)
|
|
229
|
+
|
|
230
|
+
return endpoint
|
|
231
|
+
|
|
232
|
+
def create_many(self, User, Collection) -> Callable[..., _Response]: # type: ignore
|
|
233
|
+
def endpoint(user: User, items: Collection) -> _Response:
|
|
234
|
+
service = self.infra.with_user(user).build()
|
|
235
|
+
|
|
236
|
+
try:
|
|
237
|
+
return self.response.created_many(service.create_many(items))
|
|
238
|
+
except ExistsError as e:
|
|
239
|
+
return JSONResponse(self.response.exists(e), 409)
|
|
240
|
+
except ForbiddenError as e:
|
|
241
|
+
return JSONResponse(self.response.forbidden(e), 403)
|
|
242
|
+
|
|
243
|
+
return endpoint
|
|
244
|
+
|
|
245
|
+
def read_one(self, User, ItemId) -> Callable[..., _Response]: # type: ignore
|
|
246
|
+
def endpoint(user: User, item_id: ItemId) -> _Response:
|
|
247
|
+
service = self.infra.with_user(user).build()
|
|
248
|
+
|
|
249
|
+
try:
|
|
250
|
+
return self.response.found_one(service.read_one(item_id))
|
|
251
|
+
except DoesNotExistError as e:
|
|
252
|
+
return JSONResponse(self.response.not_found(e), 404)
|
|
253
|
+
except ForbiddenError as e:
|
|
254
|
+
return JSONResponse(self.response.forbidden(e), 403)
|
|
255
|
+
|
|
256
|
+
return endpoint
|
|
257
|
+
|
|
258
|
+
def read_all(self, User) -> Callable[..., _Response]: # type: ignore
|
|
259
|
+
def endpoint(user: User) -> _Response:
|
|
260
|
+
service = self.infra.with_user(user).build()
|
|
261
|
+
|
|
262
|
+
try:
|
|
263
|
+
return self.response.found_many(list(service.read_all()))
|
|
264
|
+
except ForbiddenError as e:
|
|
265
|
+
return JSONResponse(self.response.forbidden(e), 403)
|
|
266
|
+
|
|
267
|
+
return endpoint
|
|
268
|
+
|
|
269
|
+
def update_one(self, User, ItemId, Updates) -> Callable[..., _Response]: # type: ignore
|
|
270
|
+
def endpoint(user: User, item_id: ItemId, updates: Updates) -> _Response:
|
|
271
|
+
service = self.infra.with_user(user).build()
|
|
272
|
+
|
|
273
|
+
try:
|
|
274
|
+
service.update_one(item_id, **updates)
|
|
275
|
+
except DoesNotExistError as e:
|
|
276
|
+
return JSONResponse(self.response.not_found(e), 404)
|
|
277
|
+
except ForbiddenError as e:
|
|
278
|
+
return JSONResponse(self.response.forbidden(e), 403)
|
|
279
|
+
|
|
280
|
+
return self.response.ok()
|
|
281
|
+
|
|
282
|
+
return endpoint
|
|
283
|
+
|
|
284
|
+
def update_many(self, User, Collection) -> Callable[..., _Response]: # type: ignore
|
|
285
|
+
def endpoint(user: User, items: Collection) -> _Response:
|
|
286
|
+
service = self.infra.with_user(user).build()
|
|
287
|
+
|
|
288
|
+
try:
|
|
289
|
+
service.update_many(items)
|
|
290
|
+
except DoesNotExistError as e:
|
|
291
|
+
return JSONResponse(self.response.not_found(e), 404)
|
|
292
|
+
except ForbiddenError as e:
|
|
293
|
+
return JSONResponse(self.response.forbidden(e), 403)
|
|
294
|
+
|
|
295
|
+
return self.response.ok()
|
|
296
|
+
|
|
297
|
+
return endpoint
|
|
298
|
+
|
|
299
|
+
def replace_one(self, User, Item) -> Callable[..., _Response]: # type: ignore
|
|
300
|
+
def endpoint(user: User, item: Item) -> _Response:
|
|
301
|
+
service = self.infra.with_user(user).build()
|
|
302
|
+
|
|
303
|
+
try:
|
|
304
|
+
service.replace_one(item)
|
|
305
|
+
except DoesNotExistError as e:
|
|
306
|
+
return JSONResponse(self.response.not_found(e), 404)
|
|
307
|
+
except ForbiddenError as e:
|
|
308
|
+
return JSONResponse(self.response.forbidden(e), 403)
|
|
309
|
+
|
|
310
|
+
return self.response.ok()
|
|
311
|
+
|
|
312
|
+
return endpoint
|
|
313
|
+
|
|
314
|
+
def replace_many(self, User, Collection) -> Callable[..., _Response]: # type: ignore
|
|
315
|
+
def endpoint(user: User, items: Collection) -> _Response:
|
|
316
|
+
service = self.infra.with_user(user).build()
|
|
317
|
+
|
|
318
|
+
try:
|
|
319
|
+
service.replace_many(items)
|
|
320
|
+
except DoesNotExistError as e:
|
|
321
|
+
return JSONResponse(self.response.not_found(e), 404)
|
|
322
|
+
except ForbiddenError as e:
|
|
323
|
+
return JSONResponse(self.response.forbidden(e), 403)
|
|
324
|
+
|
|
325
|
+
return self.response.ok()
|
|
326
|
+
|
|
327
|
+
return endpoint
|
|
328
|
+
|
|
329
|
+
def delete_one(self, User, ItemId) -> Callable[..., _Response]: # type: ignore
|
|
330
|
+
def endpoint(user: User, item_id: ItemId) -> _Response:
|
|
331
|
+
service = self.infra.with_user(user).build()
|
|
332
|
+
|
|
333
|
+
try:
|
|
334
|
+
service.delete_one(item_id)
|
|
335
|
+
except DoesNotExistError as e:
|
|
336
|
+
return JSONResponse(self.response.not_found(e), 404)
|
|
337
|
+
except ForbiddenError as e:
|
|
338
|
+
return JSONResponse(self.response.forbidden(e), 403)
|
|
339
|
+
|
|
340
|
+
return self.response.ok()
|
|
341
|
+
|
|
342
|
+
return endpoint
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Any, Iterable
|
|
5
|
+
|
|
6
|
+
from fastapi import status
|
|
7
|
+
from fastapi.responses import JSONResponse
|
|
8
|
+
|
|
9
|
+
from apexdevkit.error import DoesNotExistError, ExistsError, ForbiddenError
|
|
10
|
+
from apexdevkit.testing import RestfulName
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class SuccessResponse(dict[str, Any]):
|
|
14
|
+
def __init__(self, status_code: int, **kwargs: Any):
|
|
15
|
+
super().__init__(
|
|
16
|
+
{
|
|
17
|
+
"status": "success",
|
|
18
|
+
"code": status_code,
|
|
19
|
+
"data": {**kwargs},
|
|
20
|
+
}
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ResourceFound(SuccessResponse):
|
|
25
|
+
def __init__(self, **kwargs: Any):
|
|
26
|
+
super().__init__(status_code=status.HTTP_200_OK, **kwargs)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class ResourceCreated(SuccessResponse):
|
|
30
|
+
def __init__(self, **kwargs: Any):
|
|
31
|
+
super().__init__(status_code=status.HTTP_201_CREATED, **kwargs)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class ErrorResponse(JSONResponse):
|
|
35
|
+
def __init__(self, status_code: int, message: str, **kwargs: Any):
|
|
36
|
+
content = {
|
|
37
|
+
"status": "fail",
|
|
38
|
+
"code": status_code,
|
|
39
|
+
"error": {"message": message},
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if kwargs:
|
|
43
|
+
content["data"] = {**kwargs}
|
|
44
|
+
|
|
45
|
+
super().__init__(status_code=status_code, content=content)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class BadRequest(ErrorResponse):
|
|
49
|
+
def __init__(self, message: str, **kwargs: Any) -> None:
|
|
50
|
+
super().__init__(
|
|
51
|
+
status_code=status.HTTP_400_BAD_REQUEST,
|
|
52
|
+
message=message,
|
|
53
|
+
**kwargs,
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class ResourceNotFound(ErrorResponse):
|
|
58
|
+
def __init__(self, message: str):
|
|
59
|
+
super().__init__(status_code=status.HTTP_404_NOT_FOUND, message=message)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class ResourceExists(ErrorResponse):
|
|
63
|
+
def __init__(self, message: str, **kwargs: Any):
|
|
64
|
+
super().__init__(
|
|
65
|
+
status_code=status.HTTP_409_CONFLICT,
|
|
66
|
+
message=message,
|
|
67
|
+
**kwargs,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@dataclass
|
|
72
|
+
class RestfulResponse:
|
|
73
|
+
name: RestfulName
|
|
74
|
+
|
|
75
|
+
def _response(self, code: int, data: Any, error: str = "") -> dict[str, Any]:
|
|
76
|
+
content: dict[str, Any] = {"code": code, "status": "success"}
|
|
77
|
+
|
|
78
|
+
if error:
|
|
79
|
+
content["status"] = "fail"
|
|
80
|
+
content["error"] = {"message": error}
|
|
81
|
+
|
|
82
|
+
match data:
|
|
83
|
+
case None:
|
|
84
|
+
content["data"] = {}
|
|
85
|
+
case list():
|
|
86
|
+
content["data"] = {self.name.plural: data, "count": len(data)}
|
|
87
|
+
case _:
|
|
88
|
+
content["data"] = {self.name.singular: data}
|
|
89
|
+
|
|
90
|
+
return content
|
|
91
|
+
|
|
92
|
+
def ok(self) -> dict[str, Any]:
|
|
93
|
+
return self._response(200, data=None)
|
|
94
|
+
|
|
95
|
+
def not_found(self, e: DoesNotExistError) -> dict[str, Any]:
|
|
96
|
+
name = self.name.singular.capitalize()
|
|
97
|
+
|
|
98
|
+
return self._response(
|
|
99
|
+
404,
|
|
100
|
+
data={"id": str(e.id)},
|
|
101
|
+
error=f"An item<{name}> with id<{e.id}> does not exist.",
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
def exists(self, e: ExistsError) -> dict[str, Any]:
|
|
105
|
+
name = self.name.singular.capitalize()
|
|
106
|
+
|
|
107
|
+
return self._response(
|
|
108
|
+
409,
|
|
109
|
+
data={"id": str(e.id)},
|
|
110
|
+
error=f"An item<{name}> with the {e} already exists.",
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
def forbidden(self, e: ForbiddenError) -> dict[str, Any]:
|
|
114
|
+
return self._response(
|
|
115
|
+
403,
|
|
116
|
+
data={"id": str(e.id)},
|
|
117
|
+
error=e.message,
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
def created_one(self, item: Any) -> dict[str, Any]:
|
|
121
|
+
return self._response(201, item)
|
|
122
|
+
|
|
123
|
+
def created_many(self, items: Iterable[Any]) -> dict[str, Any]:
|
|
124
|
+
return self._response(201, list(items))
|
|
125
|
+
|
|
126
|
+
def found_one(self, item: Any) -> dict[str, Any]:
|
|
127
|
+
return self._response(200, item)
|
|
128
|
+
|
|
129
|
+
def found_many(self, items: list[Any]) -> dict[str, Any]:
|
|
130
|
+
return self._response(200, items)
|