apexdevkit 2.1.3__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.3 → apexdevkit-2.2.1}/PKG-INFO +2 -2
- {apexdevkit-2.1.3 → apexdevkit-2.2.1}/apexdevkit/__init__.py +1 -1
- {apexdevkit-2.1.3 → apexdevkit-2.2.1}/apexdevkit/annotation/deprecate.py +3 -3
- {apexdevkit-2.1.3 → apexdevkit-2.2.1}/apexdevkit/date.py +16 -16
- {apexdevkit-2.1.3 → apexdevkit-2.2.1}/apexdevkit/fastapi/__init__.py +2 -2
- {apexdevkit-2.1.3 → apexdevkit-2.2.1}/apexdevkit/fastapi/builder.py +6 -3
- apexdevkit-2.2.1/apexdevkit/fastapi/resource.py +103 -0
- {apexdevkit-2.1.3 → apexdevkit-2.2.1}/apexdevkit/fastapi/router.py +21 -11
- {apexdevkit-2.1.3 → apexdevkit-2.2.1}/apexdevkit/fastapi/schema.py +0 -3
- apexdevkit-2.2.1/apexdevkit/fastapi/service.py +68 -0
- {apexdevkit-2.1.3 → apexdevkit-2.2.1}/apexdevkit/query/generator.py +16 -16
- {apexdevkit-2.1.3 → apexdevkit-2.2.1}/apexdevkit/repository/__init__.py +5 -8
- {apexdevkit-2.1.3 → apexdevkit-2.2.1}/apexdevkit/repository/core/__init__.py +4 -8
- {apexdevkit-2.1.3 → apexdevkit-2.2.1}/apexdevkit/repository/core/database.py +1 -1
- {apexdevkit-2.1.3 → apexdevkit-2.2.1}/apexdevkit/repository/in_memory/__init__.py +1 -1
- {apexdevkit-2.1.3 → apexdevkit-2.2.1}/apexdevkit/repository/in_memory/repository.py +4 -1
- {apexdevkit-2.1.3 → apexdevkit-2.2.1}/apexdevkit/repository/in_memory/store.py +4 -2
- {apexdevkit-2.1.3 → apexdevkit-2.2.1}/apexdevkit/repository/sql/__init__.py +1 -1
- {apexdevkit-2.1.3 → apexdevkit-2.2.1}/apexdevkit/repository/sql/connector.py +6 -3
- {apexdevkit-2.1.3 → apexdevkit-2.2.1}/apexdevkit/repository/sql/field.py +17 -10
- {apexdevkit-2.1.3 → apexdevkit-2.2.1}/apexdevkit/repository/sql/mssql/repository.py +2 -2
- {apexdevkit-2.1.3 → apexdevkit-2.2.1}/apexdevkit/repository/sql/mssql/table.py +6 -3
- {apexdevkit-2.1.3 → apexdevkit-2.2.1}/apexdevkit/repository/sql/sqlite/__init__.py +1 -1
- {apexdevkit-2.1.3 → apexdevkit-2.2.1}/apexdevkit/repository/sql/sqlite/repository.py +6 -3
- {apexdevkit-2.1.3 → apexdevkit-2.2.1}/apexdevkit/repository/sql/sqlite/table.py +6 -3
- {apexdevkit-2.1.3 → apexdevkit-2.2.1}/apexdevkit/server.py +7 -4
- {apexdevkit-2.1.3 → apexdevkit-2.2.1}/apexdevkit/sync/__init__.py +4 -4
- {apexdevkit-2.1.3 → apexdevkit-2.2.1}/apexdevkit/sync/observer.py +5 -3
- {apexdevkit-2.1.3 → apexdevkit-2.2.1}/apexdevkit/sync/source.py +7 -7
- {apexdevkit-2.1.3 → apexdevkit-2.2.1}/apexdevkit/sync/sync.py +12 -4
- {apexdevkit-2.1.3 → apexdevkit-2.2.1}/apexdevkit/testing.py +6 -4
- {apexdevkit-2.1.3 → apexdevkit-2.2.1}/pyproject.toml +61 -38
- apexdevkit-2.1.3/apexdevkit/fastapi/docs.py +0 -15
- apexdevkit-2.1.3/apexdevkit/fastapi/resource.py +0 -319
- apexdevkit-2.1.3/apexdevkit/fastapi/service.py +0 -118
- {apexdevkit-2.1.3 → apexdevkit-2.2.1}/LICENSE +0 -0
- {apexdevkit-2.1.3 → apexdevkit-2.2.1}/README.md +0 -0
- {apexdevkit-2.1.3 → apexdevkit-2.2.1}/apexdevkit/annotation/__init__.py +0 -0
- {apexdevkit-2.1.3 → apexdevkit-2.2.1}/apexdevkit/error.py +0 -0
- {apexdevkit-2.1.3 → apexdevkit-2.2.1}/apexdevkit/fastapi/dependable.py +0 -0
- {apexdevkit-2.1.3 → apexdevkit-2.2.1}/apexdevkit/fastapi/name.py +0 -0
- {apexdevkit-2.1.3 → apexdevkit-2.2.1}/apexdevkit/fastapi/response.py +0 -0
- {apexdevkit-2.1.3 → apexdevkit-2.2.1}/apexdevkit/formatter.py +0 -0
- {apexdevkit-2.1.3 → apexdevkit-2.2.1}/apexdevkit/key_fn.py +0 -0
- {apexdevkit-2.1.3 → apexdevkit-2.2.1}/apexdevkit/py.typed +0 -0
- {apexdevkit-2.1.3 → apexdevkit-2.2.1}/apexdevkit/query/__init__.py +11 -11
- {apexdevkit-2.1.3 → apexdevkit-2.2.1}/apexdevkit/query/query.py +0 -0
- {apexdevkit-2.1.3 → apexdevkit-2.2.1}/apexdevkit/repository/core/decorator.py +0 -0
- {apexdevkit-2.1.3 → apexdevkit-2.2.1}/apexdevkit/repository/core/interface.py +0 -0
- {apexdevkit-2.1.3 → apexdevkit-2.2.1}/apexdevkit/repository/core/mixin.py +0 -0
- {apexdevkit-2.1.3 → apexdevkit-2.2.1}/apexdevkit/repository/in_memory/mixin.py +0 -0
- {apexdevkit-2.1.3 → apexdevkit-2.2.1}/apexdevkit/repository/sql/mssql/__init__.py +1 -1
- {apexdevkit-2.1.3 → apexdevkit-2.2.1}/apexdevkit/sync/target.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: apexdevkit
|
|
3
|
-
Version: 2.1
|
|
3
|
+
Version: 2.2.1
|
|
4
4
|
Summary: Apex Development Tools for python.
|
|
5
5
|
License-File: LICENSE
|
|
6
6
|
Author: Apex Dev
|
|
@@ -14,7 +14,7 @@ Classifier: Programming Language :: Python :: 3.15
|
|
|
14
14
|
Requires-Dist: fastapi
|
|
15
15
|
Requires-Dist: pydantic
|
|
16
16
|
Requires-Dist: pymssql (==2.*)
|
|
17
|
-
Requires-Dist: pypebbles (==0.
|
|
17
|
+
Requires-Dist: pypebbles (==0.4.*)
|
|
18
18
|
Requires-Dist: python-dotenv
|
|
19
19
|
Requires-Dist: sentry-sdk[fastapi]
|
|
20
20
|
Requires-Dist: uvicorn
|
|
@@ -25,7 +25,7 @@ def deprecated(warning: str) -> Callable[[F], F]:
|
|
|
25
25
|
if inspect.isfunction(obj) or inspect.ismethod(obj):
|
|
26
26
|
return _wrap_function(obj, warning)
|
|
27
27
|
if inspect.isclass(obj):
|
|
28
|
-
return cast(F, _deprecate_class(obj, warning))
|
|
28
|
+
return cast("F", _deprecate_class(obj, warning))
|
|
29
29
|
raise TypeError(
|
|
30
30
|
f"Unsupported type for deprecation: {type(obj)}"
|
|
31
31
|
) # pragma: no cover
|
|
@@ -38,7 +38,7 @@ def _wrap_function[F: Callable[..., Any]](func: F, warning: str) -> F:
|
|
|
38
38
|
warn(warning, category=DeprecationWarning, stacklevel=2)
|
|
39
39
|
return func(*args, **kwargs)
|
|
40
40
|
|
|
41
|
-
return cast(F, wrapper)
|
|
41
|
+
return cast("F", wrapper)
|
|
42
42
|
|
|
43
43
|
|
|
44
44
|
def _deprecate_class(cls: type, warning: str) -> type:
|
|
@@ -47,4 +47,4 @@ def _deprecate_class(cls: type, warning: str) -> type:
|
|
|
47
47
|
warn(warning, category=DeprecationWarning, stacklevel=2)
|
|
48
48
|
super().__init__(*args, **kwargs)
|
|
49
49
|
|
|
50
|
-
return cast(type, DeprecatedClass)
|
|
50
|
+
return cast("type", DeprecatedClass)
|
|
@@ -36,28 +36,28 @@ class DateTime:
|
|
|
36
36
|
# Date in format of "yyyy-mm-dd"
|
|
37
37
|
@classmethod
|
|
38
38
|
def from_date(cls, date: str, time_zone: ZoneInfo | None = None) -> DateTime:
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
39
|
+
return cls(
|
|
40
|
+
timestamp_ms=int(
|
|
41
|
+
datetime.strptime(date, "%Y-%m-%d")
|
|
42
|
+
.replace(tzinfo=time_zone or ZoneInfo("UTC"))
|
|
43
|
+
.timestamp()
|
|
44
|
+
* 1000
|
|
45
|
+
)
|
|
46
|
+
)
|
|
47
47
|
|
|
48
48
|
# Date in format of "yyyy-mm-ddTHH:MM:SS"
|
|
49
49
|
@classmethod
|
|
50
50
|
def from_date_time(
|
|
51
51
|
cls, date_time: str, time_zone: ZoneInfo | None = None
|
|
52
52
|
) -> DateTime:
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
53
|
+
return cls(
|
|
54
|
+
timestamp_ms=int(
|
|
55
|
+
datetime.strptime(date_time, "%Y-%m-%dT%H:%M:%S")
|
|
56
|
+
.replace(tzinfo=time_zone or ZoneInfo("UTC"))
|
|
57
|
+
.timestamp()
|
|
58
|
+
* 1000
|
|
59
|
+
)
|
|
60
|
+
)
|
|
61
61
|
|
|
62
62
|
@classmethod
|
|
63
63
|
def from_timestamp(cls, timestamp: float) -> DateTime:
|
|
@@ -5,9 +5,9 @@ from apexdevkit.fastapi.service import RestfulRepository
|
|
|
5
5
|
|
|
6
6
|
__all__ = [
|
|
7
7
|
"FastApiBuilder",
|
|
8
|
-
"RestfulServiceBuilder",
|
|
9
|
-
"inject",
|
|
10
8
|
"RestfulRepository",
|
|
11
9
|
"RestfulRouter",
|
|
10
|
+
"RestfulServiceBuilder",
|
|
12
11
|
"RouterWithHiddenUnderscoreRoutes",
|
|
12
|
+
"inject",
|
|
13
13
|
]
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from abc import ABC, abstractmethod
|
|
4
|
-
from collections.abc import Mapping, MutableSet
|
|
5
4
|
from dataclasses import dataclass, field
|
|
6
|
-
from typing import Any, Protocol, Self
|
|
5
|
+
from typing import TYPE_CHECKING, Any, Protocol, Self
|
|
7
6
|
|
|
8
7
|
from fastapi import APIRouter, FastAPI
|
|
9
8
|
from fastapi.middleware.cors import CORSMiddleware
|
|
@@ -12,7 +11,11 @@ from fastapi.responses import JSONResponse
|
|
|
12
11
|
from apexdevkit.annotation import deprecated
|
|
13
12
|
from apexdevkit.error import ApiError
|
|
14
13
|
from apexdevkit.fastapi.dependable import DependableBuilder
|
|
15
|
-
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from collections.abc import Mapping, MutableSet
|
|
17
|
+
|
|
18
|
+
from apexdevkit.fastapi.service import RestfulService
|
|
16
19
|
|
|
17
20
|
|
|
18
21
|
@dataclass
|
|
@@ -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]:
|
|
@@ -123,11 +121,12 @@ class RestfulRouter:
|
|
|
123
121
|
def with_create_one(
|
|
124
122
|
self,
|
|
125
123
|
dependency: Dependency | None = None,
|
|
124
|
+
*,
|
|
126
125
|
is_documented: bool = True,
|
|
127
126
|
) -> Self:
|
|
128
127
|
self.router.add_api_route(
|
|
129
128
|
"",
|
|
130
|
-
self.resource.
|
|
129
|
+
self.resource.create_one_endpoint(
|
|
131
130
|
Service=self._resolve(dependency),
|
|
132
131
|
Item=Annotated[
|
|
133
132
|
RawItem,
|
|
@@ -147,11 +146,12 @@ class RestfulRouter:
|
|
|
147
146
|
def with_read_one(
|
|
148
147
|
self,
|
|
149
148
|
dependency: Dependency | None = None,
|
|
149
|
+
*,
|
|
150
150
|
is_documented: bool = True,
|
|
151
151
|
) -> Self:
|
|
152
152
|
self.router.add_api_route(
|
|
153
153
|
self.item_path,
|
|
154
|
-
self.resource.
|
|
154
|
+
self.resource.read_one_endpoint(
|
|
155
155
|
Service=self._resolve(dependency),
|
|
156
156
|
ItemId=Annotated[
|
|
157
157
|
str,
|
|
@@ -172,14 +172,18 @@ class RestfulRouter:
|
|
|
172
172
|
self,
|
|
173
173
|
query: FluentDict[Any],
|
|
174
174
|
dependency: Dependency | None = None,
|
|
175
|
+
*,
|
|
175
176
|
is_documented: bool = True,
|
|
176
177
|
) -> Self:
|
|
177
178
|
self.router.add_api_route(
|
|
178
179
|
"",
|
|
179
|
-
self.resource.
|
|
180
|
+
self.resource.read_many_endpoint(
|
|
180
181
|
Service=self._resolve(dependency),
|
|
181
182
|
QueryParams=Annotated[
|
|
182
|
-
Schema(self.name.singular).optional_schema_for(
|
|
183
|
+
Schema(self.name.singular).optional_schema_for(
|
|
184
|
+
action="ReadMany",
|
|
185
|
+
fields=query,
|
|
186
|
+
),
|
|
183
187
|
Query(),
|
|
184
188
|
],
|
|
185
189
|
),
|
|
@@ -196,11 +200,14 @@ class RestfulRouter:
|
|
|
196
200
|
def with_read_all(
|
|
197
201
|
self,
|
|
198
202
|
dependency: Dependency | None = None,
|
|
203
|
+
*,
|
|
199
204
|
is_documented: bool = True,
|
|
200
205
|
) -> Self:
|
|
201
206
|
self.router.add_api_route(
|
|
202
207
|
"",
|
|
203
|
-
self.resource.
|
|
208
|
+
self.resource.read_all_endpoint(
|
|
209
|
+
Service=self._resolve(dependency),
|
|
210
|
+
),
|
|
204
211
|
methods=["GET"],
|
|
205
212
|
status_code=200,
|
|
206
213
|
responses={},
|
|
@@ -214,11 +221,12 @@ class RestfulRouter:
|
|
|
214
221
|
def with_update_one(
|
|
215
222
|
self,
|
|
216
223
|
dependency: Dependency | None = None,
|
|
224
|
+
*,
|
|
217
225
|
is_documented: bool = True,
|
|
218
226
|
) -> Self:
|
|
219
227
|
self.router.add_api_route(
|
|
220
228
|
self.item_path,
|
|
221
|
-
self.resource.
|
|
229
|
+
self.resource.update_one_endpoint(
|
|
222
230
|
Service=self._resolve(dependency),
|
|
223
231
|
ItemId=Annotated[
|
|
224
232
|
str,
|
|
@@ -242,11 +250,12 @@ class RestfulRouter:
|
|
|
242
250
|
def with_replace_one(
|
|
243
251
|
self,
|
|
244
252
|
dependency: Dependency | None = None,
|
|
253
|
+
*,
|
|
245
254
|
is_documented: bool = True,
|
|
246
255
|
) -> Self:
|
|
247
256
|
self.router.add_api_route(
|
|
248
257
|
"",
|
|
249
|
-
self.resource.
|
|
258
|
+
self.resource.replace_one_endpoint(
|
|
250
259
|
Service=self._resolve(dependency),
|
|
251
260
|
Item=Annotated[
|
|
252
261
|
RawItem,
|
|
@@ -266,11 +275,12 @@ class RestfulRouter:
|
|
|
266
275
|
def with_delete_one(
|
|
267
276
|
self,
|
|
268
277
|
dependency: Dependency | None = None,
|
|
278
|
+
*,
|
|
269
279
|
is_documented: bool = True,
|
|
270
280
|
) -> Self:
|
|
271
281
|
self.router.add_api_route(
|
|
272
282
|
self.item_path,
|
|
273
|
-
self.resource.
|
|
283
|
+
self.resource.delete_one_endpoint(
|
|
274
284
|
Service=self._resolve(dependency),
|
|
275
285
|
ItemId=Annotated[
|
|
276
286
|
str,
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Iterable, Mapping
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from typing import TYPE_CHECKING, Any
|
|
6
|
+
|
|
7
|
+
from apexdevkit.repository import Entity, Repository
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from apexdevkit.formatter import Formatter
|
|
11
|
+
|
|
12
|
+
RawItem = Mapping[str, Any]
|
|
13
|
+
RawCollection = Iterable[RawItem]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class RestfulService: # pragma: no cover
|
|
17
|
+
def create_one(self, item: RawItem) -> RawItem:
|
|
18
|
+
raise NotImplementedError(self.create_one.__name__)
|
|
19
|
+
|
|
20
|
+
def read_one(self, item_id: str) -> RawItem:
|
|
21
|
+
raise NotImplementedError(self.read_one.__name__)
|
|
22
|
+
|
|
23
|
+
def read_many(self, **params: Any) -> RawCollection:
|
|
24
|
+
raise NotImplementedError(self.read_many.__name__)
|
|
25
|
+
|
|
26
|
+
def read_all(self) -> RawCollection:
|
|
27
|
+
raise NotImplementedError(self.read_all.__name__)
|
|
28
|
+
|
|
29
|
+
def update_one(self, item_id: str, **with_fields: Any) -> RawItem:
|
|
30
|
+
raise NotImplementedError(self.update_one.__name__)
|
|
31
|
+
|
|
32
|
+
def replace_one(self, item: RawItem) -> RawItem:
|
|
33
|
+
raise NotImplementedError(self.replace_one.__name__)
|
|
34
|
+
|
|
35
|
+
def delete_one(self, item_id: str) -> None:
|
|
36
|
+
raise NotImplementedError(self.delete_one.__name__)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass(frozen=True)
|
|
40
|
+
class RestfulRepository[ItemT: Entity](RestfulService):
|
|
41
|
+
repository: Repository[ItemT]
|
|
42
|
+
|
|
43
|
+
formatter: Formatter[Mapping[str, Any], ItemT]
|
|
44
|
+
|
|
45
|
+
def create_one(self, item: RawItem) -> RawItem:
|
|
46
|
+
return self.formatter.dump(self.repository.create(self.formatter.load(item)))
|
|
47
|
+
|
|
48
|
+
def read_one(self, item_id: str) -> RawItem:
|
|
49
|
+
return self.formatter.dump(self.repository.read(item_id))
|
|
50
|
+
|
|
51
|
+
def read_all(self) -> RawCollection:
|
|
52
|
+
return [self.formatter.dump(item) for item in self.repository]
|
|
53
|
+
|
|
54
|
+
def update_one(self, item_id: str, **with_fields: Any) -> RawItem:
|
|
55
|
+
data = dict(self.formatter.dump(self.repository.read(item_id)))
|
|
56
|
+
data.update(**with_fields)
|
|
57
|
+
|
|
58
|
+
self.repository.update(self.formatter.load(data))
|
|
59
|
+
|
|
60
|
+
return data
|
|
61
|
+
|
|
62
|
+
def replace_one(self, item: RawItem) -> RawItem:
|
|
63
|
+
self.repository.update(self.formatter.load(item))
|
|
64
|
+
|
|
65
|
+
return item
|
|
66
|
+
|
|
67
|
+
def delete_one(self, item_id: str) -> None:
|
|
68
|
+
self.repository.delete(item_id)
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from collections import defaultdict
|
|
4
|
-
from collections.abc import Iterable, Mapping
|
|
5
4
|
from dataclasses import dataclass, field
|
|
6
|
-
from typing import Any, ClassVar, Protocol
|
|
5
|
+
from typing import TYPE_CHECKING, Any, ClassVar, Protocol
|
|
7
6
|
|
|
8
7
|
from apexdevkit.annotation import deprecated
|
|
9
8
|
from apexdevkit.error import ForbiddenError
|
|
@@ -24,44 +23,45 @@ from apexdevkit.query.query import (
|
|
|
24
23
|
)
|
|
25
24
|
from apexdevkit.repository import Database, DatabaseCommand
|
|
26
25
|
|
|
27
|
-
|
|
26
|
+
if TYPE_CHECKING:
|
|
27
|
+
from collections.abc import Iterable, Mapping
|
|
28
28
|
|
|
29
29
|
|
|
30
|
-
class _Loader(Protocol
|
|
31
|
-
def load(self, raw: Mapping[str, Any]) ->
|
|
30
|
+
class _Loader[T](Protocol):
|
|
31
|
+
def load(self, raw: Mapping[str, Any]) -> T:
|
|
32
32
|
pass
|
|
33
33
|
|
|
34
34
|
|
|
35
35
|
@dataclass
|
|
36
|
-
class DefaultMsSqlFilter[
|
|
36
|
+
class DefaultMsSqlFilter[T]:
|
|
37
37
|
_database: Database = field(init=False)
|
|
38
38
|
_query_builder: MsSqlQueryBuilder = field(init=False)
|
|
39
39
|
_options: QueryOptions = field(init=False)
|
|
40
|
-
_formatter: _Loader[
|
|
40
|
+
_formatter: _Loader[T] = field(init=False)
|
|
41
41
|
|
|
42
|
-
def with_database(self, value: Database) -> DefaultMsSqlFilter[
|
|
42
|
+
def with_database(self, value: Database) -> DefaultMsSqlFilter[T]:
|
|
43
43
|
self._database = value
|
|
44
44
|
|
|
45
45
|
return self
|
|
46
46
|
|
|
47
|
-
def with_query_builder(self, value: MsSqlQueryBuilder) -> DefaultMsSqlFilter[
|
|
47
|
+
def with_query_builder(self, value: MsSqlQueryBuilder) -> DefaultMsSqlFilter[T]:
|
|
48
48
|
self._query_builder = value
|
|
49
49
|
|
|
50
50
|
return self
|
|
51
51
|
|
|
52
|
-
def with_options(self, value: QueryOptions) -> DefaultMsSqlFilter[
|
|
52
|
+
def with_options(self, value: QueryOptions) -> DefaultMsSqlFilter[T]:
|
|
53
53
|
self._options = value
|
|
54
54
|
|
|
55
55
|
return self
|
|
56
56
|
|
|
57
|
-
def with_formatter(self, value: _Loader[
|
|
57
|
+
def with_formatter(self, value: _Loader[T]) -> DefaultMsSqlFilter[T]:
|
|
58
58
|
self._formatter = value
|
|
59
59
|
|
|
60
60
|
return self
|
|
61
61
|
|
|
62
|
-
def retrieve(self) -> Iterable[
|
|
62
|
+
def retrieve(self) -> Iterable[T]:
|
|
63
63
|
if not any(sort.name == "id" for sort in self._options.ordering):
|
|
64
|
-
self._options.ordering.append(Sort("id", False))
|
|
64
|
+
self._options.ordering.append(Sort(name="id", is_descending=False))
|
|
65
65
|
|
|
66
66
|
result = self._database.execute(
|
|
67
67
|
self._query_builder.filter(self._options)
|
|
@@ -101,7 +101,7 @@ class DefaultMsSqlAggregate:
|
|
|
101
101
|
for aggregation in self._options.aggregations
|
|
102
102
|
for value in [
|
|
103
103
|
result[
|
|
104
|
-
f"{aggregation.name
|
|
104
|
+
f"{aggregation.name or 'general'}"
|
|
105
105
|
f"_{aggregation.aggregation.value.lower()}"
|
|
106
106
|
]
|
|
107
107
|
]
|
|
@@ -215,7 +215,7 @@ class MsSqlField:
|
|
|
215
215
|
|
|
216
216
|
return result
|
|
217
217
|
|
|
218
|
-
def as_order_part(self, is_descending: bool = False) -> str:
|
|
218
|
+
def as_order_part(self, *, is_descending: bool = False) -> str:
|
|
219
219
|
result = self.name
|
|
220
220
|
|
|
221
221
|
if is_descending:
|
|
@@ -278,7 +278,7 @@ class MsSqlOrderGenerator:
|
|
|
278
278
|
return f"ORDER BY {clause}"
|
|
279
279
|
|
|
280
280
|
def generate_one(self, item: Sort) -> str:
|
|
281
|
-
return self.field_for(item.name).as_order_part(item.is_descending)
|
|
281
|
+
return self.field_for(item.name).as_order_part(is_descending=item.is_descending)
|
|
282
282
|
|
|
283
283
|
def field_for(self, name: str) -> MsSqlField:
|
|
284
284
|
for f in self.fields:
|
|
@@ -19,23 +19,20 @@ from .in_memory import (
|
|
|
19
19
|
from .sql import MsSqlRepository, SqliteRepository
|
|
20
20
|
|
|
21
21
|
__all__ = [
|
|
22
|
-
"Entity",
|
|
23
|
-
# Core
|
|
24
22
|
"BruteForceBatch",
|
|
23
|
+
"CacheMixin",
|
|
25
24
|
"Connection",
|
|
26
25
|
"Connector",
|
|
27
26
|
"Cursor",
|
|
28
27
|
"Database",
|
|
29
28
|
"DatabaseCommand",
|
|
30
|
-
"
|
|
31
|
-
"Repository",
|
|
32
|
-
"RepositoryDecorator",
|
|
33
|
-
# In-memory
|
|
34
|
-
"CacheMixin",
|
|
29
|
+
"Entity",
|
|
35
30
|
"InMemoryByteStore",
|
|
36
31
|
"InMemoryRepository",
|
|
37
32
|
"KeyValueStore",
|
|
38
|
-
# SQL
|
|
39
33
|
"MsSqlRepository",
|
|
34
|
+
"NoRepository",
|
|
35
|
+
"Repository",
|
|
36
|
+
"RepositoryDecorator",
|
|
40
37
|
"SqliteRepository",
|
|
41
38
|
]
|
|
@@ -11,22 +11,18 @@ from .interface import Entity, ItemT, KeyFn, Repository
|
|
|
11
11
|
from .mixin import ContainsMixin
|
|
12
12
|
|
|
13
13
|
__all__ = [
|
|
14
|
-
|
|
14
|
+
"BruteForceBatch",
|
|
15
15
|
"Connection",
|
|
16
16
|
"ConnectionContextManager",
|
|
17
17
|
"Connector",
|
|
18
|
+
"ContainsMixin",
|
|
18
19
|
"Cursor",
|
|
19
20
|
"Database",
|
|
20
21
|
"DatabaseCommand",
|
|
21
|
-
# Decorator
|
|
22
|
-
"RepositoryDecorator",
|
|
23
|
-
"BruteForceBatch",
|
|
24
|
-
# Interface
|
|
25
22
|
"Entity",
|
|
26
23
|
"ItemT",
|
|
27
24
|
"KeyFn",
|
|
28
|
-
"Repository",
|
|
29
25
|
"NoRepository",
|
|
30
|
-
|
|
31
|
-
"
|
|
26
|
+
"Repository",
|
|
27
|
+
"RepositoryDecorator",
|
|
32
28
|
]
|
|
@@ -106,6 +106,6 @@ class ConnectionContextManager(AbstractContextManager[Connection]):
|
|
|
106
106
|
def __enter__(self) -> Connection:
|
|
107
107
|
return self.connection
|
|
108
108
|
|
|
109
|
-
def __exit__(self, exc_type:
|
|
109
|
+
def __exit__(self, exc_type: object, exc_value: object, traceback: object) -> None:
|
|
110
110
|
self.connection.cursor().close()
|
|
111
111
|
self.connection.close()
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from collections.abc import Iterator
|
|
4
3
|
from contextlib import suppress
|
|
5
4
|
from dataclasses import dataclass, field
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
6
|
|
|
7
7
|
from apexdevkit.error import DoesNotExistError, ExistsError
|
|
8
8
|
from apexdevkit.key_fn import AttributeKey
|
|
@@ -10,6 +10,9 @@ from apexdevkit.repository.core import ContainsMixin, ItemT, KeyFn, Repository
|
|
|
10
10
|
|
|
11
11
|
from .store import InMemoryByteStore, KeyValueStore
|
|
12
12
|
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from collections.abc import Iterator
|
|
15
|
+
|
|
13
16
|
|
|
14
17
|
@dataclass(frozen=True)
|
|
15
18
|
class InMemoryRepository(ContainsMixin, Repository[ItemT]):
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from collections.abc import Iterable
|
|
4
3
|
from dataclasses import dataclass, field
|
|
5
|
-
from typing import Protocol
|
|
4
|
+
from typing import TYPE_CHECKING, Protocol
|
|
6
5
|
|
|
7
6
|
from apexdevkit.formatter import Formatter, PickleFormatter
|
|
8
7
|
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from collections.abc import Iterable
|
|
10
|
+
|
|
9
11
|
|
|
10
12
|
class KeyValueStore[ItemT](Protocol): # pragma: no cover
|
|
11
13
|
def count(self) -> int:
|