apexdevkit 1.33.1__tar.gz → 2.0.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-1.33.1 → apexdevkit-2.0.1}/PKG-INFO +3 -4
- {apexdevkit-1.33.1 → apexdevkit-2.0.1}/apexdevkit/annotation/deprecate.py +1 -1
- {apexdevkit-1.33.1 → apexdevkit-2.0.1}/apexdevkit/date.py +9 -19
- {apexdevkit-1.33.1 → apexdevkit-2.0.1}/apexdevkit/error.py +1 -1
- {apexdevkit-1.33.1 → apexdevkit-2.0.1}/apexdevkit/fastapi/builder.py +3 -3
- {apexdevkit-1.33.1 → apexdevkit-2.0.1}/apexdevkit/fastapi/docs.py +2 -2
- {apexdevkit-1.33.1 → apexdevkit-2.0.1}/apexdevkit/fastapi/name.py +1 -1
- {apexdevkit-1.33.1 → apexdevkit-2.0.1}/apexdevkit/fastapi/router.py +2 -120
- {apexdevkit-1.33.1 → apexdevkit-2.0.1}/apexdevkit/fastapi/schema.py +17 -81
- {apexdevkit-1.33.1 → apexdevkit-2.0.1}/apexdevkit/fastapi/service.py +3 -4
- {apexdevkit-1.33.1 → apexdevkit-2.0.1}/apexdevkit/formatter.py +20 -33
- {apexdevkit-1.33.1 → apexdevkit-2.0.1}/apexdevkit/query/generator.py +2 -2
- {apexdevkit-1.33.1 → apexdevkit-2.0.1}/apexdevkit/repository/core/mixin.py +4 -4
- {apexdevkit-1.33.1 → apexdevkit-2.0.1}/apexdevkit/repository/in_memory/repository.py +1 -1
- {apexdevkit-1.33.1 → apexdevkit-2.0.1}/apexdevkit/repository/in_memory/store.py +4 -5
- {apexdevkit-1.33.1 → apexdevkit-2.0.1}/apexdevkit/repository/sql/connector.py +7 -7
- apexdevkit-2.0.1/apexdevkit/repository/sql/mssql/__init__.py +9 -0
- apexdevkit-2.0.1/apexdevkit/repository/sql/mssql/repository.py +79 -0
- apexdevkit-1.33.1/apexdevkit/repository/sql/mssql.py → apexdevkit-2.0.1/apexdevkit/repository/sql/mssql/table.py +39 -117
- apexdevkit-2.0.1/apexdevkit/repository/sql/sqlite/__init__.py +7 -0
- apexdevkit-2.0.1/apexdevkit/repository/sql/sqlite/repository.py +63 -0
- apexdevkit-1.33.1/apexdevkit/repository/sql/sqlite.py → apexdevkit-2.0.1/apexdevkit/repository/sql/sqlite/table.py +31 -91
- {apexdevkit-1.33.1 → apexdevkit-2.0.1}/apexdevkit/server.py +20 -13
- {apexdevkit-1.33.1 → apexdevkit-2.0.1}/apexdevkit/sync/source.py +6 -6
- {apexdevkit-1.33.1 → apexdevkit-2.0.1}/apexdevkit/sync/sync.py +6 -6
- {apexdevkit-1.33.1 → apexdevkit-2.0.1}/apexdevkit/sync/target.py +4 -4
- {apexdevkit-1.33.1 → apexdevkit-2.0.1}/pyproject.toml +5 -10
- apexdevkit-1.33.1/apexdevkit/environment.py +0 -10
- apexdevkit-1.33.1/apexdevkit/fluent.py +0 -44
- apexdevkit-1.33.1/apexdevkit/http/__init__.py +0 -19
- apexdevkit-1.33.1/apexdevkit/http/fake.py +0 -92
- apexdevkit-1.33.1/apexdevkit/http/fluent.py +0 -131
- apexdevkit-1.33.1/apexdevkit/http/httpx/__init__.py +0 -10
- apexdevkit-1.33.1/apexdevkit/http/httpx/client.py +0 -198
- apexdevkit-1.33.1/apexdevkit/http/httpx/hooks.py +0 -85
- apexdevkit-1.33.1/apexdevkit/http/json.py +0 -5
- apexdevkit-1.33.1/apexdevkit/http/url.py +0 -9
- apexdevkit-1.33.1/apexdevkit/repository/core/multi.py +0 -119
- apexdevkit-1.33.1/apexdevkit/security/__init__.py +0 -8
- apexdevkit-1.33.1/apexdevkit/security/core.py +0 -18
- apexdevkit-1.33.1/apexdevkit/security/hashing.py +0 -21
- apexdevkit-1.33.1/apexdevkit/testing/__init__.py +0 -9
- apexdevkit-1.33.1/apexdevkit/testing/database.py +0 -42
- apexdevkit-1.33.1/apexdevkit/testing/fake.py +0 -224
- apexdevkit-1.33.1/apexdevkit/testing/rest.py +0 -281
- apexdevkit-1.33.1/apexdevkit/value.py +0 -64
- {apexdevkit-1.33.1 → apexdevkit-2.0.1}/LICENSE +0 -0
- {apexdevkit-1.33.1 → apexdevkit-2.0.1}/README.md +0 -0
- {apexdevkit-1.33.1 → apexdevkit-2.0.1}/apexdevkit/__init__.py +0 -0
- {apexdevkit-1.33.1 → apexdevkit-2.0.1}/apexdevkit/annotation/__init__.py +0 -0
- {apexdevkit-1.33.1 → apexdevkit-2.0.1}/apexdevkit/fastapi/__init__.py +0 -0
- {apexdevkit-1.33.1 → apexdevkit-2.0.1}/apexdevkit/fastapi/dependable.py +0 -0
- {apexdevkit-1.33.1 → apexdevkit-2.0.1}/apexdevkit/fastapi/resource.py +0 -0
- {apexdevkit-1.33.1 → apexdevkit-2.0.1}/apexdevkit/fastapi/response.py +0 -0
- {apexdevkit-1.33.1 → apexdevkit-2.0.1}/apexdevkit/key_fn.py +0 -0
- {apexdevkit-1.33.1 → apexdevkit-2.0.1}/apexdevkit/py.typed +0 -0
- {apexdevkit-1.33.1 → apexdevkit-2.0.1}/apexdevkit/query/__init__.py +0 -0
- {apexdevkit-1.33.1 → apexdevkit-2.0.1}/apexdevkit/query/query.py +0 -0
- {apexdevkit-1.33.1 → apexdevkit-2.0.1}/apexdevkit/repository/__init__.py +0 -0
- {apexdevkit-1.33.1 → apexdevkit-2.0.1}/apexdevkit/repository/core/__init__.py +0 -0
- {apexdevkit-1.33.1 → apexdevkit-2.0.1}/apexdevkit/repository/core/database.py +0 -0
- {apexdevkit-1.33.1 → apexdevkit-2.0.1}/apexdevkit/repository/core/decorator.py +0 -0
- {apexdevkit-1.33.1 → apexdevkit-2.0.1}/apexdevkit/repository/core/interface.py +0 -0
- {apexdevkit-1.33.1 → apexdevkit-2.0.1}/apexdevkit/repository/in_memory/__init__.py +0 -0
- {apexdevkit-1.33.1 → apexdevkit-2.0.1}/apexdevkit/repository/in_memory/mixin.py +0 -0
- {apexdevkit-1.33.1 → apexdevkit-2.0.1}/apexdevkit/repository/sql/__init__.py +0 -0
- {apexdevkit-1.33.1 → apexdevkit-2.0.1}/apexdevkit/repository/sql/field.py +0 -0
- {apexdevkit-1.33.1 → apexdevkit-2.0.1}/apexdevkit/sync/__init__.py +0 -0
- {apexdevkit-1.33.1 → apexdevkit-2.0.1}/apexdevkit/sync/observer.py +0 -0
|
@@ -1,20 +1,19 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: apexdevkit
|
|
3
|
-
Version:
|
|
3
|
+
Version: 2.0.1
|
|
4
4
|
Summary: Apex Development Tools for python.
|
|
5
5
|
License-File: LICENSE
|
|
6
6
|
Author: Apex Dev
|
|
7
7
|
Author-email: dev@apex.ge
|
|
8
|
-
Requires-Python: >=3.
|
|
8
|
+
Requires-Python: >=3.12,<4
|
|
9
9
|
Classifier: Programming Language :: Python :: 3
|
|
10
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
11
10
|
Classifier: Programming Language :: Python :: 3.12
|
|
12
11
|
Classifier: Programming Language :: Python :: 3.13
|
|
13
12
|
Classifier: Programming Language :: Python :: 3.14
|
|
14
13
|
Requires-Dist: fastapi
|
|
15
|
-
Requires-Dist: httpx
|
|
16
14
|
Requires-Dist: pydantic
|
|
17
15
|
Requires-Dist: pymssql (==2.*)
|
|
16
|
+
Requires-Dist: pypebbles (==0.2.*)
|
|
18
17
|
Requires-Dist: python-dotenv
|
|
19
18
|
Requires-Dist: sentry-sdk[fastapi]
|
|
20
19
|
Requires-Dist: uvicorn
|
|
@@ -33,7 +33,7 @@ def deprecated(warning: str) -> Callable[[F], F]:
|
|
|
33
33
|
return decorator
|
|
34
34
|
|
|
35
35
|
|
|
36
|
-
def _wrap_function(func: F, warning: str) -> F:
|
|
36
|
+
def _wrap_function[F: Callable[..., Any]](func: F, warning: str) -> F:
|
|
37
37
|
def wrapper(*args: Any, **kwargs: dict[str, Any]) -> Any:
|
|
38
38
|
warn(warning, category=DeprecationWarning, stacklevel=2)
|
|
39
39
|
return func(*args, **kwargs)
|
|
@@ -39,16 +39,11 @@ class DateTime:
|
|
|
39
39
|
if time_zone is None:
|
|
40
40
|
time_zone = ZoneInfo("UTC")
|
|
41
41
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
timestamp_ms = int(dt.timestamp() * 1000)
|
|
46
|
-
return cls(timestamp_ms=timestamp_ms)
|
|
42
|
+
dt = datetime.strptime(date, "%Y-%m-%d")
|
|
43
|
+
dt = dt.replace(tzinfo=time_zone)
|
|
44
|
+
timestamp_ms = int(dt.timestamp() * 1000)
|
|
47
45
|
|
|
48
|
-
|
|
49
|
-
raise ValueError(
|
|
50
|
-
f"Invalid date format: '{date}'. Expected 'yyyy-mm-dd'."
|
|
51
|
-
) from e
|
|
46
|
+
return cls(timestamp_ms=timestamp_ms)
|
|
52
47
|
|
|
53
48
|
# Date in format of "yyyy-mm-ddTHH:MM:SS"
|
|
54
49
|
@classmethod
|
|
@@ -58,16 +53,11 @@ class DateTime:
|
|
|
58
53
|
if time_zone is None:
|
|
59
54
|
time_zone = ZoneInfo("UTC")
|
|
60
55
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
except ValueError as e:
|
|
67
|
-
raise ValueError(
|
|
68
|
-
f"Invalid datetime format: '{date_time}'."
|
|
69
|
-
f" Expected 'yyyy-mm-ddTHH:MM:SS'."
|
|
70
|
-
) from e
|
|
56
|
+
dt = datetime.strptime(date_time, "%Y-%m-%dT%H:%M:%S")
|
|
57
|
+
dt = dt.replace(tzinfo=time_zone)
|
|
58
|
+
timestamp_ms = int(dt.timestamp() * 1000)
|
|
59
|
+
|
|
60
|
+
return cls(timestamp_ms=timestamp_ms)
|
|
71
61
|
|
|
72
62
|
@classmethod
|
|
73
63
|
def from_timestamp(cls, timestamp: float) -> DateTime:
|
|
@@ -65,7 +65,7 @@ class FastApiBuilder:
|
|
|
65
65
|
|
|
66
66
|
return self
|
|
67
67
|
|
|
68
|
-
def with_frontend(self, origin: str) -> Self:
|
|
68
|
+
def with_frontend(self, origin: str) -> Self: # pragma: no cover
|
|
69
69
|
self.app.add_middleware(
|
|
70
70
|
CORSMiddleware,
|
|
71
71
|
allow_origins=[origin],
|
|
@@ -76,7 +76,7 @@ class FastApiBuilder:
|
|
|
76
76
|
|
|
77
77
|
return self
|
|
78
78
|
|
|
79
|
-
def with_swagger(self, **config: Any) -> Self:
|
|
79
|
+
def with_swagger(self, **config: Any) -> Self: # pragma: no cover
|
|
80
80
|
self.app.swagger_ui_parameters = config
|
|
81
81
|
|
|
82
82
|
return self
|
|
@@ -120,5 +120,5 @@ class _Endpoint:
|
|
|
120
120
|
def normilized(self) -> str:
|
|
121
121
|
return f"/{self.raw.replace('_', '-')}"
|
|
122
122
|
|
|
123
|
-
def __str__(self) -> str:
|
|
123
|
+
def __str__(self) -> str: # pragma: no cover
|
|
124
124
|
return self.normilized()
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
from typing import
|
|
1
|
+
from typing import TypeVar
|
|
2
2
|
|
|
3
3
|
from pydantic import BaseModel
|
|
4
4
|
|
|
5
5
|
PayloadT = TypeVar("PayloadT")
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
class Response
|
|
8
|
+
class Response[PayloadT](BaseModel):
|
|
9
9
|
status: str
|
|
10
10
|
code: int
|
|
11
11
|
data: PayloadT
|
|
@@ -5,13 +5,13 @@ from typing import Annotated, Any, Protocol, Self, TypeVar
|
|
|
5
5
|
|
|
6
6
|
from fastapi import APIRouter, Depends, Path, Query
|
|
7
7
|
from fastapi.responses import JSONResponse
|
|
8
|
+
from pypebbles import FluentDict
|
|
8
9
|
|
|
9
10
|
from apexdevkit.fastapi.name import RestfulName
|
|
10
11
|
from apexdevkit.fastapi.resource import RestfulResource
|
|
11
12
|
from apexdevkit.fastapi.response import RestfulResponse
|
|
12
13
|
from apexdevkit.fastapi.schema import RestfulSchema, Schema, SchemaFields
|
|
13
|
-
from apexdevkit.fastapi.service import
|
|
14
|
-
from apexdevkit.fluent import FluentDict
|
|
14
|
+
from apexdevkit.fastapi.service import RawItem, RestfulService
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
class RouterWithHiddenUnderscoreRoutes(APIRouter):
|
|
@@ -144,30 +144,6 @@ class RestfulRouter:
|
|
|
144
144
|
|
|
145
145
|
return self
|
|
146
146
|
|
|
147
|
-
def with_create_many(
|
|
148
|
-
self,
|
|
149
|
-
dependency: Dependency | None = None,
|
|
150
|
-
is_documented: bool = True,
|
|
151
|
-
) -> Self:
|
|
152
|
-
self.router.add_api_route(
|
|
153
|
-
"/batch",
|
|
154
|
-
self.resource.create_many(
|
|
155
|
-
Service=self._resolve(dependency),
|
|
156
|
-
Collection=Annotated[
|
|
157
|
-
RawCollection,
|
|
158
|
-
Depends(self._schema.for_create_many()),
|
|
159
|
-
],
|
|
160
|
-
),
|
|
161
|
-
methods=["POST"],
|
|
162
|
-
status_code=201,
|
|
163
|
-
responses={409: {}},
|
|
164
|
-
response_model=self._schema.for_collection(),
|
|
165
|
-
include_in_schema=is_documented,
|
|
166
|
-
summary="Create Many",
|
|
167
|
-
)
|
|
168
|
-
|
|
169
|
-
return self
|
|
170
|
-
|
|
171
147
|
def with_read_one(
|
|
172
148
|
self,
|
|
173
149
|
dependency: Dependency | None = None,
|
|
@@ -217,50 +193,6 @@ class RestfulRouter:
|
|
|
217
193
|
|
|
218
194
|
return self
|
|
219
195
|
|
|
220
|
-
def with_filter(
|
|
221
|
-
self,
|
|
222
|
-
dependency: Dependency | None = None,
|
|
223
|
-
is_documented: bool = True,
|
|
224
|
-
) -> Self:
|
|
225
|
-
self.router.add_api_route(
|
|
226
|
-
"/filter",
|
|
227
|
-
self.resource.filter_with(
|
|
228
|
-
Service=self._resolve(dependency),
|
|
229
|
-
QueryOptions=Annotated[RawItem, Depends(self._schema.for_filters())],
|
|
230
|
-
),
|
|
231
|
-
methods=["POST"],
|
|
232
|
-
status_code=200,
|
|
233
|
-
responses={},
|
|
234
|
-
response_model=self._schema.for_collection(),
|
|
235
|
-
include_in_schema=is_documented,
|
|
236
|
-
summary="Read Filtered",
|
|
237
|
-
)
|
|
238
|
-
|
|
239
|
-
return self
|
|
240
|
-
|
|
241
|
-
def with_aggregation(
|
|
242
|
-
self,
|
|
243
|
-
dependency: Dependency | None = None,
|
|
244
|
-
is_documented: bool = True,
|
|
245
|
-
) -> Self:
|
|
246
|
-
self.router.add_api_route(
|
|
247
|
-
"/aggregation",
|
|
248
|
-
self.resource.aggregation_with(
|
|
249
|
-
Service=self._resolve(dependency),
|
|
250
|
-
FilterOptions=Annotated[
|
|
251
|
-
RawItem, Depends(self._schema.for_aggregation())
|
|
252
|
-
],
|
|
253
|
-
),
|
|
254
|
-
methods=["POST"],
|
|
255
|
-
status_code=200,
|
|
256
|
-
responses={},
|
|
257
|
-
response_model=self._schema.for_aggregation_result(),
|
|
258
|
-
include_in_schema=is_documented,
|
|
259
|
-
summary="Aggregation",
|
|
260
|
-
)
|
|
261
|
-
|
|
262
|
-
return self
|
|
263
|
-
|
|
264
196
|
def with_read_all(
|
|
265
197
|
self,
|
|
266
198
|
dependency: Dependency | None = None,
|
|
@@ -307,30 +239,6 @@ class RestfulRouter:
|
|
|
307
239
|
|
|
308
240
|
return self
|
|
309
241
|
|
|
310
|
-
def with_update_many(
|
|
311
|
-
self,
|
|
312
|
-
dependency: Dependency | None = None,
|
|
313
|
-
is_documented: bool = True,
|
|
314
|
-
) -> Self:
|
|
315
|
-
self.router.add_api_route(
|
|
316
|
-
"",
|
|
317
|
-
self.resource.update_many(
|
|
318
|
-
Service=self._resolve(dependency),
|
|
319
|
-
Collection=Annotated[
|
|
320
|
-
RawCollection,
|
|
321
|
-
Depends(self._schema.for_update_many()),
|
|
322
|
-
],
|
|
323
|
-
),
|
|
324
|
-
methods=["PATCH"],
|
|
325
|
-
status_code=200,
|
|
326
|
-
responses={},
|
|
327
|
-
response_model=self._schema.for_no_data(),
|
|
328
|
-
include_in_schema=is_documented,
|
|
329
|
-
summary="Update Many",
|
|
330
|
-
)
|
|
331
|
-
|
|
332
|
-
return self
|
|
333
|
-
|
|
334
242
|
def with_replace_one(
|
|
335
243
|
self,
|
|
336
244
|
dependency: Dependency | None = None,
|
|
@@ -355,30 +263,6 @@ class RestfulRouter:
|
|
|
355
263
|
|
|
356
264
|
return self
|
|
357
265
|
|
|
358
|
-
def with_replace_many(
|
|
359
|
-
self,
|
|
360
|
-
dependency: Dependency | None = None,
|
|
361
|
-
is_documented: bool = True,
|
|
362
|
-
) -> Self:
|
|
363
|
-
self.router.add_api_route(
|
|
364
|
-
"/batch",
|
|
365
|
-
self.resource.replace_many(
|
|
366
|
-
Service=self._resolve(dependency),
|
|
367
|
-
Collection=Annotated[
|
|
368
|
-
RawCollection,
|
|
369
|
-
Depends(self._schema.for_replace_many()),
|
|
370
|
-
],
|
|
371
|
-
),
|
|
372
|
-
methods=["PUT"],
|
|
373
|
-
status_code=200,
|
|
374
|
-
responses={},
|
|
375
|
-
response_model=self._schema.for_no_data(),
|
|
376
|
-
include_in_schema=is_documented,
|
|
377
|
-
summary="Replace Many",
|
|
378
|
-
)
|
|
379
|
-
|
|
380
|
-
return self
|
|
381
|
-
|
|
382
266
|
def with_delete_one(
|
|
383
267
|
self,
|
|
384
268
|
dependency: Dependency | None = None,
|
|
@@ -412,11 +296,9 @@ class RestfulRouter:
|
|
|
412
296
|
def default(self) -> Self:
|
|
413
297
|
return (
|
|
414
298
|
self.with_create_one()
|
|
415
|
-
.with_create_many()
|
|
416
299
|
.with_read_one()
|
|
417
300
|
.with_read_all()
|
|
418
301
|
.with_update_one()
|
|
419
|
-
.with_update_many()
|
|
420
302
|
.with_delete_one()
|
|
421
303
|
)
|
|
422
304
|
|
|
@@ -1,20 +1,13 @@
|
|
|
1
1
|
from abc import ABC, abstractmethod
|
|
2
|
-
from collections.abc import Callable,
|
|
2
|
+
from collections.abc import Callable, Iterator
|
|
3
3
|
from dataclasses import dataclass
|
|
4
4
|
from functools import cached_property
|
|
5
5
|
from typing import Any
|
|
6
6
|
|
|
7
7
|
from pydantic import BaseModel, create_model
|
|
8
|
+
from pypebbles import FluentDict
|
|
8
9
|
|
|
9
10
|
from apexdevkit.fastapi.name import RestfulName
|
|
10
|
-
from apexdevkit.fluent import FluentDict
|
|
11
|
-
from apexdevkit.http import JsonDict
|
|
12
|
-
from apexdevkit.value import Value
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
class AggregationResult(BaseModel):
|
|
16
|
-
field: str
|
|
17
|
-
aggregation: Value
|
|
18
11
|
|
|
19
12
|
|
|
20
13
|
class SchemaFields(ABC):
|
|
@@ -27,15 +20,6 @@ class SchemaFields(ABC):
|
|
|
27
20
|
def editable(self) -> FluentDict[type]:
|
|
28
21
|
return self.readable().drop("id")
|
|
29
22
|
|
|
30
|
-
def filters(self) -> FluentDict[type]:
|
|
31
|
-
return JsonDict()
|
|
32
|
-
|
|
33
|
-
def aggregation_filters(self) -> FluentDict[type]:
|
|
34
|
-
return JsonDict()
|
|
35
|
-
|
|
36
|
-
def aggregation_result(self) -> FluentDict[type]:
|
|
37
|
-
return JsonDict().with_a(count=int).and_a(sums=list[AggregationResult])
|
|
38
|
-
|
|
39
23
|
@abstractmethod
|
|
40
24
|
def readable(self) -> FluentDict[type]: # pragma: no cover
|
|
41
25
|
pass
|
|
@@ -49,21 +33,22 @@ class RestfulSchema:
|
|
|
49
33
|
|
|
50
34
|
def __post_init__(self) -> None:
|
|
51
35
|
schema = self._schema_for("", self.fields.readable())
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
36
|
+
self._schema_for(
|
|
37
|
+
"Item",
|
|
38
|
+
{
|
|
39
|
+
self.name.singular: schema,
|
|
40
|
+
},
|
|
57
41
|
)
|
|
58
|
-
self._schema_for(
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
self._schema_for("
|
|
66
|
-
self._schema_for("
|
|
42
|
+
self._schema_for(
|
|
43
|
+
"Collection",
|
|
44
|
+
{
|
|
45
|
+
self.name.plural: list[schema],
|
|
46
|
+
"count": int,
|
|
47
|
+
},
|
|
48
|
+
)
|
|
49
|
+
self._schema_for("Create", self.fields.writable())
|
|
50
|
+
self._schema_for("Update", self.fields.editable())
|
|
51
|
+
self._schema_for("Replace", self.fields.readable())
|
|
67
52
|
|
|
68
53
|
def _schema_for(self, action: str, fields: dict[str, Any]) -> type[BaseModel]:
|
|
69
54
|
if action not in self._models:
|
|
@@ -113,14 +98,6 @@ class RestfulSchema:
|
|
|
113
98
|
|
|
114
99
|
return _
|
|
115
100
|
|
|
116
|
-
def for_create_many(self) -> Callable[[BaseModel], Iterable[dict[str, Any]]]:
|
|
117
|
-
schema = self._models["CreateMany"]
|
|
118
|
-
|
|
119
|
-
def _(request: schema) -> Iterable[dict[str, Any]]:
|
|
120
|
-
return [dict(item) for item in request.model_dump()[self.name.plural]]
|
|
121
|
-
|
|
122
|
-
return _
|
|
123
|
-
|
|
124
101
|
def for_update_one(self) -> Callable[[BaseModel], dict[str, Any]]:
|
|
125
102
|
schema = self._models["Update"]
|
|
126
103
|
|
|
@@ -129,14 +106,6 @@ class RestfulSchema:
|
|
|
129
106
|
|
|
130
107
|
return _
|
|
131
108
|
|
|
132
|
-
def for_update_many(self) -> Callable[[BaseModel], Iterable[dict[str, Any]]]:
|
|
133
|
-
schema = self._models["UpdateMany"]
|
|
134
|
-
|
|
135
|
-
def _(request: schema) -> Iterable[dict[str, Any]]:
|
|
136
|
-
return [dict(item) for item in request.model_dump()[self.name.plural]]
|
|
137
|
-
|
|
138
|
-
return _
|
|
139
|
-
|
|
140
109
|
def for_replace_one(self) -> Callable[[BaseModel], dict[str, Any]]:
|
|
141
110
|
schema = self._models["Replace"]
|
|
142
111
|
|
|
@@ -145,39 +114,6 @@ class RestfulSchema:
|
|
|
145
114
|
|
|
146
115
|
return _
|
|
147
116
|
|
|
148
|
-
def for_replace_many(self) -> Callable[[BaseModel], Iterable[dict[str, Any]]]:
|
|
149
|
-
schema = self._models["ReplaceMany"]
|
|
150
|
-
|
|
151
|
-
def _(request: schema) -> Iterable[dict[str, Any]]:
|
|
152
|
-
return [dict(item) for item in request.model_dump()[self.name.plural]]
|
|
153
|
-
|
|
154
|
-
return _
|
|
155
|
-
|
|
156
|
-
def for_filters(self) -> Callable[[BaseModel], dict[str, Any]]:
|
|
157
|
-
schema = self._models["Filter"]
|
|
158
|
-
|
|
159
|
-
def _(request: schema) -> dict[str, Any]:
|
|
160
|
-
return request.model_dump()
|
|
161
|
-
|
|
162
|
-
return _
|
|
163
|
-
|
|
164
|
-
def for_aggregation(self) -> Callable[[BaseModel], dict[str, Any]]:
|
|
165
|
-
schema = self._models["Aggregation"]
|
|
166
|
-
|
|
167
|
-
def _(request: schema) -> dict[str, Any]:
|
|
168
|
-
return request.model_dump()
|
|
169
|
-
|
|
170
|
-
return _
|
|
171
|
-
|
|
172
|
-
def for_aggregation_result(self) -> type[BaseModel]:
|
|
173
|
-
return self._schema_for(
|
|
174
|
-
"AggregationResultResponse",
|
|
175
|
-
FluentDict[type]()
|
|
176
|
-
.with_a(status=str)
|
|
177
|
-
.and_a(code=int)
|
|
178
|
-
.and_a(aggregations=self._models["AggregationResult"]),
|
|
179
|
-
)
|
|
180
|
-
|
|
181
117
|
|
|
182
118
|
@dataclass(frozen=True)
|
|
183
119
|
class Schema:
|
|
@@ -3,11 +3,10 @@ from __future__ import annotations
|
|
|
3
3
|
from collections.abc import Iterable, Mapping
|
|
4
4
|
from dataclasses import dataclass
|
|
5
5
|
from functools import cached_property
|
|
6
|
-
from typing import Any
|
|
6
|
+
from typing import Any
|
|
7
7
|
|
|
8
8
|
from apexdevkit.formatter import Formatter
|
|
9
|
-
from apexdevkit.repository import BruteForceBatch, Repository
|
|
10
|
-
from apexdevkit.repository.core import ItemT
|
|
9
|
+
from apexdevkit.repository import BruteForceBatch, Entity, Repository
|
|
11
10
|
from apexdevkit.repository.core.interface import BatchRepository
|
|
12
11
|
|
|
13
12
|
RawItem = Mapping[str, Any]
|
|
@@ -61,7 +60,7 @@ class RestfulService: # pragma: no cover
|
|
|
61
60
|
|
|
62
61
|
|
|
63
62
|
@dataclass(frozen=True)
|
|
64
|
-
class RestfulRepository
|
|
63
|
+
class RestfulRepository[ItemT: Entity](RestfulService):
|
|
65
64
|
repository: Repository[ItemT]
|
|
66
65
|
|
|
67
66
|
formatter: Formatter[Mapping[str, Any], ItemT]
|
|
@@ -4,34 +4,29 @@ import pickle
|
|
|
4
4
|
from collections.abc import Mapping
|
|
5
5
|
from copy import deepcopy
|
|
6
6
|
from dataclasses import asdict, dataclass, field, fields, is_dataclass
|
|
7
|
-
from typing import Any,
|
|
7
|
+
from typing import Any, Protocol, Self, get_args, get_type_hints
|
|
8
8
|
|
|
9
|
-
from
|
|
10
|
-
from apexdevkit.value import Value
|
|
9
|
+
from pypebbles import FluentDict
|
|
11
10
|
|
|
12
|
-
_SourceT = TypeVar("_SourceT")
|
|
13
|
-
_TargetT = TypeVar("_TargetT")
|
|
14
|
-
_ItemT = TypeVar("_ItemT")
|
|
15
11
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
def load(self, source: _SourceT) -> _TargetT:
|
|
12
|
+
class Formatter[SourceT, TargetT](Protocol): # pragma: no cover
|
|
13
|
+
def load(self, source: SourceT) -> TargetT:
|
|
19
14
|
pass
|
|
20
15
|
|
|
21
|
-
def dump(self, target:
|
|
16
|
+
def dump(self, target: TargetT) -> SourceT:
|
|
22
17
|
pass
|
|
23
18
|
|
|
24
19
|
|
|
25
20
|
@dataclass(frozen=True)
|
|
26
|
-
class AliasFormatter(Formatter[Mapping[str, Any],
|
|
27
|
-
inner: Formatter[Mapping[str, Any],
|
|
21
|
+
class AliasFormatter[TargetT](Formatter[Mapping[str, Any], TargetT]):
|
|
22
|
+
inner: Formatter[Mapping[str, Any], TargetT]
|
|
28
23
|
|
|
29
24
|
alias: AliasMapping
|
|
30
25
|
|
|
31
|
-
def load(self, source: Mapping[str, Any]) ->
|
|
26
|
+
def load(self, source: Mapping[str, Any]) -> TargetT:
|
|
32
27
|
return self.inner.load(self.alias.reverse().translate(source))
|
|
33
28
|
|
|
34
|
-
def dump(self, target:
|
|
29
|
+
def dump(self, target: TargetT) -> Mapping[str, Any]:
|
|
35
30
|
return self.alias.translate(self.inner.dump(target))
|
|
36
31
|
|
|
37
32
|
|
|
@@ -53,28 +48,28 @@ class AliasMapping:
|
|
|
53
48
|
return self.alias.get(key, key)
|
|
54
49
|
|
|
55
50
|
|
|
56
|
-
class PickleFormatter
|
|
57
|
-
def dump(self, item:
|
|
51
|
+
class PickleFormatter[ItemT]:
|
|
52
|
+
def dump(self, item: ItemT) -> bytes:
|
|
58
53
|
return pickle.dumps(item)
|
|
59
54
|
|
|
60
|
-
def load(self, raw: bytes) ->
|
|
55
|
+
def load(self, raw: bytes) -> ItemT:
|
|
61
56
|
return pickle.loads(raw) # type: ignore
|
|
62
57
|
|
|
63
58
|
|
|
64
59
|
@dataclass
|
|
65
|
-
class ListFormatter
|
|
66
|
-
inner: Formatter[
|
|
60
|
+
class ListFormatter[SourceT, TargetT]:
|
|
61
|
+
inner: Formatter[SourceT, TargetT]
|
|
67
62
|
|
|
68
|
-
def load(self, source: list[
|
|
63
|
+
def load(self, source: list[SourceT]) -> list[TargetT]:
|
|
69
64
|
return [self.inner.load(item) for item in source]
|
|
70
65
|
|
|
71
|
-
def dump(self, target: list[
|
|
66
|
+
def dump(self, target: list[TargetT]) -> list[SourceT]:
|
|
72
67
|
return [self.inner.dump(item) for item in target]
|
|
73
68
|
|
|
74
69
|
|
|
75
70
|
@dataclass
|
|
76
|
-
class DataclassFormatter
|
|
77
|
-
resource: type[
|
|
71
|
+
class DataclassFormatter[TargetT]:
|
|
72
|
+
resource: type[TargetT]
|
|
78
73
|
sub_formatters: dict[str, Formatter[Any, Any]] = field(default_factory=dict)
|
|
79
74
|
|
|
80
75
|
def and_nested(self, **formatters: Formatter[Any, Any]) -> Self:
|
|
@@ -85,7 +80,7 @@ class DataclassFormatter(Generic[_TargetT]):
|
|
|
85
80
|
|
|
86
81
|
return self
|
|
87
82
|
|
|
88
|
-
def load(self, source: Mapping[str, Any]) ->
|
|
83
|
+
def load(self, source: Mapping[str, Any]) -> TargetT:
|
|
89
84
|
source = FluentDict[Any](deepcopy(source)).select(
|
|
90
85
|
*self.resource.__annotations__.keys(),
|
|
91
86
|
"id",
|
|
@@ -114,13 +109,5 @@ class DataclassFormatter(Generic[_TargetT]):
|
|
|
114
109
|
|
|
115
110
|
return self.resource(**source)
|
|
116
111
|
|
|
117
|
-
def dump(self, target:
|
|
112
|
+
def dump(self, target: TargetT) -> Mapping[str, Any]:
|
|
118
113
|
return asdict(target) # type: ignore
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
class ValueFormatter:
|
|
122
|
-
def load(self, raw: Mapping[str, Any]) -> Value:
|
|
123
|
-
return DataclassFormatter(Value).load(raw)
|
|
124
|
-
|
|
125
|
-
def dump(self, value: Value) -> Mapping[str, Any]:
|
|
126
|
-
return DataclassFormatter(Value).dump(value)
|
|
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
from collections import defaultdict
|
|
4
4
|
from collections.abc import Iterable, Mapping
|
|
5
5
|
from dataclasses import dataclass, field
|
|
6
|
-
from typing import Any, ClassVar,
|
|
6
|
+
from typing import Any, ClassVar, Protocol, TypeVar
|
|
7
7
|
|
|
8
8
|
from apexdevkit.annotation import deprecated
|
|
9
9
|
from apexdevkit.error import ForbiddenError
|
|
@@ -33,7 +33,7 @@ class _Loader(Protocol[ItemT]):
|
|
|
33
33
|
|
|
34
34
|
|
|
35
35
|
@dataclass
|
|
36
|
-
class DefaultMsSqlFilter
|
|
36
|
+
class DefaultMsSqlFilter[ItemT]:
|
|
37
37
|
_database: Database = field(init=False)
|
|
38
38
|
_query_builder: MsSqlQueryBuilder = field(init=False)
|
|
39
39
|
_options: QueryOptions = field(init=False)
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
from typing import
|
|
1
|
+
from typing import Any
|
|
2
2
|
|
|
3
3
|
from apexdevkit.error import DoesNotExistError
|
|
4
|
-
from apexdevkit.repository.core import Entity
|
|
4
|
+
from apexdevkit.repository.core import Entity
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
class ContainsMixin
|
|
7
|
+
class ContainsMixin:
|
|
8
8
|
def __contains__(self, item: object) -> bool:
|
|
9
9
|
match item:
|
|
10
10
|
case Entity():
|
|
@@ -20,5 +20,5 @@ class ContainsMixin(Generic[ItemT]):
|
|
|
20
20
|
|
|
21
21
|
return True
|
|
22
22
|
|
|
23
|
-
def read(self, item_id: str) ->
|
|
23
|
+
def read(self, item_id: str) -> Any:
|
|
24
24
|
raise NotImplementedError
|
|
@@ -12,7 +12,7 @@ from .store import InMemoryByteStore, KeyValueStore
|
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
@dataclass(frozen=True)
|
|
15
|
-
class InMemoryRepository(ContainsMixin
|
|
15
|
+
class InMemoryRepository(ContainsMixin, Repository[ItemT]):
|
|
16
16
|
store: KeyValueStore[ItemT] = field(default_factory=InMemoryByteStore)
|
|
17
17
|
keys: list[KeyFn[ItemT]] = field(default_factory=lambda: [AttributeKey("id")])
|
|
18
18
|
|
|
@@ -2,13 +2,12 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from collections.abc import Iterable
|
|
4
4
|
from dataclasses import dataclass, field
|
|
5
|
-
from typing import
|
|
5
|
+
from typing import Protocol
|
|
6
6
|
|
|
7
7
|
from apexdevkit.formatter import Formatter, PickleFormatter
|
|
8
|
-
from apexdevkit.repository.core.interface import ItemT
|
|
9
8
|
|
|
10
9
|
|
|
11
|
-
class KeyValueStore
|
|
10
|
+
class KeyValueStore[ItemT](Protocol): # pragma: no cover
|
|
12
11
|
def count(self) -> int:
|
|
13
12
|
pass
|
|
14
13
|
|
|
@@ -25,8 +24,8 @@ class KeyValueStore(Protocol[ItemT]): # pragma: no cover
|
|
|
25
24
|
pass
|
|
26
25
|
|
|
27
26
|
|
|
28
|
-
@dataclass
|
|
29
|
-
class InMemoryByteStore
|
|
27
|
+
@dataclass(frozen=True)
|
|
28
|
+
class InMemoryByteStore[ItemT]:
|
|
30
29
|
formatter: Formatter[bytes, ItemT] = field(default_factory=PickleFormatter)
|
|
31
30
|
|
|
32
31
|
items: dict[str, bytes] = field(default_factory=dict)
|