apexdevkit 2.2.1__tar.gz → 2.3.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.2.1 → apexdevkit-2.3.1}/PKG-INFO +1 -1
- apexdevkit-2.3.1/apexdevkit/fastapi/__init__.py +15 -0
- {apexdevkit-2.2.1 → apexdevkit-2.3.1}/apexdevkit/fastapi/builder.py +4 -39
- {apexdevkit-2.2.1 → apexdevkit-2.3.1}/apexdevkit/fastapi/dependable.py +6 -14
- apexdevkit-2.2.1/apexdevkit/fastapi/resource.py → apexdevkit-2.3.1/apexdevkit/fastapi/endpoints.py +1 -1
- {apexdevkit-2.2.1 → apexdevkit-2.3.1}/apexdevkit/fastapi/router.py +28 -26
- apexdevkit-2.3.1/apexdevkit/fastapi/service.py +137 -0
- {apexdevkit-2.2.1 → apexdevkit-2.3.1}/apexdevkit/formatter.py +8 -0
- {apexdevkit-2.2.1 → apexdevkit-2.3.1}/apexdevkit/testing.py +3 -2
- {apexdevkit-2.2.1 → apexdevkit-2.3.1}/pyproject.toml +3 -3
- apexdevkit-2.2.1/apexdevkit/fastapi/__init__.py +0 -13
- apexdevkit-2.2.1/apexdevkit/fastapi/service.py +0 -68
- {apexdevkit-2.2.1 → apexdevkit-2.3.1}/LICENSE +0 -0
- {apexdevkit-2.2.1 → apexdevkit-2.3.1}/README.md +0 -0
- {apexdevkit-2.2.1 → apexdevkit-2.3.1}/apexdevkit/__init__.py +0 -0
- {apexdevkit-2.2.1 → apexdevkit-2.3.1}/apexdevkit/annotation/__init__.py +0 -0
- {apexdevkit-2.2.1 → apexdevkit-2.3.1}/apexdevkit/annotation/deprecate.py +0 -0
- {apexdevkit-2.2.1 → apexdevkit-2.3.1}/apexdevkit/date.py +0 -0
- {apexdevkit-2.2.1 → apexdevkit-2.3.1}/apexdevkit/error.py +0 -0
- {apexdevkit-2.2.1 → apexdevkit-2.3.1}/apexdevkit/fastapi/name.py +0 -0
- {apexdevkit-2.2.1 → apexdevkit-2.3.1}/apexdevkit/fastapi/response.py +0 -0
- {apexdevkit-2.2.1 → apexdevkit-2.3.1}/apexdevkit/fastapi/schema.py +0 -0
- {apexdevkit-2.2.1 → apexdevkit-2.3.1}/apexdevkit/key_fn.py +0 -0
- {apexdevkit-2.2.1 → apexdevkit-2.3.1}/apexdevkit/py.typed +0 -0
- {apexdevkit-2.2.1 → apexdevkit-2.3.1}/apexdevkit/query/__init__.py +0 -0
- {apexdevkit-2.2.1 → apexdevkit-2.3.1}/apexdevkit/query/generator.py +0 -0
- {apexdevkit-2.2.1 → apexdevkit-2.3.1}/apexdevkit/query/query.py +0 -0
- {apexdevkit-2.2.1 → apexdevkit-2.3.1}/apexdevkit/repository/__init__.py +0 -0
- {apexdevkit-2.2.1 → apexdevkit-2.3.1}/apexdevkit/repository/core/__init__.py +0 -0
- {apexdevkit-2.2.1 → apexdevkit-2.3.1}/apexdevkit/repository/core/database.py +0 -0
- {apexdevkit-2.2.1 → apexdevkit-2.3.1}/apexdevkit/repository/core/decorator.py +0 -0
- {apexdevkit-2.2.1 → apexdevkit-2.3.1}/apexdevkit/repository/core/interface.py +0 -0
- {apexdevkit-2.2.1 → apexdevkit-2.3.1}/apexdevkit/repository/core/mixin.py +0 -0
- {apexdevkit-2.2.1 → apexdevkit-2.3.1}/apexdevkit/repository/in_memory/__init__.py +0 -0
- {apexdevkit-2.2.1 → apexdevkit-2.3.1}/apexdevkit/repository/in_memory/mixin.py +0 -0
- {apexdevkit-2.2.1 → apexdevkit-2.3.1}/apexdevkit/repository/in_memory/repository.py +0 -0
- {apexdevkit-2.2.1 → apexdevkit-2.3.1}/apexdevkit/repository/in_memory/store.py +0 -0
- {apexdevkit-2.2.1 → apexdevkit-2.3.1}/apexdevkit/repository/sql/__init__.py +0 -0
- {apexdevkit-2.2.1 → apexdevkit-2.3.1}/apexdevkit/repository/sql/connector.py +0 -0
- {apexdevkit-2.2.1 → apexdevkit-2.3.1}/apexdevkit/repository/sql/field.py +0 -0
- {apexdevkit-2.2.1 → apexdevkit-2.3.1}/apexdevkit/repository/sql/mssql/__init__.py +0 -0
- {apexdevkit-2.2.1 → apexdevkit-2.3.1}/apexdevkit/repository/sql/mssql/repository.py +0 -0
- {apexdevkit-2.2.1 → apexdevkit-2.3.1}/apexdevkit/repository/sql/mssql/table.py +0 -0
- {apexdevkit-2.2.1 → apexdevkit-2.3.1}/apexdevkit/repository/sql/sqlite/__init__.py +0 -0
- {apexdevkit-2.2.1 → apexdevkit-2.3.1}/apexdevkit/repository/sql/sqlite/repository.py +0 -0
- {apexdevkit-2.2.1 → apexdevkit-2.3.1}/apexdevkit/repository/sql/sqlite/table.py +0 -0
- {apexdevkit-2.2.1 → apexdevkit-2.3.1}/apexdevkit/server.py +0 -0
- {apexdevkit-2.2.1 → apexdevkit-2.3.1}/apexdevkit/sync/__init__.py +0 -0
- {apexdevkit-2.2.1 → apexdevkit-2.3.1}/apexdevkit/sync/observer.py +0 -0
- {apexdevkit-2.2.1 → apexdevkit-2.3.1}/apexdevkit/sync/source.py +0 -0
- {apexdevkit-2.2.1 → apexdevkit-2.3.1}/apexdevkit/sync/sync.py +0 -0
- {apexdevkit-2.2.1 → apexdevkit-2.3.1}/apexdevkit/sync/target.py +0 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from apexdevkit.fastapi.builder import FastApiBuilder
|
|
2
|
+
from apexdevkit.fastapi.router import RestfulRouter
|
|
3
|
+
from apexdevkit.fastapi.service import (
|
|
4
|
+
RestfulRepository,
|
|
5
|
+
RestfulResource,
|
|
6
|
+
RestfulService,
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"FastApiBuilder",
|
|
11
|
+
"RestfulRepository",
|
|
12
|
+
"RestfulResource",
|
|
13
|
+
"RestfulRouter",
|
|
14
|
+
"RestfulService",
|
|
15
|
+
]
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from abc import ABC, abstractmethod
|
|
4
3
|
from dataclasses import dataclass, field
|
|
5
4
|
from typing import TYPE_CHECKING, Any, Protocol, Self
|
|
6
5
|
|
|
@@ -10,13 +9,10 @@ from fastapi.responses import JSONResponse
|
|
|
10
9
|
|
|
11
10
|
from apexdevkit.annotation import deprecated
|
|
12
11
|
from apexdevkit.error import ApiError
|
|
13
|
-
from apexdevkit.fastapi.dependable import DependableBuilder
|
|
14
12
|
|
|
15
13
|
if TYPE_CHECKING:
|
|
16
14
|
from collections.abc import Mapping, MutableSet
|
|
17
15
|
|
|
18
|
-
from apexdevkit.fastapi.service import RestfulService
|
|
19
|
-
|
|
20
16
|
|
|
21
17
|
@dataclass
|
|
22
18
|
class FastApiBuilder:
|
|
@@ -57,7 +53,7 @@ class FastApiBuilder:
|
|
|
57
53
|
for key, value in values.items():
|
|
58
54
|
self.app.include_router(
|
|
59
55
|
value,
|
|
60
|
-
prefix=_Endpoint(key).
|
|
56
|
+
prefix=_Endpoint(key).normalized(),
|
|
61
57
|
tags=value.tags or [key.title()],
|
|
62
58
|
)
|
|
63
59
|
|
|
@@ -65,7 +61,7 @@ class FastApiBuilder:
|
|
|
65
61
|
|
|
66
62
|
def with_mounted(self, **apps: FastAPI) -> Self:
|
|
67
63
|
for path, app in apps.items():
|
|
68
|
-
self.app.mount(_Endpoint(path).
|
|
64
|
+
self.app.mount(_Endpoint(path).normalized(), app)
|
|
69
65
|
|
|
70
66
|
return self
|
|
71
67
|
|
|
@@ -125,43 +121,12 @@ class CORSApplicator:
|
|
|
125
121
|
)
|
|
126
122
|
|
|
127
123
|
|
|
128
|
-
@dataclass
|
|
129
|
-
class RestfulServiceBuilder(ABC):
|
|
130
|
-
parent_id: str = field(init=False)
|
|
131
|
-
user: Any = field(init=False)
|
|
132
|
-
|
|
133
|
-
def as_dependable(self) -> DependableBuilder:
|
|
134
|
-
return DependableBuilder.from_builder(self)
|
|
135
|
-
|
|
136
|
-
def with_user(self, user: Any) -> RestfulServiceBuilder:
|
|
137
|
-
self.user = user
|
|
138
|
-
|
|
139
|
-
return self
|
|
140
|
-
|
|
141
|
-
def with_parent(self, identity: str) -> RestfulServiceBuilder:
|
|
142
|
-
self.parent_id = identity
|
|
143
|
-
|
|
144
|
-
return self
|
|
145
|
-
|
|
146
|
-
@abstractmethod
|
|
147
|
-
def build(self) -> RestfulService: # pragma: no cover
|
|
148
|
-
pass
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
@dataclass
|
|
152
|
-
class PreBuilt(RestfulServiceBuilder): # pragma: no cover
|
|
153
|
-
service: RestfulService
|
|
154
|
-
|
|
155
|
-
def build(self) -> RestfulService:
|
|
156
|
-
return self.service
|
|
157
|
-
|
|
158
|
-
|
|
159
124
|
@dataclass(frozen=True)
|
|
160
125
|
class _Endpoint:
|
|
161
126
|
raw: str
|
|
162
127
|
|
|
163
|
-
def
|
|
128
|
+
def normalized(self) -> str:
|
|
164
129
|
return f"/{self.raw.replace('_', '-')}"
|
|
165
130
|
|
|
166
131
|
def __str__(self) -> str: # pragma: no cover
|
|
167
|
-
return self.
|
|
132
|
+
return self.normalized()
|
|
@@ -3,19 +3,11 @@ from dataclasses import dataclass
|
|
|
3
3
|
from typing import Annotated, Any, Protocol
|
|
4
4
|
|
|
5
5
|
from fastapi import Depends, Path
|
|
6
|
-
from fastapi.requests import Request
|
|
7
6
|
|
|
8
7
|
from apexdevkit.error import ApiError, DoesNotExistError, ForbiddenError
|
|
9
8
|
from apexdevkit.fastapi.name import RestfulName
|
|
10
9
|
from apexdevkit.fastapi.response import RestfulResponse
|
|
11
|
-
from apexdevkit.fastapi.service import
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
def inject(dependency: str) -> Any: # pragma: no cover
|
|
15
|
-
def get(request: Request) -> Any:
|
|
16
|
-
return getattr(request.app.state, dependency)
|
|
17
|
-
|
|
18
|
-
return Depends(get)
|
|
10
|
+
from apexdevkit.fastapi.service import RestfulResource
|
|
19
11
|
|
|
20
12
|
|
|
21
13
|
class _ServiceBuilder(Protocol):
|
|
@@ -25,7 +17,7 @@ class _ServiceBuilder(Protocol):
|
|
|
25
17
|
def with_parent(self, identity: str) -> "_ServiceBuilder":
|
|
26
18
|
pass
|
|
27
19
|
|
|
28
|
-
def build(self) ->
|
|
20
|
+
def build(self) -> RestfulResource: # pragma: no cover
|
|
29
21
|
pass
|
|
30
22
|
|
|
31
23
|
|
|
@@ -38,10 +30,10 @@ class _Dependency(Protocol):
|
|
|
38
30
|
class ServiceDependency:
|
|
39
31
|
dependency: _Dependency
|
|
40
32
|
|
|
41
|
-
def as_dependable(self) -> type[
|
|
33
|
+
def as_dependable(self) -> type[RestfulResource]:
|
|
42
34
|
Builder = self.dependency.as_dependable()
|
|
43
35
|
|
|
44
|
-
def _(builder: Builder) ->
|
|
36
|
+
def _(builder: Builder) -> RestfulResource:
|
|
45
37
|
try:
|
|
46
38
|
return builder.build()
|
|
47
39
|
except ForbiddenError as e:
|
|
@@ -49,7 +41,7 @@ class ServiceDependency:
|
|
|
49
41
|
403, RestfulResponse(RestfulName("unknown")).forbidden(e)
|
|
50
42
|
) from e
|
|
51
43
|
|
|
52
|
-
return Annotated[
|
|
44
|
+
return Annotated[RestfulResource, Depends(_)]
|
|
53
45
|
|
|
54
46
|
|
|
55
47
|
@dataclass(frozen=True)
|
|
@@ -119,5 +111,5 @@ class DependableBuilder:
|
|
|
119
111
|
def with_user(self, extract_user: Callable[..., Any]) -> "DependableBuilder":
|
|
120
112
|
return DependableBuilder(UserDependency(extract_user, self.dependency))
|
|
121
113
|
|
|
122
|
-
def as_dependable(self) -> type[
|
|
114
|
+
def as_dependable(self) -> type[RestfulResource]:
|
|
123
115
|
return ServiceDependency(self.dependency).as_dependable()
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from collections.abc import Callable
|
|
1
|
+
from collections.abc import Callable, Mapping
|
|
2
2
|
from dataclasses import dataclass, field
|
|
3
3
|
from enum import Enum
|
|
4
4
|
from typing import Annotated, Any, Protocol, Self
|
|
@@ -7,11 +7,13 @@ from fastapi import APIRouter, Depends, Path, Query
|
|
|
7
7
|
from fastapi.responses import JSONResponse
|
|
8
8
|
from pypebbles import FluentDict
|
|
9
9
|
|
|
10
|
+
from apexdevkit.fastapi.endpoints import RestfulEndpoints
|
|
10
11
|
from apexdevkit.fastapi.name import RestfulName
|
|
11
|
-
from apexdevkit.fastapi.resource import RestfulResource
|
|
12
12
|
from apexdevkit.fastapi.response import RestfulResponse
|
|
13
13
|
from apexdevkit.fastapi.schema import RestfulSchema, Schema, SchemaFields
|
|
14
|
-
from apexdevkit.fastapi.service import
|
|
14
|
+
from apexdevkit.fastapi.service import RestfulResource
|
|
15
|
+
|
|
16
|
+
RawItem = Mapping[str, Any]
|
|
15
17
|
|
|
16
18
|
|
|
17
19
|
class RouterWithHiddenUnderscoreRoutes(APIRouter):
|
|
@@ -48,23 +50,23 @@ class RouterWithHiddenUnderscoreRoutes(APIRouter):
|
|
|
48
50
|
_Response = JSONResponse | dict[str, Any]
|
|
49
51
|
|
|
50
52
|
|
|
51
|
-
class Dependency(Protocol): # pragma: no cover
|
|
52
|
-
def as_dependable(self) -> type[
|
|
53
|
+
class Dependency[T](Protocol): # pragma: no cover
|
|
54
|
+
def as_dependable(self) -> type[RestfulResource[T]]:
|
|
53
55
|
pass
|
|
54
56
|
|
|
55
57
|
|
|
56
58
|
@dataclass
|
|
57
|
-
class RestfulRouter:
|
|
59
|
+
class RestfulRouter[T]:
|
|
58
60
|
name: RestfulName
|
|
59
61
|
|
|
60
62
|
parent: RestfulName | None = None
|
|
61
63
|
router: APIRouter = field(default_factory=RouterWithHiddenUnderscoreRoutes)
|
|
62
64
|
|
|
63
65
|
_schema: RestfulSchema = field(init=False)
|
|
64
|
-
_dependency: Dependency | None = field(init=False, default=None)
|
|
66
|
+
_dependency: Dependency[T] | None = field(init=False, default=None)
|
|
65
67
|
|
|
66
68
|
@classmethod
|
|
67
|
-
def named(cls, singular: str, *, plural: str | None = None) -> "RestfulRouter":
|
|
69
|
+
def named(cls, singular: str, *, plural: str | None = None) -> "RestfulRouter[T]":
|
|
68
70
|
if plural:
|
|
69
71
|
return cls(RestfulName(singular, plural))
|
|
70
72
|
|
|
@@ -79,8 +81,8 @@ class RestfulRouter:
|
|
|
79
81
|
return self
|
|
80
82
|
|
|
81
83
|
@property
|
|
82
|
-
def
|
|
83
|
-
return
|
|
84
|
+
def endpoints(self) -> RestfulEndpoints:
|
|
85
|
+
return RestfulEndpoints(RestfulResponse(self.name))
|
|
84
86
|
|
|
85
87
|
@property
|
|
86
88
|
def id_alias(self) -> str:
|
|
@@ -113,20 +115,20 @@ class RestfulRouter:
|
|
|
113
115
|
|
|
114
116
|
return self
|
|
115
117
|
|
|
116
|
-
def with_default_dependency(self, value: Dependency) -> Self:
|
|
118
|
+
def with_default_dependency(self, value: Dependency[T]) -> Self:
|
|
117
119
|
self._dependency = value
|
|
118
120
|
|
|
119
121
|
return self
|
|
120
122
|
|
|
121
123
|
def with_create_one(
|
|
122
124
|
self,
|
|
123
|
-
dependency: Dependency | None = None,
|
|
125
|
+
dependency: Dependency[T] | None = None,
|
|
124
126
|
*,
|
|
125
127
|
is_documented: bool = True,
|
|
126
128
|
) -> Self:
|
|
127
129
|
self.router.add_api_route(
|
|
128
130
|
"",
|
|
129
|
-
self.
|
|
131
|
+
self.endpoints.create_one_endpoint(
|
|
130
132
|
Service=self._resolve(dependency),
|
|
131
133
|
Item=Annotated[
|
|
132
134
|
RawItem,
|
|
@@ -145,13 +147,13 @@ class RestfulRouter:
|
|
|
145
147
|
|
|
146
148
|
def with_read_one(
|
|
147
149
|
self,
|
|
148
|
-
dependency: Dependency | None = None,
|
|
150
|
+
dependency: Dependency[T] | None = None,
|
|
149
151
|
*,
|
|
150
152
|
is_documented: bool = True,
|
|
151
153
|
) -> Self:
|
|
152
154
|
self.router.add_api_route(
|
|
153
155
|
self.item_path,
|
|
154
|
-
self.
|
|
156
|
+
self.endpoints.read_one_endpoint(
|
|
155
157
|
Service=self._resolve(dependency),
|
|
156
158
|
ItemId=Annotated[
|
|
157
159
|
str,
|
|
@@ -171,13 +173,13 @@ class RestfulRouter:
|
|
|
171
173
|
def with_read_many(
|
|
172
174
|
self,
|
|
173
175
|
query: FluentDict[Any],
|
|
174
|
-
dependency: Dependency | None = None,
|
|
176
|
+
dependency: Dependency[T] | None = None,
|
|
175
177
|
*,
|
|
176
178
|
is_documented: bool = True,
|
|
177
179
|
) -> Self:
|
|
178
180
|
self.router.add_api_route(
|
|
179
181
|
"",
|
|
180
|
-
self.
|
|
182
|
+
self.endpoints.read_many_endpoint(
|
|
181
183
|
Service=self._resolve(dependency),
|
|
182
184
|
QueryParams=Annotated[
|
|
183
185
|
Schema(self.name.singular).optional_schema_for(
|
|
@@ -199,13 +201,13 @@ class RestfulRouter:
|
|
|
199
201
|
|
|
200
202
|
def with_read_all(
|
|
201
203
|
self,
|
|
202
|
-
dependency: Dependency | None = None,
|
|
204
|
+
dependency: Dependency[T] | None = None,
|
|
203
205
|
*,
|
|
204
206
|
is_documented: bool = True,
|
|
205
207
|
) -> Self:
|
|
206
208
|
self.router.add_api_route(
|
|
207
209
|
"",
|
|
208
|
-
self.
|
|
210
|
+
self.endpoints.read_all_endpoint(
|
|
209
211
|
Service=self._resolve(dependency),
|
|
210
212
|
),
|
|
211
213
|
methods=["GET"],
|
|
@@ -220,13 +222,13 @@ class RestfulRouter:
|
|
|
220
222
|
|
|
221
223
|
def with_update_one(
|
|
222
224
|
self,
|
|
223
|
-
dependency: Dependency | None = None,
|
|
225
|
+
dependency: Dependency[T] | None = None,
|
|
224
226
|
*,
|
|
225
227
|
is_documented: bool = True,
|
|
226
228
|
) -> Self:
|
|
227
229
|
self.router.add_api_route(
|
|
228
230
|
self.item_path,
|
|
229
|
-
self.
|
|
231
|
+
self.endpoints.update_one_endpoint(
|
|
230
232
|
Service=self._resolve(dependency),
|
|
231
233
|
ItemId=Annotated[
|
|
232
234
|
str,
|
|
@@ -249,13 +251,13 @@ class RestfulRouter:
|
|
|
249
251
|
|
|
250
252
|
def with_replace_one(
|
|
251
253
|
self,
|
|
252
|
-
dependency: Dependency | None = None,
|
|
254
|
+
dependency: Dependency[T] | None = None,
|
|
253
255
|
*,
|
|
254
256
|
is_documented: bool = True,
|
|
255
257
|
) -> Self:
|
|
256
258
|
self.router.add_api_route(
|
|
257
259
|
"",
|
|
258
|
-
self.
|
|
260
|
+
self.endpoints.replace_one_endpoint(
|
|
259
261
|
Service=self._resolve(dependency),
|
|
260
262
|
Item=Annotated[
|
|
261
263
|
RawItem,
|
|
@@ -274,13 +276,13 @@ class RestfulRouter:
|
|
|
274
276
|
|
|
275
277
|
def with_delete_one(
|
|
276
278
|
self,
|
|
277
|
-
dependency: Dependency | None = None,
|
|
279
|
+
dependency: Dependency[T] | None = None,
|
|
278
280
|
*,
|
|
279
281
|
is_documented: bool = True,
|
|
280
282
|
) -> Self:
|
|
281
283
|
self.router.add_api_route(
|
|
282
284
|
self.item_path,
|
|
283
|
-
self.
|
|
285
|
+
self.endpoints.delete_one_endpoint(
|
|
284
286
|
Service=self._resolve(dependency),
|
|
285
287
|
ItemId=Annotated[
|
|
286
288
|
str,
|
|
@@ -315,7 +317,7 @@ class RestfulRouter:
|
|
|
315
317
|
def build(self) -> APIRouter:
|
|
316
318
|
return self.router
|
|
317
319
|
|
|
318
|
-
def _resolve(self, dependency: Dependency | None) -> type[
|
|
320
|
+
def _resolve(self, dependency: Dependency[T] | None) -> type[RestfulResource[T]]:
|
|
319
321
|
resolved = dependency or self._dependency
|
|
320
322
|
assert resolved, "One of default or endpoint dependency must be specified"
|
|
321
323
|
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
from typing import TYPE_CHECKING, Any
|
|
6
|
+
|
|
7
|
+
from pypebbles import JsonDict
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from collections.abc import Iterable, Mapping
|
|
11
|
+
|
|
12
|
+
from apexdevkit.formatter import Formatter
|
|
13
|
+
from apexdevkit.repository import Entity, Repository
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass(frozen=True, kw_only=True)
|
|
17
|
+
class RestfulResource[T]:
|
|
18
|
+
service: RestfulService[T]
|
|
19
|
+
formatter: Formatter[Mapping[str, Any], T]
|
|
20
|
+
|
|
21
|
+
def create_one(self, item: Mapping[str, Any]) -> Mapping[str, Any]:
|
|
22
|
+
result = self.service.create_one(self.formatter.load(item))
|
|
23
|
+
|
|
24
|
+
return self.formatter.dump(result)
|
|
25
|
+
|
|
26
|
+
def read_one(self, item_id: str) -> Mapping[str, Any]:
|
|
27
|
+
result = self.service.read_one(with_id=item_id)
|
|
28
|
+
|
|
29
|
+
return self.formatter.dump(result)
|
|
30
|
+
|
|
31
|
+
def read_all(self) -> Iterable[Mapping[str, Any]]:
|
|
32
|
+
result = self.service.read_all()
|
|
33
|
+
|
|
34
|
+
return [self.formatter.dump(item) for item in result]
|
|
35
|
+
|
|
36
|
+
def read_many(self, **params: Any) -> Iterable[Mapping[str, Any]]:
|
|
37
|
+
result = self.service.read_many(JsonDict(params))
|
|
38
|
+
|
|
39
|
+
return [self.formatter.dump(item) for item in result]
|
|
40
|
+
|
|
41
|
+
def update_one(self, item_id: str, **with_fields: Any) -> Mapping[str, Any]:
|
|
42
|
+
current = self.service.read_one(with_id=item_id)
|
|
43
|
+
updated = {**self.formatter.dump(current), **with_fields}
|
|
44
|
+
result = self.service.update_one(self.formatter.load(updated))
|
|
45
|
+
|
|
46
|
+
return self.formatter.dump(result)
|
|
47
|
+
|
|
48
|
+
def replace_one(self, item: Mapping[str, Any]) -> Mapping[str, Any]:
|
|
49
|
+
result = self.service.update_one(self.formatter.load(item))
|
|
50
|
+
|
|
51
|
+
return self.formatter.dump(result)
|
|
52
|
+
|
|
53
|
+
def delete_one(self, item_id: str) -> None:
|
|
54
|
+
self.service.delete_one(with_id=item_id)
|
|
55
|
+
|
|
56
|
+
@dataclass(kw_only=True)
|
|
57
|
+
class Builder[BT](ABC):
|
|
58
|
+
formatter: Formatter[Mapping[str, Any], BT]
|
|
59
|
+
|
|
60
|
+
parent_id: str = field(init=False)
|
|
61
|
+
user: Any = field(init=False)
|
|
62
|
+
|
|
63
|
+
def with_user(self, user: Any) -> RestfulResource.Builder[BT]:
|
|
64
|
+
self.user = user
|
|
65
|
+
|
|
66
|
+
return self
|
|
67
|
+
|
|
68
|
+
def with_parent(self, identity: str) -> RestfulResource.Builder[BT]:
|
|
69
|
+
self.parent_id = identity
|
|
70
|
+
|
|
71
|
+
return self
|
|
72
|
+
|
|
73
|
+
def build(self) -> RestfulResource[BT]:
|
|
74
|
+
return RestfulResource(
|
|
75
|
+
service=self.service(),
|
|
76
|
+
formatter=self.formatter,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
@abstractmethod
|
|
80
|
+
def service(self) -> RestfulService[BT]:
|
|
81
|
+
pass
|
|
82
|
+
|
|
83
|
+
@dataclass(kw_only=True)
|
|
84
|
+
class PreBuilt[PBT](Builder[PBT]): # pragma: no cover
|
|
85
|
+
instance: RestfulService[PBT]
|
|
86
|
+
|
|
87
|
+
def service(self) -> RestfulService[PBT]:
|
|
88
|
+
return self.instance
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class RestfulService[T]:
|
|
92
|
+
def create_one(self, item: T) -> T:
|
|
93
|
+
raise NotImplementedError
|
|
94
|
+
|
|
95
|
+
def read_one(self, *, with_id: str) -> T:
|
|
96
|
+
raise NotImplementedError
|
|
97
|
+
|
|
98
|
+
def read_all(self) -> Iterable[T]:
|
|
99
|
+
raise NotImplementedError
|
|
100
|
+
|
|
101
|
+
def read_many(self, query: JsonDict) -> Iterable[T]:
|
|
102
|
+
raise NotImplementedError
|
|
103
|
+
|
|
104
|
+
def update_one(self, item: T) -> T:
|
|
105
|
+
raise NotImplementedError
|
|
106
|
+
|
|
107
|
+
def delete_one(self, with_id: str) -> T:
|
|
108
|
+
raise NotImplementedError
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@dataclass(frozen=True)
|
|
112
|
+
class RestfulRepository[T: Entity](RestfulService[T]):
|
|
113
|
+
repository: Repository[T]
|
|
114
|
+
|
|
115
|
+
def create_one(self, item: T) -> T:
|
|
116
|
+
return self.repository.create(item)
|
|
117
|
+
|
|
118
|
+
def read_one(self, *, with_id: str) -> T:
|
|
119
|
+
return self.repository.read(with_id)
|
|
120
|
+
|
|
121
|
+
def read_all(self) -> Iterable[T]:
|
|
122
|
+
yield from self.repository
|
|
123
|
+
|
|
124
|
+
def read_many(self, query: JsonDict) -> Iterable[T]:
|
|
125
|
+
raise NotImplementedError
|
|
126
|
+
|
|
127
|
+
def update_one(self, item: T) -> T:
|
|
128
|
+
self.repository.update(item)
|
|
129
|
+
|
|
130
|
+
return item
|
|
131
|
+
|
|
132
|
+
def delete_one(self, with_id: str) -> T:
|
|
133
|
+
result = self.repository.read(with_id)
|
|
134
|
+
|
|
135
|
+
self.repository.delete(with_id)
|
|
136
|
+
|
|
137
|
+
return result
|
|
@@ -17,6 +17,14 @@ class Formatter[SourceT, TargetT](Protocol): # pragma: no cover
|
|
|
17
17
|
pass
|
|
18
18
|
|
|
19
19
|
|
|
20
|
+
class NoFormatter[S, T]:
|
|
21
|
+
def load(self, source: S) -> T:
|
|
22
|
+
raise NotImplementedError
|
|
23
|
+
|
|
24
|
+
def dump(self, target: T) -> S:
|
|
25
|
+
raise NotImplementedError
|
|
26
|
+
|
|
27
|
+
|
|
20
28
|
@dataclass(frozen=True)
|
|
21
29
|
class AliasFormatter[TargetT](Formatter[Mapping[str, Any], TargetT]):
|
|
22
30
|
inner: Formatter[Mapping[str, Any], TargetT]
|
|
@@ -127,9 +127,10 @@ class ResponseProbe:
|
|
|
127
127
|
return self.with_item(value)
|
|
128
128
|
|
|
129
129
|
def with_item(self, value: Any) -> Self:
|
|
130
|
-
|
|
130
|
+
if self.response.item() == value:
|
|
131
|
+
return self
|
|
131
132
|
|
|
132
|
-
|
|
133
|
+
raise AssertionError("expected=", self.response.item(), "actual=", value)
|
|
133
134
|
|
|
134
135
|
def and_collection(self, values: Collection[Any]) -> Self:
|
|
135
136
|
return self.with_collection(values)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "apexdevkit"
|
|
3
|
-
version = "2.
|
|
3
|
+
version = "2.3.1"
|
|
4
4
|
description = "Apex Development Tools for python."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [
|
|
@@ -31,7 +31,7 @@ pytest-env = "*"
|
|
|
31
31
|
pytest-recording = "*"
|
|
32
32
|
|
|
33
33
|
[tool.ruff.lint.per-file-ignores]
|
|
34
|
-
"apexdevkit/fastapi/
|
|
34
|
+
"apexdevkit/fastapi/endpoints.py" = [ "N803" ]
|
|
35
35
|
"apexdevkit/fastapi/dependable.py" = [ "N806" ]
|
|
36
36
|
|
|
37
37
|
[tool.ruff.lint.mccabe]
|
|
@@ -80,7 +80,7 @@ ignore = [
|
|
|
80
80
|
[tool.mypy]
|
|
81
81
|
exclude = [
|
|
82
82
|
"apexdevkit/fastapi/dependable.py",
|
|
83
|
-
"apexdevkit/fastapi/
|
|
83
|
+
"apexdevkit/fastapi/endpoints.py",
|
|
84
84
|
"apexdevkit/fastapi/schema.py",
|
|
85
85
|
]
|
|
86
86
|
ignore_missing_imports = true
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
from apexdevkit.fastapi.builder import FastApiBuilder, RestfulServiceBuilder
|
|
2
|
-
from apexdevkit.fastapi.dependable import inject
|
|
3
|
-
from apexdevkit.fastapi.router import RestfulRouter, RouterWithHiddenUnderscoreRoutes
|
|
4
|
-
from apexdevkit.fastapi.service import RestfulRepository
|
|
5
|
-
|
|
6
|
-
__all__ = [
|
|
7
|
-
"FastApiBuilder",
|
|
8
|
-
"RestfulRepository",
|
|
9
|
-
"RestfulRouter",
|
|
10
|
-
"RestfulServiceBuilder",
|
|
11
|
-
"RouterWithHiddenUnderscoreRoutes",
|
|
12
|
-
"inject",
|
|
13
|
-
]
|
|
@@ -1,68 +0,0 @@
|
|
|
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)
|
|
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
|