apexdevkit 2.1.4__tar.gz → 2.2.1__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-2.1.4 → apexdevkit-2.2.1}/PKG-INFO +1 -1
- apexdevkit-2.2.1/apexdevkit/fastapi/resource.py +103 -0
- {apexdevkit-2.1.4 → apexdevkit-2.2.1}/apexdevkit/fastapi/router.py +14 -11
- {apexdevkit-2.1.4 → apexdevkit-2.2.1}/apexdevkit/fastapi/schema.py +0 -3
- {apexdevkit-2.1.4 → apexdevkit-2.2.1}/apexdevkit/fastapi/service.py +1 -53
- {apexdevkit-2.1.4 → apexdevkit-2.2.1}/pyproject.toml +2 -1
- apexdevkit-2.1.4/apexdevkit/fastapi/docs.py +0 -15
- apexdevkit-2.1.4/apexdevkit/fastapi/resource.py +0 -319
- {apexdevkit-2.1.4 → apexdevkit-2.2.1}/LICENSE +0 -0
- {apexdevkit-2.1.4 → apexdevkit-2.2.1}/README.md +0 -0
- {apexdevkit-2.1.4 → apexdevkit-2.2.1}/apexdevkit/__init__.py +0 -0
- {apexdevkit-2.1.4 → apexdevkit-2.2.1}/apexdevkit/annotation/__init__.py +0 -0
- {apexdevkit-2.1.4 → apexdevkit-2.2.1}/apexdevkit/annotation/deprecate.py +0 -0
- {apexdevkit-2.1.4 → apexdevkit-2.2.1}/apexdevkit/date.py +0 -0
- {apexdevkit-2.1.4 → apexdevkit-2.2.1}/apexdevkit/error.py +0 -0
- {apexdevkit-2.1.4 → apexdevkit-2.2.1}/apexdevkit/fastapi/__init__.py +0 -0
- {apexdevkit-2.1.4 → apexdevkit-2.2.1}/apexdevkit/fastapi/builder.py +0 -0
- {apexdevkit-2.1.4 → apexdevkit-2.2.1}/apexdevkit/fastapi/dependable.py +0 -0
- {apexdevkit-2.1.4 → apexdevkit-2.2.1}/apexdevkit/fastapi/name.py +0 -0
- {apexdevkit-2.1.4 → apexdevkit-2.2.1}/apexdevkit/fastapi/response.py +0 -0
- {apexdevkit-2.1.4 → apexdevkit-2.2.1}/apexdevkit/formatter.py +0 -0
- {apexdevkit-2.1.4 → apexdevkit-2.2.1}/apexdevkit/key_fn.py +0 -0
- {apexdevkit-2.1.4 → apexdevkit-2.2.1}/apexdevkit/py.typed +0 -0
- {apexdevkit-2.1.4 → apexdevkit-2.2.1}/apexdevkit/query/__init__.py +0 -0
- {apexdevkit-2.1.4 → apexdevkit-2.2.1}/apexdevkit/query/generator.py +0 -0
- {apexdevkit-2.1.4 → apexdevkit-2.2.1}/apexdevkit/query/query.py +0 -0
- {apexdevkit-2.1.4 → apexdevkit-2.2.1}/apexdevkit/repository/__init__.py +0 -0
- {apexdevkit-2.1.4 → apexdevkit-2.2.1}/apexdevkit/repository/core/__init__.py +0 -0
- {apexdevkit-2.1.4 → apexdevkit-2.2.1}/apexdevkit/repository/core/database.py +0 -0
- {apexdevkit-2.1.4 → apexdevkit-2.2.1}/apexdevkit/repository/core/decorator.py +0 -0
- {apexdevkit-2.1.4 → apexdevkit-2.2.1}/apexdevkit/repository/core/interface.py +0 -0
- {apexdevkit-2.1.4 → apexdevkit-2.2.1}/apexdevkit/repository/core/mixin.py +0 -0
- {apexdevkit-2.1.4 → apexdevkit-2.2.1}/apexdevkit/repository/in_memory/__init__.py +0 -0
- {apexdevkit-2.1.4 → apexdevkit-2.2.1}/apexdevkit/repository/in_memory/mixin.py +0 -0
- {apexdevkit-2.1.4 → apexdevkit-2.2.1}/apexdevkit/repository/in_memory/repository.py +0 -0
- {apexdevkit-2.1.4 → apexdevkit-2.2.1}/apexdevkit/repository/in_memory/store.py +0 -0
- {apexdevkit-2.1.4 → apexdevkit-2.2.1}/apexdevkit/repository/sql/__init__.py +0 -0
- {apexdevkit-2.1.4 → apexdevkit-2.2.1}/apexdevkit/repository/sql/connector.py +0 -0
- {apexdevkit-2.1.4 → apexdevkit-2.2.1}/apexdevkit/repository/sql/field.py +0 -0
- {apexdevkit-2.1.4 → apexdevkit-2.2.1}/apexdevkit/repository/sql/mssql/__init__.py +0 -0
- {apexdevkit-2.1.4 → apexdevkit-2.2.1}/apexdevkit/repository/sql/mssql/repository.py +0 -0
- {apexdevkit-2.1.4 → apexdevkit-2.2.1}/apexdevkit/repository/sql/mssql/table.py +0 -0
- {apexdevkit-2.1.4 → apexdevkit-2.2.1}/apexdevkit/repository/sql/sqlite/__init__.py +0 -0
- {apexdevkit-2.1.4 → apexdevkit-2.2.1}/apexdevkit/repository/sql/sqlite/repository.py +0 -0
- {apexdevkit-2.1.4 → apexdevkit-2.2.1}/apexdevkit/repository/sql/sqlite/table.py +0 -0
- {apexdevkit-2.1.4 → apexdevkit-2.2.1}/apexdevkit/server.py +0 -0
- {apexdevkit-2.1.4 → apexdevkit-2.2.1}/apexdevkit/sync/__init__.py +0 -0
- {apexdevkit-2.1.4 → apexdevkit-2.2.1}/apexdevkit/sync/observer.py +0 -0
- {apexdevkit-2.1.4 → apexdevkit-2.2.1}/apexdevkit/sync/source.py +0 -0
- {apexdevkit-2.1.4 → apexdevkit-2.2.1}/apexdevkit/sync/sync.py +0 -0
- {apexdevkit-2.1.4 → apexdevkit-2.2.1}/apexdevkit/sync/target.py +0 -0
- {apexdevkit-2.1.4 → apexdevkit-2.2.1}/apexdevkit/testing.py +0 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
from collections.abc import Callable
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from fastapi.responses import JSONResponse
|
|
6
|
+
|
|
7
|
+
from apexdevkit.error import DoesNotExistError, ExistsError, ForbiddenError
|
|
8
|
+
from apexdevkit.fastapi.response import RestfulResponse
|
|
9
|
+
|
|
10
|
+
_Response = JSONResponse | dict[str, Any]
|
|
11
|
+
_Endpoint = Callable[..., _Response]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass(frozen=True)
|
|
15
|
+
class RestfulResource:
|
|
16
|
+
response: RestfulResponse
|
|
17
|
+
|
|
18
|
+
def create_one_endpoint(self, Service, Item) -> _Endpoint:
|
|
19
|
+
def endpoint(service: Service, item: Item) -> _Response:
|
|
20
|
+
try:
|
|
21
|
+
result = service.create_one(item)
|
|
22
|
+
except ExistsError as e:
|
|
23
|
+
return JSONResponse(self.response.exists(e), 409)
|
|
24
|
+
except ForbiddenError as e:
|
|
25
|
+
return JSONResponse(self.response.forbidden(e), 403)
|
|
26
|
+
|
|
27
|
+
return self.response.created_one(result)
|
|
28
|
+
|
|
29
|
+
return endpoint
|
|
30
|
+
|
|
31
|
+
def read_one_endpoint(self, Service, ItemId) -> _Endpoint:
|
|
32
|
+
def endpoint(service: Service, item_id: ItemId) -> _Response:
|
|
33
|
+
try:
|
|
34
|
+
result = service.read_one(item_id)
|
|
35
|
+
except DoesNotExistError as e:
|
|
36
|
+
return JSONResponse(self.response.not_found(e), 404)
|
|
37
|
+
except ForbiddenError as e:
|
|
38
|
+
return JSONResponse(self.response.forbidden(e), 403)
|
|
39
|
+
|
|
40
|
+
return self.response.found_one(result)
|
|
41
|
+
|
|
42
|
+
return endpoint
|
|
43
|
+
|
|
44
|
+
def read_many_endpoint(self, Service, QueryParams) -> _Endpoint:
|
|
45
|
+
def endpoint(service: Service, params: QueryParams) -> _Response:
|
|
46
|
+
try:
|
|
47
|
+
result = list(service.read_many(**dict(params)))
|
|
48
|
+
except ForbiddenError as e:
|
|
49
|
+
return JSONResponse(self.response.forbidden(e), 403)
|
|
50
|
+
|
|
51
|
+
return self.response.found_many(result)
|
|
52
|
+
|
|
53
|
+
return endpoint
|
|
54
|
+
|
|
55
|
+
def read_all_endpoint(self, Service) -> _Endpoint:
|
|
56
|
+
def endpoint(service: Service) -> _Response:
|
|
57
|
+
try:
|
|
58
|
+
result = list(service.read_all())
|
|
59
|
+
except ForbiddenError as e:
|
|
60
|
+
return JSONResponse(self.response.forbidden(e), 403)
|
|
61
|
+
|
|
62
|
+
return self.response.found_many(result)
|
|
63
|
+
|
|
64
|
+
return endpoint
|
|
65
|
+
|
|
66
|
+
def update_one_endpoint(self, Service, ItemId, Updates) -> _Endpoint:
|
|
67
|
+
def endpoint(service: Service, item_id: ItemId, updates: Updates) -> _Response:
|
|
68
|
+
try:
|
|
69
|
+
service.update_one(item_id, **updates)
|
|
70
|
+
except DoesNotExistError as e:
|
|
71
|
+
return JSONResponse(self.response.not_found(e), 404)
|
|
72
|
+
except ForbiddenError as e:
|
|
73
|
+
return JSONResponse(self.response.forbidden(e), 403)
|
|
74
|
+
|
|
75
|
+
return self.response.ok()
|
|
76
|
+
|
|
77
|
+
return endpoint
|
|
78
|
+
|
|
79
|
+
def replace_one_endpoint(self, Service, Item) -> _Endpoint:
|
|
80
|
+
def endpoint(service: Service, item: Item) -> _Response:
|
|
81
|
+
try:
|
|
82
|
+
service.replace_one(item)
|
|
83
|
+
except DoesNotExistError as e:
|
|
84
|
+
return JSONResponse(self.response.not_found(e), 404)
|
|
85
|
+
except ForbiddenError as e:
|
|
86
|
+
return JSONResponse(self.response.forbidden(e), 403)
|
|
87
|
+
|
|
88
|
+
return self.response.ok()
|
|
89
|
+
|
|
90
|
+
return endpoint
|
|
91
|
+
|
|
92
|
+
def delete_one_endpoint(self, Service, ItemId) -> _Endpoint:
|
|
93
|
+
def endpoint(service: Service, item_id: ItemId) -> _Response:
|
|
94
|
+
try:
|
|
95
|
+
service.delete_one(item_id)
|
|
96
|
+
except DoesNotExistError as e:
|
|
97
|
+
return JSONResponse(self.response.not_found(e), 404)
|
|
98
|
+
except ForbiddenError as e:
|
|
99
|
+
return JSONResponse(self.response.forbidden(e), 403)
|
|
100
|
+
|
|
101
|
+
return self.response.ok()
|
|
102
|
+
|
|
103
|
+
return endpoint
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from collections.abc import Callable
|
|
2
2
|
from dataclasses import dataclass, field
|
|
3
3
|
from enum import Enum
|
|
4
|
-
from typing import Annotated, Any, Protocol, Self
|
|
4
|
+
from typing import Annotated, Any, Protocol, Self
|
|
5
5
|
|
|
6
6
|
from fastapi import APIRouter, Depends, Path, Query
|
|
7
7
|
from fastapi.responses import JSONResponse
|
|
@@ -47,8 +47,6 @@ class RouterWithHiddenUnderscoreRoutes(APIRouter):
|
|
|
47
47
|
|
|
48
48
|
_Response = JSONResponse | dict[str, Any]
|
|
49
49
|
|
|
50
|
-
T = TypeVar("T")
|
|
51
|
-
|
|
52
50
|
|
|
53
51
|
class Dependency(Protocol): # pragma: no cover
|
|
54
52
|
def as_dependable(self) -> type[RestfulService]:
|
|
@@ -128,7 +126,7 @@ class RestfulRouter:
|
|
|
128
126
|
) -> Self:
|
|
129
127
|
self.router.add_api_route(
|
|
130
128
|
"",
|
|
131
|
-
self.resource.
|
|
129
|
+
self.resource.create_one_endpoint(
|
|
132
130
|
Service=self._resolve(dependency),
|
|
133
131
|
Item=Annotated[
|
|
134
132
|
RawItem,
|
|
@@ -153,7 +151,7 @@ class RestfulRouter:
|
|
|
153
151
|
) -> Self:
|
|
154
152
|
self.router.add_api_route(
|
|
155
153
|
self.item_path,
|
|
156
|
-
self.resource.
|
|
154
|
+
self.resource.read_one_endpoint(
|
|
157
155
|
Service=self._resolve(dependency),
|
|
158
156
|
ItemId=Annotated[
|
|
159
157
|
str,
|
|
@@ -179,10 +177,13 @@ class RestfulRouter:
|
|
|
179
177
|
) -> Self:
|
|
180
178
|
self.router.add_api_route(
|
|
181
179
|
"",
|
|
182
|
-
self.resource.
|
|
180
|
+
self.resource.read_many_endpoint(
|
|
183
181
|
Service=self._resolve(dependency),
|
|
184
182
|
QueryParams=Annotated[
|
|
185
|
-
Schema(self.name.singular).optional_schema_for(
|
|
183
|
+
Schema(self.name.singular).optional_schema_for(
|
|
184
|
+
action="ReadMany",
|
|
185
|
+
fields=query,
|
|
186
|
+
),
|
|
186
187
|
Query(),
|
|
187
188
|
],
|
|
188
189
|
),
|
|
@@ -204,7 +205,9 @@ class RestfulRouter:
|
|
|
204
205
|
) -> Self:
|
|
205
206
|
self.router.add_api_route(
|
|
206
207
|
"",
|
|
207
|
-
self.resource.
|
|
208
|
+
self.resource.read_all_endpoint(
|
|
209
|
+
Service=self._resolve(dependency),
|
|
210
|
+
),
|
|
208
211
|
methods=["GET"],
|
|
209
212
|
status_code=200,
|
|
210
213
|
responses={},
|
|
@@ -223,7 +226,7 @@ class RestfulRouter:
|
|
|
223
226
|
) -> Self:
|
|
224
227
|
self.router.add_api_route(
|
|
225
228
|
self.item_path,
|
|
226
|
-
self.resource.
|
|
229
|
+
self.resource.update_one_endpoint(
|
|
227
230
|
Service=self._resolve(dependency),
|
|
228
231
|
ItemId=Annotated[
|
|
229
232
|
str,
|
|
@@ -252,7 +255,7 @@ class RestfulRouter:
|
|
|
252
255
|
) -> Self:
|
|
253
256
|
self.router.add_api_route(
|
|
254
257
|
"",
|
|
255
|
-
self.resource.
|
|
258
|
+
self.resource.replace_one_endpoint(
|
|
256
259
|
Service=self._resolve(dependency),
|
|
257
260
|
Item=Annotated[
|
|
258
261
|
RawItem,
|
|
@@ -277,7 +280,7 @@ class RestfulRouter:
|
|
|
277
280
|
) -> Self:
|
|
278
281
|
self.router.add_api_route(
|
|
279
282
|
self.item_path,
|
|
280
|
-
self.resource.
|
|
283
|
+
self.resource.delete_one_endpoint(
|
|
281
284
|
Service=self._resolve(dependency),
|
|
282
285
|
ItemId=Annotated[
|
|
283
286
|
str,
|
|
@@ -2,61 +2,36 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from collections.abc import Iterable, Mapping
|
|
4
4
|
from dataclasses import dataclass
|
|
5
|
-
from functools import cached_property
|
|
6
5
|
from typing import TYPE_CHECKING, Any
|
|
7
6
|
|
|
8
|
-
from apexdevkit.repository import
|
|
7
|
+
from apexdevkit.repository import Entity, Repository
|
|
9
8
|
|
|
10
9
|
if TYPE_CHECKING:
|
|
11
10
|
from apexdevkit.formatter import Formatter
|
|
12
|
-
from apexdevkit.repository.core.interface import BatchRepository
|
|
13
11
|
|
|
14
12
|
RawItem = Mapping[str, Any]
|
|
15
13
|
RawCollection = Iterable[RawItem]
|
|
16
14
|
|
|
17
15
|
|
|
18
|
-
class _RawItemWithId(dict[str, Any]):
|
|
19
|
-
def __post_init__(self) -> None:
|
|
20
|
-
assert "id" in self
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
RawCollectionWithId = Iterable[_RawItemWithId]
|
|
24
|
-
|
|
25
|
-
|
|
26
16
|
class RestfulService: # pragma: no cover
|
|
27
17
|
def create_one(self, item: RawItem) -> RawItem:
|
|
28
18
|
raise NotImplementedError(self.create_one.__name__)
|
|
29
19
|
|
|
30
|
-
def create_many(self, items: RawCollection) -> RawCollection:
|
|
31
|
-
raise NotImplementedError(self.create_many.__name__)
|
|
32
|
-
|
|
33
20
|
def read_one(self, item_id: str) -> RawItem:
|
|
34
21
|
raise NotImplementedError(self.read_one.__name__)
|
|
35
22
|
|
|
36
23
|
def read_many(self, **params: Any) -> RawCollection:
|
|
37
24
|
raise NotImplementedError(self.read_many.__name__)
|
|
38
25
|
|
|
39
|
-
def filter_with(self, options: RawItem) -> RawCollection:
|
|
40
|
-
raise NotImplementedError(self.filter_with.__name__)
|
|
41
|
-
|
|
42
|
-
def aggregation_with(self, options: RawItem) -> RawItem:
|
|
43
|
-
raise NotImplementedError(self.filter_with.__name__)
|
|
44
|
-
|
|
45
26
|
def read_all(self) -> RawCollection:
|
|
46
27
|
raise NotImplementedError(self.read_all.__name__)
|
|
47
28
|
|
|
48
29
|
def update_one(self, item_id: str, **with_fields: Any) -> RawItem:
|
|
49
30
|
raise NotImplementedError(self.update_one.__name__)
|
|
50
31
|
|
|
51
|
-
def update_many(self, items: RawCollectionWithId) -> RawCollection:
|
|
52
|
-
raise NotImplementedError(self.update_many.__name__)
|
|
53
|
-
|
|
54
32
|
def replace_one(self, item: RawItem) -> RawItem:
|
|
55
33
|
raise NotImplementedError(self.replace_one.__name__)
|
|
56
34
|
|
|
57
|
-
def replace_many(self, items: RawCollection) -> RawCollection:
|
|
58
|
-
raise NotImplementedError(self.replace_many.__name__)
|
|
59
|
-
|
|
60
35
|
def delete_one(self, item_id: str) -> None:
|
|
61
36
|
raise NotImplementedError(self.delete_one.__name__)
|
|
62
37
|
|
|
@@ -67,21 +42,9 @@ class RestfulRepository[ItemT: Entity](RestfulService):
|
|
|
67
42
|
|
|
68
43
|
formatter: Formatter[Mapping[str, Any], ItemT]
|
|
69
44
|
|
|
70
|
-
@cached_property
|
|
71
|
-
def _batch(self) -> BatchRepository[ItemT]:
|
|
72
|
-
return BruteForceBatch(inner=self.repository)
|
|
73
|
-
|
|
74
45
|
def create_one(self, item: RawItem) -> RawItem:
|
|
75
46
|
return self.formatter.dump(self.repository.create(self.formatter.load(item)))
|
|
76
47
|
|
|
77
|
-
def create_many(self, items: RawCollection) -> RawCollection:
|
|
78
|
-
return [
|
|
79
|
-
self.formatter.dump(item)
|
|
80
|
-
for item in self._batch.load(
|
|
81
|
-
[self.formatter.load(fields) for fields in items]
|
|
82
|
-
)
|
|
83
|
-
]
|
|
84
|
-
|
|
85
48
|
def read_one(self, item_id: str) -> RawItem:
|
|
86
49
|
return self.formatter.dump(self.repository.read(item_id))
|
|
87
50
|
|
|
@@ -96,25 +59,10 @@ class RestfulRepository[ItemT: Entity](RestfulService):
|
|
|
96
59
|
|
|
97
60
|
return data
|
|
98
61
|
|
|
99
|
-
def update_many(self, items: RawCollectionWithId) -> RawCollection:
|
|
100
|
-
updates = []
|
|
101
|
-
for fields in items:
|
|
102
|
-
data = dict(self.formatter.dump(self.repository.read(fields["id"])))
|
|
103
|
-
data.update(**fields)
|
|
104
|
-
|
|
105
|
-
updates.append(self.formatter.load(data))
|
|
106
|
-
|
|
107
|
-
return [self.formatter.dump(item) for item in self._batch.renew(updates)]
|
|
108
|
-
|
|
109
62
|
def replace_one(self, item: RawItem) -> RawItem:
|
|
110
63
|
self.repository.update(self.formatter.load(item))
|
|
111
64
|
|
|
112
65
|
return item
|
|
113
66
|
|
|
114
|
-
def replace_many(self, items: RawCollection) -> RawCollection:
|
|
115
|
-
updated = self._batch.renew([self.formatter.load(item) for item in items])
|
|
116
|
-
|
|
117
|
-
return [self.formatter.dump(item) for item in updated]
|
|
118
|
-
|
|
119
67
|
def delete_one(self, item_id: str) -> None:
|
|
120
68
|
self.repository.delete(item_id)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "apexdevkit"
|
|
3
|
-
version = "2.1
|
|
3
|
+
version = "2.2.1"
|
|
4
4
|
description = "Apex Development Tools for python."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [
|
|
@@ -80,6 +80,7 @@ ignore = [
|
|
|
80
80
|
[tool.mypy]
|
|
81
81
|
exclude = [
|
|
82
82
|
"apexdevkit/fastapi/dependable.py",
|
|
83
|
+
"apexdevkit/fastapi/resource.py",
|
|
83
84
|
"apexdevkit/fastapi/schema.py",
|
|
84
85
|
]
|
|
85
86
|
ignore_missing_imports = true
|
|
@@ -1,319 +0,0 @@
|
|
|
1
|
-
import re
|
|
2
|
-
from collections.abc import Callable
|
|
3
|
-
from dataclasses import dataclass
|
|
4
|
-
from typing import Any, Union
|
|
5
|
-
|
|
6
|
-
from fastapi.responses import JSONResponse
|
|
7
|
-
from pydantic import BaseModel
|
|
8
|
-
|
|
9
|
-
from apexdevkit.error import DoesNotExistError, ExistsError, ForbiddenError
|
|
10
|
-
from apexdevkit.fastapi.response import RestfulResponse
|
|
11
|
-
from apexdevkit.query.query import (
|
|
12
|
-
Aggregation,
|
|
13
|
-
AggregationOption,
|
|
14
|
-
DateValue,
|
|
15
|
-
Filter,
|
|
16
|
-
FooterOptions,
|
|
17
|
-
Leaf,
|
|
18
|
-
ListValue,
|
|
19
|
-
NullValue,
|
|
20
|
-
NumericValue,
|
|
21
|
-
Operation,
|
|
22
|
-
Operator,
|
|
23
|
-
Page,
|
|
24
|
-
QueryOptions,
|
|
25
|
-
Sort,
|
|
26
|
-
StringValue,
|
|
27
|
-
)
|
|
28
|
-
|
|
29
|
-
_Response = JSONResponse | dict[str, Any]
|
|
30
|
-
_Endpoint = Callable[..., _Response]
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
@dataclass(frozen=True)
|
|
34
|
-
class RestfulResource:
|
|
35
|
-
response: RestfulResponse
|
|
36
|
-
|
|
37
|
-
def create_one(self, Service, Item) -> _Endpoint: # type: ignore
|
|
38
|
-
def endpoint(service: Service, item: Item) -> _Response:
|
|
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, Service, Collection) -> _Endpoint: # type: ignore
|
|
51
|
-
def endpoint(service: Service, items: Collection) -> _Response:
|
|
52
|
-
try:
|
|
53
|
-
return self.response.created_many(service.create_many(items))
|
|
54
|
-
except ExistsError as e:
|
|
55
|
-
return JSONResponse(self.response.exists(e), 409)
|
|
56
|
-
except ForbiddenError as e:
|
|
57
|
-
return JSONResponse(self.response.forbidden(e), 403)
|
|
58
|
-
|
|
59
|
-
return endpoint
|
|
60
|
-
|
|
61
|
-
def read_one(self, Service, ItemId) -> _Endpoint: # type: ignore
|
|
62
|
-
def endpoint(service: Service, item_id: ItemId) -> _Response:
|
|
63
|
-
try:
|
|
64
|
-
return self.response.found_one(service.read_one(item_id))
|
|
65
|
-
except DoesNotExistError as e:
|
|
66
|
-
return JSONResponse(self.response.not_found(e), 404)
|
|
67
|
-
except ForbiddenError as e:
|
|
68
|
-
return JSONResponse(self.response.forbidden(e), 403)
|
|
69
|
-
|
|
70
|
-
return endpoint
|
|
71
|
-
|
|
72
|
-
def read_many(self, Service, QueryParams) -> _Endpoint: # type: ignore
|
|
73
|
-
def endpoint(service: Service, params: QueryParams) -> _Response:
|
|
74
|
-
try:
|
|
75
|
-
return self.response.found_many(list(service.read_many(**dict(params))))
|
|
76
|
-
except ForbiddenError as e:
|
|
77
|
-
return JSONResponse(self.response.forbidden(e), 403)
|
|
78
|
-
|
|
79
|
-
return endpoint
|
|
80
|
-
|
|
81
|
-
def filter_with(self, Service, QueryOptions) -> _Endpoint: # type: ignore
|
|
82
|
-
def endpoint(service: Service, options: QueryOptions) -> _Response:
|
|
83
|
-
try:
|
|
84
|
-
return self.response.found_many(list(service.filter_with(options)))
|
|
85
|
-
except ForbiddenError as e:
|
|
86
|
-
return JSONResponse(self.response.forbidden(e), 403)
|
|
87
|
-
|
|
88
|
-
return endpoint
|
|
89
|
-
|
|
90
|
-
def aggregation_with(self, Service, FilterOptions) -> _Endpoint: # type: ignore
|
|
91
|
-
def endpoint(service: Service, options: FilterOptions) -> _Response:
|
|
92
|
-
try:
|
|
93
|
-
return {
|
|
94
|
-
"status": "success",
|
|
95
|
-
"code": 200,
|
|
96
|
-
"aggregations": service.aggregation_with(options),
|
|
97
|
-
}
|
|
98
|
-
except ForbiddenError as e:
|
|
99
|
-
return JSONResponse(self.response.forbidden(e), 403)
|
|
100
|
-
|
|
101
|
-
return endpoint
|
|
102
|
-
|
|
103
|
-
def read_all(self, Service) -> _Endpoint: # type: ignore
|
|
104
|
-
def endpoint(service: Service) -> _Response:
|
|
105
|
-
try:
|
|
106
|
-
return self.response.found_many(list(service.read_all()))
|
|
107
|
-
except ForbiddenError as e:
|
|
108
|
-
return JSONResponse(self.response.forbidden(e), 403)
|
|
109
|
-
|
|
110
|
-
return endpoint
|
|
111
|
-
|
|
112
|
-
def update_one(self, Service, ItemId, Updates) -> _Endpoint: # type: ignore
|
|
113
|
-
def endpoint(service: Service, item_id: ItemId, updates: Updates) -> _Response:
|
|
114
|
-
try:
|
|
115
|
-
service.update_one(item_id, **updates)
|
|
116
|
-
except DoesNotExistError as e:
|
|
117
|
-
return JSONResponse(self.response.not_found(e), 404)
|
|
118
|
-
except ForbiddenError as e:
|
|
119
|
-
return JSONResponse(self.response.forbidden(e), 403)
|
|
120
|
-
|
|
121
|
-
return self.response.ok()
|
|
122
|
-
|
|
123
|
-
return endpoint
|
|
124
|
-
|
|
125
|
-
def update_many(self, Service, Collection) -> _Endpoint: # type: ignore
|
|
126
|
-
def endpoint(service: Service, items: Collection) -> _Response:
|
|
127
|
-
try:
|
|
128
|
-
service.update_many(items)
|
|
129
|
-
except DoesNotExistError as e:
|
|
130
|
-
return JSONResponse(self.response.not_found(e), 404)
|
|
131
|
-
except ForbiddenError as e:
|
|
132
|
-
return JSONResponse(self.response.forbidden(e), 403)
|
|
133
|
-
|
|
134
|
-
return self.response.ok()
|
|
135
|
-
|
|
136
|
-
return endpoint
|
|
137
|
-
|
|
138
|
-
def replace_one(self, Service, Item) -> _Endpoint: # type: ignore
|
|
139
|
-
def endpoint(service: Service, item: Item) -> _Response:
|
|
140
|
-
try:
|
|
141
|
-
service.replace_one(item)
|
|
142
|
-
except DoesNotExistError as e:
|
|
143
|
-
return JSONResponse(self.response.not_found(e), 404)
|
|
144
|
-
except ForbiddenError as e:
|
|
145
|
-
return JSONResponse(self.response.forbidden(e), 403)
|
|
146
|
-
|
|
147
|
-
return self.response.ok()
|
|
148
|
-
|
|
149
|
-
return endpoint
|
|
150
|
-
|
|
151
|
-
def replace_many(self, Service, Collection) -> _Endpoint: # type: ignore
|
|
152
|
-
def endpoint(service: Service, items: Collection) -> _Response:
|
|
153
|
-
try:
|
|
154
|
-
service.replace_many(items)
|
|
155
|
-
except DoesNotExistError as e:
|
|
156
|
-
return JSONResponse(self.response.not_found(e), 404)
|
|
157
|
-
except ForbiddenError as e:
|
|
158
|
-
return JSONResponse(self.response.forbidden(e), 403)
|
|
159
|
-
|
|
160
|
-
return self.response.ok()
|
|
161
|
-
|
|
162
|
-
return endpoint
|
|
163
|
-
|
|
164
|
-
def delete_one(self, Service, ItemId) -> _Endpoint: # type: ignore
|
|
165
|
-
def endpoint(service: Service, item_id: ItemId) -> _Response:
|
|
166
|
-
try:
|
|
167
|
-
service.delete_one(item_id)
|
|
168
|
-
except DoesNotExistError as e:
|
|
169
|
-
return JSONResponse(self.response.not_found(e), 404)
|
|
170
|
-
except ForbiddenError as e:
|
|
171
|
-
return JSONResponse(self.response.forbidden(e), 403)
|
|
172
|
-
|
|
173
|
-
return self.response.ok()
|
|
174
|
-
|
|
175
|
-
return endpoint
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
@dataclass
|
|
179
|
-
class _ColumnName:
|
|
180
|
-
raw: str
|
|
181
|
-
|
|
182
|
-
def __str__(self) -> str:
|
|
183
|
-
return re.sub(r"(?<!^)(?=[A-Z])", "_", self.raw).lower()
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
class _NumericItem(BaseModel):
|
|
187
|
-
value: int
|
|
188
|
-
exponent: int
|
|
189
|
-
|
|
190
|
-
def to_value(self) -> NumericValue:
|
|
191
|
-
return NumericValue(self.value, self.exponent)
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
class _StringItem(BaseModel):
|
|
195
|
-
value: str
|
|
196
|
-
|
|
197
|
-
def to_value(self) -> StringValue:
|
|
198
|
-
return StringValue(self.value)
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
class _DateItem(BaseModel):
|
|
202
|
-
date: str
|
|
203
|
-
|
|
204
|
-
def to_value(self) -> DateValue:
|
|
205
|
-
return DateValue(self.date)
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
class _ListItem(BaseModel):
|
|
209
|
-
values: list[_NumericItem | _StringItem | _DateItem]
|
|
210
|
-
|
|
211
|
-
def to_value(self) -> ListValue:
|
|
212
|
-
return ListValue([item.to_value() for item in self.values])
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
class _LeafItem(BaseModel):
|
|
216
|
-
name: str
|
|
217
|
-
values: list[_NumericItem | _StringItem | _DateItem]
|
|
218
|
-
|
|
219
|
-
def to_value(self) -> Leaf:
|
|
220
|
-
return Leaf(
|
|
221
|
-
name=str(_ColumnName(self.name)),
|
|
222
|
-
values=[item.to_value() for item in self.values],
|
|
223
|
-
)
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
class _OperatorItem(BaseModel):
|
|
227
|
-
operation: Operation
|
|
228
|
-
operands: list[Union["_LeafItem", "_OperatorItem"]]
|
|
229
|
-
|
|
230
|
-
def to_value(self) -> Operator:
|
|
231
|
-
return Operator(self.operation, [item.to_value() for item in self.operands])
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
class _SortItem(BaseModel):
|
|
235
|
-
name: str
|
|
236
|
-
is_descending: bool
|
|
237
|
-
|
|
238
|
-
def to_value(self) -> Sort:
|
|
239
|
-
return Sort(str(_ColumnName(self.name)), self.is_descending)
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
class _PageItem(BaseModel):
|
|
243
|
-
page: int | None
|
|
244
|
-
length: int | None
|
|
245
|
-
offset: int | None
|
|
246
|
-
|
|
247
|
-
def to_value(self) -> Page:
|
|
248
|
-
return Page(self.page, self.length, self.offset)
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
class _FilterItem(BaseModel):
|
|
252
|
-
args: list[_NumericItem | _StringItem | _DateItem | _ListItem | None]
|
|
253
|
-
|
|
254
|
-
def to_value(self) -> Filter:
|
|
255
|
-
return Filter(
|
|
256
|
-
[arg.to_value() if arg is not None else None for arg in self.args]
|
|
257
|
-
)
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
class _AggregationOptionItem(BaseModel):
|
|
261
|
-
name: str | None
|
|
262
|
-
aggregation: Aggregation
|
|
263
|
-
|
|
264
|
-
def to_value(self) -> AggregationOption:
|
|
265
|
-
return AggregationOption(
|
|
266
|
-
str(_ColumnName(self.name)) if self.name else None,
|
|
267
|
-
self.aggregation,
|
|
268
|
-
)
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
class _FooterOptions(BaseModel):
|
|
272
|
-
filter: _FilterItem | None
|
|
273
|
-
condition: _OperatorItem | None
|
|
274
|
-
aggregations: list[_AggregationOptionItem]
|
|
275
|
-
|
|
276
|
-
def to_footer_options(self) -> FooterOptions:
|
|
277
|
-
return FooterOptions(
|
|
278
|
-
filter=self.filter.to_value() if self.filter else None,
|
|
279
|
-
condition=self.condition.to_value() if self.condition else None,
|
|
280
|
-
aggregations=[item.to_value() for item in self.aggregations],
|
|
281
|
-
)
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
class _QueryOptions(BaseModel):
|
|
285
|
-
filter: _FilterItem | None
|
|
286
|
-
condition: _OperatorItem | None
|
|
287
|
-
ordering: list[_SortItem]
|
|
288
|
-
paging: _PageItem
|
|
289
|
-
|
|
290
|
-
def to_query_options(self) -> QueryOptions:
|
|
291
|
-
return QueryOptions(
|
|
292
|
-
filter=self.filter.to_value() if self.filter else None,
|
|
293
|
-
condition=self.condition.to_value() if self.condition else None,
|
|
294
|
-
ordering=[item.to_value() for item in self.ordering],
|
|
295
|
-
paging=self.paging.to_value(),
|
|
296
|
-
)
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
class _SummaryItem(BaseModel):
|
|
300
|
-
name: str | None
|
|
301
|
-
aggregation: Aggregation
|
|
302
|
-
result: NumericValue | StringValue | DateValue | NullValue
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
class _SummaryListEnvelope(BaseModel):
|
|
306
|
-
count: int
|
|
307
|
-
summaries: list[_SummaryItem]
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
class SummaryResponse(BaseModel):
|
|
311
|
-
status: str
|
|
312
|
-
code: int
|
|
313
|
-
data: _SummaryListEnvelope
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
class SumResponse(BaseModel):
|
|
317
|
-
status: str
|
|
318
|
-
code: int
|
|
319
|
-
count: int
|
|
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
|
|
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
|