apexdevkit 1.5.14__tar.gz → 1.5.16__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.5.14 → apexdevkit-1.5.16}/PKG-INFO +1 -1
- {apexdevkit-1.5.14 → apexdevkit-1.5.16}/apexdevkit/fastapi/builder.py +0 -3
- {apexdevkit-1.5.14 → apexdevkit-1.5.16}/apexdevkit/fastapi/router.py +101 -15
- {apexdevkit-1.5.14 → apexdevkit-1.5.16}/apexdevkit/fastapi/schema.py +20 -10
- {apexdevkit-1.5.14 → apexdevkit-1.5.16}/apexdevkit/fastapi/service.py +18 -13
- {apexdevkit-1.5.14 → apexdevkit-1.5.16}/apexdevkit/testing/rest.py +42 -2
- {apexdevkit-1.5.14 → apexdevkit-1.5.16}/pyproject.toml +1 -1
- {apexdevkit-1.5.14 → apexdevkit-1.5.16}/LICENSE +0 -0
- {apexdevkit-1.5.14 → apexdevkit-1.5.16}/README.md +0 -0
- {apexdevkit-1.5.14 → apexdevkit-1.5.16}/apexdevkit/__init__.py +0 -0
- {apexdevkit-1.5.14 → apexdevkit-1.5.16}/apexdevkit/annotation/__init__.py +0 -0
- {apexdevkit-1.5.14 → apexdevkit-1.5.16}/apexdevkit/annotation/deprecate.py +0 -0
- {apexdevkit-1.5.14 → apexdevkit-1.5.16}/apexdevkit/error.py +0 -0
- {apexdevkit-1.5.14 → apexdevkit-1.5.16}/apexdevkit/fastapi/__init__.py +0 -0
- {apexdevkit-1.5.14 → apexdevkit-1.5.16}/apexdevkit/fastapi/dependable.py +0 -0
- {apexdevkit-1.5.14 → apexdevkit-1.5.16}/apexdevkit/fastapi/docs.py +0 -0
- {apexdevkit-1.5.14 → apexdevkit-1.5.16}/apexdevkit/fastapi/response.py +0 -0
- {apexdevkit-1.5.14 → apexdevkit-1.5.16}/apexdevkit/formatter.py +0 -0
- {apexdevkit-1.5.14 → apexdevkit-1.5.16}/apexdevkit/http/__init__.py +0 -0
- {apexdevkit-1.5.14 → apexdevkit-1.5.16}/apexdevkit/http/fake.py +0 -0
- {apexdevkit-1.5.14 → apexdevkit-1.5.16}/apexdevkit/http/fluent.py +0 -0
- {apexdevkit-1.5.14 → apexdevkit-1.5.16}/apexdevkit/http/httpx.py +0 -0
- {apexdevkit-1.5.14 → apexdevkit-1.5.16}/apexdevkit/http/json.py +0 -0
- {apexdevkit-1.5.14 → apexdevkit-1.5.16}/apexdevkit/http/url.py +0 -0
- {apexdevkit-1.5.14 → apexdevkit-1.5.16}/apexdevkit/py.typed +0 -0
- {apexdevkit-1.5.14 → apexdevkit-1.5.16}/apexdevkit/repository/__init__.py +0 -0
- {apexdevkit-1.5.14 → apexdevkit-1.5.16}/apexdevkit/repository/base.py +0 -0
- {apexdevkit-1.5.14 → apexdevkit-1.5.16}/apexdevkit/repository/connector.py +0 -0
- {apexdevkit-1.5.14 → apexdevkit-1.5.16}/apexdevkit/repository/database.py +0 -0
- {apexdevkit-1.5.14 → apexdevkit-1.5.16}/apexdevkit/repository/in_memory.py +0 -0
- {apexdevkit-1.5.14 → apexdevkit-1.5.16}/apexdevkit/repository/interface.py +0 -0
- {apexdevkit-1.5.14 → apexdevkit-1.5.16}/apexdevkit/testing/__init__.py +0 -0
- {apexdevkit-1.5.14 → apexdevkit-1.5.16}/apexdevkit/testing/database.py +0 -0
- {apexdevkit-1.5.14 → apexdevkit-1.5.16}/apexdevkit/testing/fake.py +0 -0
|
@@ -3,8 +3,6 @@ from typing import Any, Self
|
|
|
3
3
|
|
|
4
4
|
from fastapi import APIRouter, FastAPI
|
|
5
5
|
|
|
6
|
-
from apexdevkit.annotation import deprecated
|
|
7
|
-
|
|
8
6
|
|
|
9
7
|
@dataclass
|
|
10
8
|
class FastApiBuilder:
|
|
@@ -28,7 +26,6 @@ class FastApiBuilder:
|
|
|
28
26
|
|
|
29
27
|
return self
|
|
30
28
|
|
|
31
|
-
@deprecated("Pass dependencies to router via with_infra method instead")
|
|
32
29
|
def with_dependency(self, **values: Any) -> Self: # pragma: no cover
|
|
33
30
|
for key, value in values.items():
|
|
34
31
|
setattr(self.app.state, key, value)
|
|
@@ -6,9 +6,8 @@ from typing import Annotated, Any, Callable, Iterable, Self, TypeVar
|
|
|
6
6
|
from fastapi import APIRouter, Depends, Path
|
|
7
7
|
from fastapi.responses import JSONResponse
|
|
8
8
|
|
|
9
|
-
from apexdevkit.annotation import deprecated
|
|
10
9
|
from apexdevkit.error import DoesNotExistError, ExistsError, ForbiddenError
|
|
11
|
-
from apexdevkit.fastapi.schema import
|
|
10
|
+
from apexdevkit.fastapi.schema import RestfulSchema, SchemaFields
|
|
12
11
|
from apexdevkit.fastapi.service import RawCollection, RawItem, RestfulService
|
|
13
12
|
from apexdevkit.testing import RestfulName
|
|
14
13
|
|
|
@@ -100,7 +99,6 @@ class RestfulServiceBuilder(ABC):
|
|
|
100
99
|
pass
|
|
101
100
|
|
|
102
101
|
|
|
103
|
-
@deprecated("Use infra instead")
|
|
104
102
|
@dataclass
|
|
105
103
|
class PreBuiltRestfulService(RestfulServiceBuilder): # pragma: no cover
|
|
106
104
|
service: RestfulService
|
|
@@ -149,12 +147,6 @@ class RestfulRouter:
|
|
|
149
147
|
def item_path(self) -> str:
|
|
150
148
|
return "/{" + self.id_alias + "}"
|
|
151
149
|
|
|
152
|
-
@deprecated("Use with_name and with_fields instead")
|
|
153
|
-
def with_dataclass(self, value: Any) -> Self: # pragma: no cover
|
|
154
|
-
return self.with_name(RestfulName(value.__name__.lower())).with_fields(
|
|
155
|
-
DataclassFields(value)
|
|
156
|
-
)
|
|
157
|
-
|
|
158
150
|
def with_name(self, value: RestfulName) -> Self:
|
|
159
151
|
self.name = value
|
|
160
152
|
|
|
@@ -170,12 +162,6 @@ class RestfulRouter:
|
|
|
170
162
|
|
|
171
163
|
return self
|
|
172
164
|
|
|
173
|
-
@deprecated("Use with_infra instead")
|
|
174
|
-
def with_service(self, value: RestfulService) -> Self: # pragma: no cover
|
|
175
|
-
self.infra = PreBuiltRestfulService(value)
|
|
176
|
-
|
|
177
|
-
return self
|
|
178
|
-
|
|
179
165
|
def with_infra(self, value: RestfulServiceBuilder) -> Self:
|
|
180
166
|
self.infra = value
|
|
181
167
|
|
|
@@ -481,6 +467,106 @@ class RestfulRouter:
|
|
|
481
467
|
|
|
482
468
|
return endpoint
|
|
483
469
|
|
|
470
|
+
def with_replace_one_endpoint(
|
|
471
|
+
self,
|
|
472
|
+
is_documented: bool = True,
|
|
473
|
+
extract_user: Callable[..., Any] = no_user,
|
|
474
|
+
) -> Self:
|
|
475
|
+
self.router.add_api_route(
|
|
476
|
+
"",
|
|
477
|
+
self.replace_one(
|
|
478
|
+
User=Annotated[
|
|
479
|
+
Any,
|
|
480
|
+
Depends(extract_user),
|
|
481
|
+
],
|
|
482
|
+
ParentId=Annotated[
|
|
483
|
+
str,
|
|
484
|
+
Path(alias=self.parent_id_alias, default_factory=str),
|
|
485
|
+
],
|
|
486
|
+
Item=Annotated[
|
|
487
|
+
RawItem,
|
|
488
|
+
Depends(self.schema.for_replace_one()),
|
|
489
|
+
],
|
|
490
|
+
),
|
|
491
|
+
methods=["PUT"],
|
|
492
|
+
status_code=200,
|
|
493
|
+
responses={404: {}},
|
|
494
|
+
response_model=self.schema.for_no_data(),
|
|
495
|
+
include_in_schema=is_documented,
|
|
496
|
+
summary="Replace One",
|
|
497
|
+
)
|
|
498
|
+
|
|
499
|
+
return self
|
|
500
|
+
|
|
501
|
+
def replace_one(self, User, ParentId, Item) -> Callable[..., _Response]: # type: ignore
|
|
502
|
+
def endpoint(user: User, parent_id: ParentId, item: Item) -> _Response:
|
|
503
|
+
try:
|
|
504
|
+
service = self.infra.with_user(user).with_parent(parent_id).build()
|
|
505
|
+
except DoesNotExistError as e:
|
|
506
|
+
return JSONResponse(
|
|
507
|
+
RestfulResponse(RestfulName(self.parent)).not_found(e), 404
|
|
508
|
+
)
|
|
509
|
+
try:
|
|
510
|
+
service.replace_one(item)
|
|
511
|
+
except DoesNotExistError as e:
|
|
512
|
+
return JSONResponse(self.response.not_found(e), 404)
|
|
513
|
+
except ForbiddenError as e:
|
|
514
|
+
return JSONResponse(self.response.forbidden(e), 403)
|
|
515
|
+
|
|
516
|
+
return self.response.ok()
|
|
517
|
+
|
|
518
|
+
return endpoint
|
|
519
|
+
|
|
520
|
+
def with_replace_many_endpoint(
|
|
521
|
+
self,
|
|
522
|
+
is_documented: bool = True,
|
|
523
|
+
extract_user: Callable[..., Any] = no_user,
|
|
524
|
+
) -> Self:
|
|
525
|
+
self.router.add_api_route(
|
|
526
|
+
"/batch",
|
|
527
|
+
self.replace_many(
|
|
528
|
+
User=Annotated[
|
|
529
|
+
Any,
|
|
530
|
+
Depends(extract_user),
|
|
531
|
+
],
|
|
532
|
+
ParentId=Annotated[
|
|
533
|
+
str,
|
|
534
|
+
Path(alias=self.parent_id_alias, default_factory=str),
|
|
535
|
+
],
|
|
536
|
+
Collection=Annotated[
|
|
537
|
+
RawCollection,
|
|
538
|
+
Depends(self.schema.for_replace_many()),
|
|
539
|
+
],
|
|
540
|
+
),
|
|
541
|
+
methods=["PUT"],
|
|
542
|
+
status_code=200,
|
|
543
|
+
responses={},
|
|
544
|
+
response_model=self.schema.for_no_data(),
|
|
545
|
+
include_in_schema=is_documented,
|
|
546
|
+
summary="Replace Many",
|
|
547
|
+
)
|
|
548
|
+
|
|
549
|
+
return self
|
|
550
|
+
|
|
551
|
+
def replace_many(self, User, ParentId, Collection) -> Callable[..., _Response]: # type: ignore
|
|
552
|
+
def endpoint(user: User, parent_id: ParentId, items: Collection) -> _Response:
|
|
553
|
+
try:
|
|
554
|
+
service = self.infra.with_user(user).with_parent(parent_id).build()
|
|
555
|
+
except DoesNotExistError as e:
|
|
556
|
+
return JSONResponse(
|
|
557
|
+
RestfulResponse(RestfulName(self.parent)).not_found(e), 404
|
|
558
|
+
)
|
|
559
|
+
try:
|
|
560
|
+
service.replace_many(items)
|
|
561
|
+
except DoesNotExistError as e:
|
|
562
|
+
return JSONResponse(self.response.not_found(e), 404)
|
|
563
|
+
except ForbiddenError as e:
|
|
564
|
+
return JSONResponse(self.response.forbidden(e), 403)
|
|
565
|
+
|
|
566
|
+
return self.response.ok()
|
|
567
|
+
|
|
568
|
+
return endpoint
|
|
569
|
+
|
|
484
570
|
def with_delete_one_endpoint(
|
|
485
571
|
self,
|
|
486
572
|
is_documented: bool = True,
|
|
@@ -5,7 +5,6 @@ from typing import Any, Callable, Iterable, List
|
|
|
5
5
|
|
|
6
6
|
from pydantic import BaseModel, create_model
|
|
7
7
|
|
|
8
|
-
from apexdevkit.annotation import deprecated
|
|
9
8
|
from apexdevkit.http import JsonDict
|
|
10
9
|
from apexdevkit.testing import RestfulName
|
|
11
10
|
|
|
@@ -25,15 +24,6 @@ class SchemaFields(ABC):
|
|
|
25
24
|
pass
|
|
26
25
|
|
|
27
26
|
|
|
28
|
-
@deprecated("Use custom schema fields instead")
|
|
29
|
-
@dataclass
|
|
30
|
-
class DataclassFields(SchemaFields): # pragma: no cover
|
|
31
|
-
source: Any
|
|
32
|
-
|
|
33
|
-
def readable(self) -> JsonDict:
|
|
34
|
-
return JsonDict(self.source.__annotations__)
|
|
35
|
-
|
|
36
|
-
|
|
37
27
|
@dataclass
|
|
38
28
|
class RestfulSchema:
|
|
39
29
|
name: RestfulName
|
|
@@ -43,6 +33,7 @@ class RestfulSchema:
|
|
|
43
33
|
schema = self._schema_for("", self.fields.readable())
|
|
44
34
|
create_schema = self._schema_for("Create", self.fields.writable())
|
|
45
35
|
self._schema_for("Update", self.fields.editable())
|
|
36
|
+
replace_schema = self._schema_for("Replace", self.fields.readable())
|
|
46
37
|
update_many_item = self._schema_for(
|
|
47
38
|
"UpdateManyItem", self.fields.editable().merge(self.fields.id())
|
|
48
39
|
)
|
|
@@ -64,6 +55,9 @@ class RestfulSchema:
|
|
|
64
55
|
"UpdateMany",
|
|
65
56
|
JsonDict({self.name.plural: List[update_many_item]}),
|
|
66
57
|
)
|
|
58
|
+
self._schema_for(
|
|
59
|
+
"ReplaceMany", JsonDict({self.name.plural: List[replace_schema]})
|
|
60
|
+
)
|
|
67
61
|
|
|
68
62
|
def _schema_for(self, action: str, fields: dict[str, Any]) -> type[BaseModel]:
|
|
69
63
|
if action not in self.schemas:
|
|
@@ -139,3 +133,19 @@ class RestfulSchema:
|
|
|
139
133
|
return [dict(item) for item in request.model_dump()[self.name.plural]]
|
|
140
134
|
|
|
141
135
|
return _
|
|
136
|
+
|
|
137
|
+
def for_replace_one(self) -> Callable[[BaseModel], dict[str, Any]]:
|
|
138
|
+
schema = self.schemas["Replace"]
|
|
139
|
+
|
|
140
|
+
def _(request: schema) -> dict[str, Any]:
|
|
141
|
+
return request.model_dump()
|
|
142
|
+
|
|
143
|
+
return _
|
|
144
|
+
|
|
145
|
+
def for_replace_many(self) -> Callable[[BaseModel], Iterable[dict[str, Any]]]:
|
|
146
|
+
schema = self.schemas["ReplaceMany"]
|
|
147
|
+
|
|
148
|
+
def _(request: schema) -> Iterable[dict[str, Any]]:
|
|
149
|
+
return [dict(item) for item in request.model_dump()[self.name.plural]]
|
|
150
|
+
|
|
151
|
+
return _
|
|
@@ -2,7 +2,7 @@ from abc import ABC
|
|
|
2
2
|
from dataclasses import dataclass, field
|
|
3
3
|
from typing import Any, Dict, Generic, Iterable, Self, TypeVar
|
|
4
4
|
|
|
5
|
-
from apexdevkit.formatter import
|
|
5
|
+
from apexdevkit.formatter import Formatter
|
|
6
6
|
from apexdevkit.repository.interface import Repository
|
|
7
7
|
|
|
8
8
|
RawItem = dict[str, Any]
|
|
@@ -36,6 +36,12 @@ class RestfulService(ABC): # pragma: no cover
|
|
|
36
36
|
def update_many(self, items: RawCollectionWithId) -> RawCollection:
|
|
37
37
|
raise NotImplementedError(self.update_many.__name__)
|
|
38
38
|
|
|
39
|
+
def replace_one(self, item: RawItem) -> RawItem:
|
|
40
|
+
raise NotImplementedError(self.replace_one.__name__)
|
|
41
|
+
|
|
42
|
+
def replace_many(self, items: RawCollection) -> RawCollection:
|
|
43
|
+
raise NotImplementedError(self.replace_many.__name__)
|
|
44
|
+
|
|
39
45
|
def delete_one(self, item_id: str) -> None:
|
|
40
46
|
raise NotImplementedError(self.delete_one.__name__)
|
|
41
47
|
|
|
@@ -45,15 +51,9 @@ ItemT = TypeVar("ItemT")
|
|
|
45
51
|
|
|
46
52
|
@dataclass
|
|
47
53
|
class RestfulRepositoryBuilder(Generic[ItemT]):
|
|
48
|
-
|
|
49
|
-
formatter: Formatter[ItemT] | None = field(init=False, default=None)
|
|
54
|
+
formatter: Formatter[ItemT] = field(init=False)
|
|
50
55
|
repository: Repository[Any, ItemT] = field(init=False)
|
|
51
56
|
|
|
52
|
-
def with_resource(self, resource: type[ItemT]) -> Self:
|
|
53
|
-
self.resource = resource
|
|
54
|
-
|
|
55
|
-
return self
|
|
56
|
-
|
|
57
57
|
def with_formatter(self, formatter: Formatter[ItemT]) -> Self:
|
|
58
58
|
self.formatter = formatter
|
|
59
59
|
|
|
@@ -65,11 +65,6 @@ class RestfulRepositoryBuilder(Generic[ItemT]):
|
|
|
65
65
|
return self
|
|
66
66
|
|
|
67
67
|
def build(self) -> RestfulService:
|
|
68
|
-
if not self.formatter and self.resource:
|
|
69
|
-
self.with_formatter(DataclassFormatter(self.resource))
|
|
70
|
-
|
|
71
|
-
assert self.formatter, "Must provide either resource or formatter"
|
|
72
|
-
|
|
73
68
|
return _RestfulNestedRepository(self.formatter, self.repository)
|
|
74
69
|
|
|
75
70
|
|
|
@@ -115,5 +110,15 @@ class _RestfulNestedRepository(RestfulService, Generic[ItemT]):
|
|
|
115
110
|
|
|
116
111
|
return [self.formatter.dump(item) for item in updates]
|
|
117
112
|
|
|
113
|
+
def replace_one(self, item: RawItem) -> RawItem:
|
|
114
|
+
self.repository.update(self.formatter.load(item))
|
|
115
|
+
|
|
116
|
+
return item
|
|
117
|
+
|
|
118
|
+
def replace_many(self, items: RawCollection) -> RawCollection:
|
|
119
|
+
self.repository.update_many([self.formatter.load(item) for item in items])
|
|
120
|
+
|
|
121
|
+
return items
|
|
122
|
+
|
|
118
123
|
def delete_one(self, item_id: str) -> None:
|
|
119
124
|
self.repository.delete(item_id)
|
|
@@ -19,6 +19,9 @@ class RestResource:
|
|
|
19
19
|
def create_one(self) -> CreateOne:
|
|
20
20
|
return CreateOne(self.name, self.http)
|
|
21
21
|
|
|
22
|
+
def create_many(self) -> CreateMany:
|
|
23
|
+
return CreateMany(self.name, self.http)
|
|
24
|
+
|
|
22
25
|
def read_one(self) -> ReadOne:
|
|
23
26
|
return ReadOne(self.name, self.http)
|
|
24
27
|
|
|
@@ -31,8 +34,11 @@ class RestResource:
|
|
|
31
34
|
def update_many(self) -> UpdateMany:
|
|
32
35
|
return UpdateMany(self.name, self.http)
|
|
33
36
|
|
|
34
|
-
def
|
|
35
|
-
return
|
|
37
|
+
def replace_one(self) -> ReplaceOne:
|
|
38
|
+
return ReplaceOne(self.name, self.http)
|
|
39
|
+
|
|
40
|
+
def replace_many(self) -> ReplaceMany:
|
|
41
|
+
return ReplaceMany(self.name, self.http)
|
|
36
42
|
|
|
37
43
|
def delete_one(self) -> DeleteOne:
|
|
38
44
|
return DeleteOne(self.name, self.http)
|
|
@@ -182,6 +188,20 @@ class UpdateOne(RestRequest):
|
|
|
182
188
|
return self
|
|
183
189
|
|
|
184
190
|
|
|
191
|
+
@dataclass
|
|
192
|
+
class ReplaceOne(RestRequest):
|
|
193
|
+
data: JsonDict = field(init=False)
|
|
194
|
+
|
|
195
|
+
@cached_property
|
|
196
|
+
def response(self) -> httpx.Response:
|
|
197
|
+
return self.http.put(self.resource + "", json=dict(self.data))
|
|
198
|
+
|
|
199
|
+
def from_data(self, value: JsonDict) -> Self:
|
|
200
|
+
self.data = value
|
|
201
|
+
|
|
202
|
+
return self
|
|
203
|
+
|
|
204
|
+
|
|
185
205
|
@dataclass
|
|
186
206
|
class CreateMany(RestRequest):
|
|
187
207
|
data: list[JsonDict] = field(default_factory=list)
|
|
@@ -222,6 +242,26 @@ class UpdateMany(RestRequest):
|
|
|
222
242
|
return self.from_data(value)
|
|
223
243
|
|
|
224
244
|
|
|
245
|
+
@dataclass
|
|
246
|
+
class ReplaceMany(RestRequest):
|
|
247
|
+
data: list[JsonDict] = field(default_factory=list)
|
|
248
|
+
|
|
249
|
+
@cached_property
|
|
250
|
+
def response(self) -> httpx.Response:
|
|
251
|
+
return self.http.put(
|
|
252
|
+
self.resource + "batch",
|
|
253
|
+
json={self.resource.plural: [dict(data) for data in self.data]},
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
def from_data(self, value: JsonDict) -> Self:
|
|
257
|
+
self.data.append(value)
|
|
258
|
+
|
|
259
|
+
return self
|
|
260
|
+
|
|
261
|
+
def and_data(self, value: JsonDict) -> Self:
|
|
262
|
+
return self.from_data(value)
|
|
263
|
+
|
|
264
|
+
|
|
225
265
|
@dataclass
|
|
226
266
|
class DeleteOne(RestRequest):
|
|
227
267
|
item_id: str = field(init=False)
|
|
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
|