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.
Files changed (38) hide show
  1. {apexdevkit-1.5.21 → apexdevkit-1.5.23}/PKG-INFO +1 -1
  2. apexdevkit-1.5.23/apexdevkit/fastapi/__init__.py +10 -0
  3. {apexdevkit-1.5.21 → apexdevkit-1.5.23}/apexdevkit/fastapi/builder.py +23 -0
  4. apexdevkit-1.5.23/apexdevkit/fastapi/resource.py +342 -0
  5. apexdevkit-1.5.23/apexdevkit/fastapi/response.py +130 -0
  6. apexdevkit-1.5.23/apexdevkit/fastapi/router.py +348 -0
  7. {apexdevkit-1.5.21 → apexdevkit-1.5.23}/pyproject.toml +1 -1
  8. apexdevkit-1.5.21/apexdevkit/fastapi/__init__.py +0 -26
  9. apexdevkit-1.5.21/apexdevkit/fastapi/response.py +0 -64
  10. apexdevkit-1.5.21/apexdevkit/fastapi/router.py +0 -639
  11. {apexdevkit-1.5.21 → apexdevkit-1.5.23}/LICENSE +0 -0
  12. {apexdevkit-1.5.21 → apexdevkit-1.5.23}/README.md +0 -0
  13. {apexdevkit-1.5.21 → apexdevkit-1.5.23}/apexdevkit/__init__.py +0 -0
  14. {apexdevkit-1.5.21 → apexdevkit-1.5.23}/apexdevkit/annotation/__init__.py +0 -0
  15. {apexdevkit-1.5.21 → apexdevkit-1.5.23}/apexdevkit/annotation/deprecate.py +0 -0
  16. {apexdevkit-1.5.21 → apexdevkit-1.5.23}/apexdevkit/error.py +0 -0
  17. {apexdevkit-1.5.21 → apexdevkit-1.5.23}/apexdevkit/fastapi/dependable.py +0 -0
  18. {apexdevkit-1.5.21 → apexdevkit-1.5.23}/apexdevkit/fastapi/docs.py +0 -0
  19. {apexdevkit-1.5.21 → apexdevkit-1.5.23}/apexdevkit/fastapi/schema.py +0 -0
  20. {apexdevkit-1.5.21 → apexdevkit-1.5.23}/apexdevkit/fastapi/service.py +0 -0
  21. {apexdevkit-1.5.21 → apexdevkit-1.5.23}/apexdevkit/formatter.py +0 -0
  22. {apexdevkit-1.5.21 → apexdevkit-1.5.23}/apexdevkit/http/__init__.py +0 -0
  23. {apexdevkit-1.5.21 → apexdevkit-1.5.23}/apexdevkit/http/fake.py +0 -0
  24. {apexdevkit-1.5.21 → apexdevkit-1.5.23}/apexdevkit/http/fluent.py +0 -0
  25. {apexdevkit-1.5.21 → apexdevkit-1.5.23}/apexdevkit/http/httpx.py +0 -0
  26. {apexdevkit-1.5.21 → apexdevkit-1.5.23}/apexdevkit/http/json.py +0 -0
  27. {apexdevkit-1.5.21 → apexdevkit-1.5.23}/apexdevkit/http/url.py +0 -0
  28. {apexdevkit-1.5.21 → apexdevkit-1.5.23}/apexdevkit/py.typed +0 -0
  29. {apexdevkit-1.5.21 → apexdevkit-1.5.23}/apexdevkit/repository/__init__.py +0 -0
  30. {apexdevkit-1.5.21 → apexdevkit-1.5.23}/apexdevkit/repository/base.py +0 -0
  31. {apexdevkit-1.5.21 → apexdevkit-1.5.23}/apexdevkit/repository/connector.py +0 -0
  32. {apexdevkit-1.5.21 → apexdevkit-1.5.23}/apexdevkit/repository/database.py +0 -0
  33. {apexdevkit-1.5.21 → apexdevkit-1.5.23}/apexdevkit/repository/in_memory.py +0 -0
  34. {apexdevkit-1.5.21 → apexdevkit-1.5.23}/apexdevkit/repository/interface.py +0 -0
  35. {apexdevkit-1.5.21 → apexdevkit-1.5.23}/apexdevkit/testing/__init__.py +0 -0
  36. {apexdevkit-1.5.21 → apexdevkit-1.5.23}/apexdevkit/testing/database.py +0 -0
  37. {apexdevkit-1.5.21 → apexdevkit-1.5.23}/apexdevkit/testing/fake.py +0 -0
  38. {apexdevkit-1.5.21 → apexdevkit-1.5.23}/apexdevkit/testing/rest.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: apexdevkit
3
- Version: 1.5.21
3
+ Version: 1.5.23
4
4
  Summary: Apex Development Tools for python.
5
5
  Author: Apex Dev
6
6
  Author-email: dev@apex.ge
@@ -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)