apexdevkit 1.9.4__tar.gz → 1.9.6__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.9.4 → apexdevkit-1.9.6}/PKG-INFO +1 -1
- {apexdevkit-1.9.4 → apexdevkit-1.9.6}/apexdevkit/fastapi/schema.py +13 -28
- apexdevkit-1.9.6/apexdevkit/fluent.py +40 -0
- apexdevkit-1.9.6/apexdevkit/http/json.py +5 -0
- {apexdevkit-1.9.4 → apexdevkit-1.9.6}/apexdevkit/repository/connector.py +2 -4
- {apexdevkit-1.9.4 → apexdevkit-1.9.6}/apexdevkit/repository/database.py +1 -53
- apexdevkit-1.9.6/apexdevkit/repository/mongo.py +110 -0
- {apexdevkit-1.9.4 → apexdevkit-1.9.6}/apexdevkit/testing/rest.py +2 -16
- {apexdevkit-1.9.4 → apexdevkit-1.9.6}/pyproject.toml +1 -1
- apexdevkit-1.9.4/apexdevkit/http/json.py +0 -38
- apexdevkit-1.9.4/apexdevkit/repository/mongo.py +0 -79
- {apexdevkit-1.9.4 → apexdevkit-1.9.6}/LICENSE +0 -0
- {apexdevkit-1.9.4 → apexdevkit-1.9.6}/README.md +0 -0
- {apexdevkit-1.9.4 → apexdevkit-1.9.6}/apexdevkit/__init__.py +0 -0
- {apexdevkit-1.9.4 → apexdevkit-1.9.6}/apexdevkit/annotation/__init__.py +0 -0
- {apexdevkit-1.9.4 → apexdevkit-1.9.6}/apexdevkit/annotation/deprecate.py +0 -0
- {apexdevkit-1.9.4 → apexdevkit-1.9.6}/apexdevkit/error.py +0 -0
- {apexdevkit-1.9.4 → apexdevkit-1.9.6}/apexdevkit/fastapi/__init__.py +0 -0
- {apexdevkit-1.9.4 → apexdevkit-1.9.6}/apexdevkit/fastapi/builder.py +0 -0
- {apexdevkit-1.9.4 → apexdevkit-1.9.6}/apexdevkit/fastapi/dependable.py +0 -0
- {apexdevkit-1.9.4 → apexdevkit-1.9.6}/apexdevkit/fastapi/docs.py +0 -0
- {apexdevkit-1.9.4 → apexdevkit-1.9.6}/apexdevkit/fastapi/resource.py +0 -0
- {apexdevkit-1.9.4 → apexdevkit-1.9.6}/apexdevkit/fastapi/response.py +0 -0
- {apexdevkit-1.9.4 → apexdevkit-1.9.6}/apexdevkit/fastapi/router.py +0 -0
- {apexdevkit-1.9.4 → apexdevkit-1.9.6}/apexdevkit/fastapi/service.py +0 -0
- {apexdevkit-1.9.4 → apexdevkit-1.9.6}/apexdevkit/formatter.py +0 -0
- {apexdevkit-1.9.4 → apexdevkit-1.9.6}/apexdevkit/http/__init__.py +0 -0
- {apexdevkit-1.9.4 → apexdevkit-1.9.6}/apexdevkit/http/fake.py +0 -0
- {apexdevkit-1.9.4 → apexdevkit-1.9.6}/apexdevkit/http/fluent.py +0 -0
- {apexdevkit-1.9.4 → apexdevkit-1.9.6}/apexdevkit/http/httpx.py +0 -0
- {apexdevkit-1.9.4 → apexdevkit-1.9.6}/apexdevkit/http/url.py +0 -0
- {apexdevkit-1.9.4 → apexdevkit-1.9.6}/apexdevkit/py.typed +0 -0
- {apexdevkit-1.9.4 → apexdevkit-1.9.6}/apexdevkit/repository/__init__.py +0 -0
- {apexdevkit-1.9.4 → apexdevkit-1.9.6}/apexdevkit/repository/base.py +0 -0
- {apexdevkit-1.9.4 → apexdevkit-1.9.6}/apexdevkit/repository/in_memory.py +0 -0
- {apexdevkit-1.9.4 → apexdevkit-1.9.6}/apexdevkit/repository/interface.py +0 -0
- {apexdevkit-1.9.4 → apexdevkit-1.9.6}/apexdevkit/repository/sqlite.py +0 -0
- {apexdevkit-1.9.4 → apexdevkit-1.9.6}/apexdevkit/testing/__init__.py +0 -0
- {apexdevkit-1.9.4 → apexdevkit-1.9.6}/apexdevkit/testing/database.py +0 -0
- {apexdevkit-1.9.4 → apexdevkit-1.9.6}/apexdevkit/testing/fake.py +0 -0
|
@@ -5,22 +5,22 @@ from typing import Any, Callable, Iterable, List
|
|
|
5
5
|
|
|
6
6
|
from pydantic import BaseModel, create_model
|
|
7
7
|
|
|
8
|
-
from apexdevkit.
|
|
8
|
+
from apexdevkit.fluent import FluentDict
|
|
9
9
|
from apexdevkit.testing import RestfulName
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
class SchemaFields(ABC):
|
|
13
|
-
def id(self) ->
|
|
13
|
+
def id(self) -> FluentDict[type]:
|
|
14
14
|
return self.readable().select("id")
|
|
15
15
|
|
|
16
|
-
def writable(self) ->
|
|
16
|
+
def writable(self) -> FluentDict[type]:
|
|
17
17
|
return self.readable().drop("id")
|
|
18
18
|
|
|
19
|
-
def editable(self) ->
|
|
19
|
+
def editable(self) -> FluentDict[type]:
|
|
20
20
|
return self.readable().drop("id")
|
|
21
21
|
|
|
22
22
|
@abstractmethod
|
|
23
|
-
def readable(self) ->
|
|
23
|
+
def readable(self) -> FluentDict[type]: # pragma: no cover
|
|
24
24
|
pass
|
|
25
25
|
|
|
26
26
|
|
|
@@ -38,26 +38,11 @@ class RestfulSchema:
|
|
|
38
38
|
"UpdateManyItem", self.fields.editable().merge(self.fields.id())
|
|
39
39
|
)
|
|
40
40
|
|
|
41
|
-
self._schema_for(
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
)
|
|
45
|
-
self._schema_for(
|
|
46
|
-
"Collection",
|
|
47
|
-
JsonDict({self.name.plural: List[schema]}).with_a(count=int),
|
|
48
|
-
)
|
|
49
|
-
|
|
50
|
-
self._schema_for(
|
|
51
|
-
"CreateMany",
|
|
52
|
-
JsonDict({self.name.plural: List[create_schema]}),
|
|
53
|
-
)
|
|
54
|
-
self._schema_for(
|
|
55
|
-
"UpdateMany",
|
|
56
|
-
JsonDict({self.name.plural: List[update_many_item]}),
|
|
57
|
-
)
|
|
58
|
-
self._schema_for(
|
|
59
|
-
"ReplaceMany", JsonDict({self.name.plural: List[replace_schema]})
|
|
60
|
-
)
|
|
41
|
+
self._schema_for("Item", {self.name.singular: schema})
|
|
42
|
+
self._schema_for("Collection", {self.name.plural: List[schema], "count": int})
|
|
43
|
+
self._schema_for("CreateMany", {self.name.plural: List[create_schema]})
|
|
44
|
+
self._schema_for("UpdateMany", {self.name.plural: List[update_many_item]})
|
|
45
|
+
self._schema_for("ReplaceMany", {self.name.plural: List[replace_schema]})
|
|
61
46
|
|
|
62
47
|
def _schema_for(self, action: str, fields: dict[str, Any]) -> type[BaseModel]:
|
|
63
48
|
if action not in self.schemas:
|
|
@@ -81,13 +66,13 @@ class RestfulSchema:
|
|
|
81
66
|
|
|
82
67
|
return self._schema_for(
|
|
83
68
|
"NoDataResponse",
|
|
84
|
-
|
|
69
|
+
FluentDict[type]().with_a(status=str).and_a(code=int).and_a(data=NoData),
|
|
85
70
|
)
|
|
86
71
|
|
|
87
72
|
def for_item(self) -> type[BaseModel]:
|
|
88
73
|
return self._schema_for(
|
|
89
74
|
"ItemResponse",
|
|
90
|
-
|
|
75
|
+
FluentDict[type]()
|
|
91
76
|
.with_a(status=str)
|
|
92
77
|
.and_a(code=int)
|
|
93
78
|
.and_a(data=self.schemas["Item"]),
|
|
@@ -96,7 +81,7 @@ class RestfulSchema:
|
|
|
96
81
|
def for_collection(self) -> type[BaseModel]:
|
|
97
82
|
return self._schema_for(
|
|
98
83
|
"CollectionResponse",
|
|
99
|
-
|
|
84
|
+
FluentDict[type]()
|
|
100
85
|
.with_a(status=str)
|
|
101
86
|
.and_a(code=int)
|
|
102
87
|
.and_a(data=self.schemas["Collection"]),
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Callable, Generic, TypeVar
|
|
5
|
+
|
|
6
|
+
ItemT = TypeVar("ItemT")
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class FluentDict(dict[str, ItemT]):
|
|
10
|
+
def value_of(self, key: str) -> FluentElement[ItemT]:
|
|
11
|
+
return FluentElement(self[key])
|
|
12
|
+
|
|
13
|
+
def and_a(self, **fields: ItemT) -> FluentDict[ItemT]:
|
|
14
|
+
return self.with_a(**fields)
|
|
15
|
+
|
|
16
|
+
def with_a(self, **fields: ItemT) -> FluentDict[ItemT]:
|
|
17
|
+
return self.merge(FluentDict[ItemT](fields))
|
|
18
|
+
|
|
19
|
+
def merge(self, other: FluentDict[ItemT]) -> FluentDict[ItemT]:
|
|
20
|
+
return FluentDict[ItemT]({**self, **other})
|
|
21
|
+
|
|
22
|
+
def drop(self, *keys: str) -> FluentDict[ItemT]:
|
|
23
|
+
return self.select(*set(self.keys()).difference(keys))
|
|
24
|
+
|
|
25
|
+
def select(self, *keys: str) -> FluentDict[ItemT]:
|
|
26
|
+
return FluentDict[ItemT]({k: v for k, v in self.items() if k in keys})
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass
|
|
30
|
+
class FluentElement(Generic[ItemT]):
|
|
31
|
+
value: ItemT
|
|
32
|
+
|
|
33
|
+
def to(self, a_type: Callable[[ItemT], ConvertedT]) -> ConvertedT:
|
|
34
|
+
return a_type(self.value)
|
|
35
|
+
|
|
36
|
+
def __str__(self) -> str:
|
|
37
|
+
return str(self.value)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
ConvertedT = TypeVar("ConvertedT")
|
|
@@ -35,10 +35,8 @@ class SqliteInMemoryConnector:
|
|
|
35
35
|
|
|
36
36
|
|
|
37
37
|
@dataclass
|
|
38
|
-
class
|
|
38
|
+
class PyMongoConnector:
|
|
39
39
|
dsn: str
|
|
40
40
|
|
|
41
41
|
def connect(self) -> ContextManager[MongoClient[Any]]:
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
return connection
|
|
42
|
+
return MongoClient(self.dsn)
|
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from dataclasses import dataclass, field
|
|
4
|
-
from typing import Any, ContextManager,
|
|
5
|
-
|
|
6
|
-
from pymongo import MongoClient
|
|
7
|
-
from pymongo.collection import Collection, ReturnDocument
|
|
8
|
-
from pymongo.results import DeleteResult, InsertOneResult
|
|
4
|
+
from typing import Any, ContextManager, Iterable, Protocol, Self
|
|
9
5
|
|
|
10
6
|
_RawData = dict[str, Any]
|
|
11
7
|
|
|
@@ -46,59 +42,11 @@ class Database:
|
|
|
46
42
|
return [dict(raw or {}) for raw in raw]
|
|
47
43
|
|
|
48
44
|
|
|
49
|
-
@dataclass
|
|
50
|
-
class MongoDatabase:
|
|
51
|
-
connector: MongoConnector
|
|
52
|
-
database_name: str
|
|
53
|
-
collection_name: str
|
|
54
|
-
|
|
55
|
-
def collection(self, client: MongoClient[Any]) -> Collection[Any]:
|
|
56
|
-
return client[self.database_name][self.collection_name]
|
|
57
|
-
|
|
58
|
-
def __iter__(self) -> Iterator[Any]:
|
|
59
|
-
with self.connector.connect() as client:
|
|
60
|
-
for raw in self.collection(client).find():
|
|
61
|
-
yield raw
|
|
62
|
-
|
|
63
|
-
def __len__(self) -> int:
|
|
64
|
-
with self.connector.connect() as client:
|
|
65
|
-
return self.collection(client).count_documents({})
|
|
66
|
-
|
|
67
|
-
def create(self, item: Dict[str, Any]) -> InsertOneResult:
|
|
68
|
-
with self.connector.connect() as client:
|
|
69
|
-
return self.collection(client).insert_one(item)
|
|
70
|
-
|
|
71
|
-
def read(self, item_id: str) -> Dict[str, Any] | None:
|
|
72
|
-
with self.connector.connect() as client:
|
|
73
|
-
return self.collection(client).find_one({"id": item_id})
|
|
74
|
-
|
|
75
|
-
def update(self, item_id: str, item: Dict[str, Any]) -> Any:
|
|
76
|
-
with self.connector.connect() as client:
|
|
77
|
-
return self.collection(client).find_one_and_update(
|
|
78
|
-
{"id": item_id},
|
|
79
|
-
{"$set": item},
|
|
80
|
-
return_document=ReturnDocument.AFTER,
|
|
81
|
-
)
|
|
82
|
-
|
|
83
|
-
def delete(self, item_id: str) -> DeleteResult:
|
|
84
|
-
with self.connector.connect() as client:
|
|
85
|
-
return self.collection(client).delete_one({"id": item_id})
|
|
86
|
-
|
|
87
|
-
def delete_all(self) -> DeleteResult:
|
|
88
|
-
with self.connector.connect() as client:
|
|
89
|
-
return self.collection(client).delete_many({})
|
|
90
|
-
|
|
91
|
-
|
|
92
45
|
class Connector(Protocol): # pragma: no cover
|
|
93
46
|
def connect(self) -> ContextManager[Connection]:
|
|
94
47
|
pass
|
|
95
48
|
|
|
96
49
|
|
|
97
|
-
class MongoConnector(Protocol): # pragma: no cover
|
|
98
|
-
def connect(self) -> ContextManager[MongoClient[Any]]:
|
|
99
|
-
pass
|
|
100
|
-
|
|
101
|
-
|
|
102
50
|
class Connection(Protocol): # pragma: no cover
|
|
103
51
|
def cursor(self) -> Cursor:
|
|
104
52
|
pass
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Any, ContextManager, Dict, Generic, Iterator, Protocol, TypeVar
|
|
5
|
+
|
|
6
|
+
from pymongo import MongoClient, ReturnDocument
|
|
7
|
+
from pymongo.collection import Collection
|
|
8
|
+
from pymongo.results import DeleteResult, InsertOneResult
|
|
9
|
+
|
|
10
|
+
from apexdevkit.error import DoesNotExistError, ExistsError
|
|
11
|
+
from apexdevkit.formatter import Formatter
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class _Item(Protocol): # pragma: no cover
|
|
15
|
+
@property
|
|
16
|
+
def id(self) -> Any:
|
|
17
|
+
pass
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
ItemT = TypeVar("ItemT", bound=_Item)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class MongoRepository(Generic[ItemT]):
|
|
25
|
+
database: MongoDatabase
|
|
26
|
+
formatter: Formatter[dict[str, Any], ItemT]
|
|
27
|
+
|
|
28
|
+
def __iter__(self) -> Iterator[ItemT]:
|
|
29
|
+
for raw in self.database:
|
|
30
|
+
yield self.formatter.load(raw)
|
|
31
|
+
|
|
32
|
+
def __len__(self) -> int:
|
|
33
|
+
return len(self.database)
|
|
34
|
+
|
|
35
|
+
def create(self, item: ItemT) -> ItemT:
|
|
36
|
+
try:
|
|
37
|
+
self.read(item.id)
|
|
38
|
+
raise ExistsError(item).with_duplicate(
|
|
39
|
+
lambda i: f"_Item with id<{i.id}> already exists."
|
|
40
|
+
)
|
|
41
|
+
except DoesNotExistError:
|
|
42
|
+
self.database.create(self.formatter.dump(item))
|
|
43
|
+
return item
|
|
44
|
+
|
|
45
|
+
def create_many(self, items: list[ItemT]) -> list[ItemT]:
|
|
46
|
+
return [self.create(item) for item in items]
|
|
47
|
+
|
|
48
|
+
def read(self, item_id: str) -> ItemT:
|
|
49
|
+
raw = self.database.read(item_id)
|
|
50
|
+
|
|
51
|
+
if not raw:
|
|
52
|
+
raise DoesNotExistError(item_id)
|
|
53
|
+
|
|
54
|
+
return self.formatter.load(raw)
|
|
55
|
+
|
|
56
|
+
def update(self, item: ItemT) -> None:
|
|
57
|
+
self.database.update(item.id, self.formatter.dump(item))
|
|
58
|
+
|
|
59
|
+
def update_many(self, items: list[ItemT]) -> None:
|
|
60
|
+
for item in items:
|
|
61
|
+
self.update(item)
|
|
62
|
+
|
|
63
|
+
def delete(self, item_id: str) -> None:
|
|
64
|
+
result = self.database.delete(item_id)
|
|
65
|
+
if result.deleted_count == 0:
|
|
66
|
+
raise DoesNotExistError(item_id)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@dataclass
|
|
70
|
+
class MongoDatabase:
|
|
71
|
+
connector: MongoConnector
|
|
72
|
+
database_name: str
|
|
73
|
+
collection_name: str
|
|
74
|
+
|
|
75
|
+
def collection(self, client: MongoClient[Any]) -> Collection[Any]:
|
|
76
|
+
return client[self.database_name][self.collection_name]
|
|
77
|
+
|
|
78
|
+
def __iter__(self) -> Iterator[Any]:
|
|
79
|
+
with self.connector.connect() as client:
|
|
80
|
+
for raw in self.collection(client).find():
|
|
81
|
+
yield raw
|
|
82
|
+
|
|
83
|
+
def __len__(self) -> int:
|
|
84
|
+
with self.connector.connect() as client:
|
|
85
|
+
return self.collection(client).count_documents({})
|
|
86
|
+
|
|
87
|
+
def create(self, item: Dict[str, Any]) -> InsertOneResult:
|
|
88
|
+
with self.connector.connect() as client:
|
|
89
|
+
return self.collection(client).insert_one(item)
|
|
90
|
+
|
|
91
|
+
def read(self, item_id: str) -> Dict[str, Any] | None:
|
|
92
|
+
with self.connector.connect() as client:
|
|
93
|
+
return self.collection(client).find_one({"id": item_id})
|
|
94
|
+
|
|
95
|
+
def update(self, item_id: str, item: Dict[str, Any]) -> Any:
|
|
96
|
+
with self.connector.connect() as client:
|
|
97
|
+
return self.collection(client).find_one_and_update(
|
|
98
|
+
{"id": item_id},
|
|
99
|
+
{"$set": item},
|
|
100
|
+
return_document=ReturnDocument.AFTER,
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
def delete(self, item_id: str) -> DeleteResult:
|
|
104
|
+
with self.connector.connect() as client:
|
|
105
|
+
return self.collection(client).delete_one({"id": item_id})
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class MongoConnector(Protocol): # pragma: no cover
|
|
109
|
+
def connect(self) -> ContextManager[MongoClient[Any]]:
|
|
110
|
+
pass
|
|
@@ -260,27 +260,13 @@ class RestResponse:
|
|
|
260
260
|
return self.with_item(value)
|
|
261
261
|
|
|
262
262
|
def with_item(self, value: Any) -> Self:
|
|
263
|
-
return self.with_data(
|
|
264
|
-
**{
|
|
265
|
-
self.resource.singular: dict(value)
|
|
266
|
-
if isinstance(value, JsonDict)
|
|
267
|
-
else value
|
|
268
|
-
}
|
|
269
|
-
)
|
|
263
|
+
return self.with_data(**{self.resource.singular: value})
|
|
270
264
|
|
|
271
265
|
def and_collection(self, value: list[Any]) -> Self:
|
|
272
266
|
return self.with_collection(value)
|
|
273
267
|
|
|
274
268
|
def with_collection(self, values: list[Any]) -> Self:
|
|
275
|
-
return self.with_data(
|
|
276
|
-
**{
|
|
277
|
-
self.resource.plural: [
|
|
278
|
-
dict(value) if isinstance(value, JsonDict) else value
|
|
279
|
-
for value in values
|
|
280
|
-
],
|
|
281
|
-
},
|
|
282
|
-
count=len(values),
|
|
283
|
-
)
|
|
269
|
+
return self.with_data(**{self.resource.plural: values}, count=len(values))
|
|
284
270
|
|
|
285
271
|
def and_no_data(self) -> Self:
|
|
286
272
|
return self.no_data()
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from dataclasses import dataclass
|
|
4
|
-
from typing import Any, Callable, TypeVar
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
class JsonDict(dict[str, Any]):
|
|
8
|
-
def value_of(self, key: str) -> FluentElement:
|
|
9
|
-
return FluentElement(self[key])
|
|
10
|
-
|
|
11
|
-
def and_a(self, **fields: Any) -> JsonDict:
|
|
12
|
-
return self.with_a(**fields)
|
|
13
|
-
|
|
14
|
-
def with_a(self, **fields: Any) -> JsonDict:
|
|
15
|
-
return self.merge(JsonDict(fields))
|
|
16
|
-
|
|
17
|
-
def merge(self, other: JsonDict) -> JsonDict:
|
|
18
|
-
return JsonDict({**self, **other})
|
|
19
|
-
|
|
20
|
-
def drop(self, *keys: str) -> JsonDict:
|
|
21
|
-
return self.select(*set(self.keys()).difference(keys))
|
|
22
|
-
|
|
23
|
-
def select(self, *keys: str) -> JsonDict:
|
|
24
|
-
return JsonDict({k: v for k, v in self.items() if k in keys})
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
@dataclass
|
|
28
|
-
class FluentElement:
|
|
29
|
-
value: Any
|
|
30
|
-
|
|
31
|
-
def to(self, a_type: Callable[[Any], ConvertedT]) -> ConvertedT:
|
|
32
|
-
return a_type(self.value)
|
|
33
|
-
|
|
34
|
-
def __str__(self) -> str:
|
|
35
|
-
return str(self.value)
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
ConvertedT = TypeVar("ConvertedT")
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from dataclasses import dataclass
|
|
4
|
-
from typing import Any, Dict, Generic, Iterator, Protocol, TypeVar
|
|
5
|
-
|
|
6
|
-
from apexdevkit.error import DoesNotExistError, ExistsError
|
|
7
|
-
from apexdevkit.repository.database import MongoDatabase
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class _Item(Protocol): # pragma: no cover
|
|
11
|
-
@property
|
|
12
|
-
def id(self) -> Any:
|
|
13
|
-
pass
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
ItemT = TypeVar("ItemT", bound=_Item)
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
@dataclass
|
|
20
|
-
class MongoDBRepository(Generic[ItemT]):
|
|
21
|
-
database: MongoDatabase
|
|
22
|
-
table: MongoTable[ItemT]
|
|
23
|
-
|
|
24
|
-
def __iter__(self) -> Iterator[ItemT]:
|
|
25
|
-
for raw in self.database:
|
|
26
|
-
yield self.table.load(raw)
|
|
27
|
-
|
|
28
|
-
def __len__(self) -> int:
|
|
29
|
-
return len(self.database)
|
|
30
|
-
|
|
31
|
-
def create(self, item: ItemT) -> ItemT:
|
|
32
|
-
try:
|
|
33
|
-
self.read(item.id)
|
|
34
|
-
raise ExistsError(item).with_duplicate(
|
|
35
|
-
lambda i: f"_Item with id<{i.id}> already exists."
|
|
36
|
-
)
|
|
37
|
-
except DoesNotExistError:
|
|
38
|
-
self.database.create(self.table.to_dict(item))
|
|
39
|
-
return item
|
|
40
|
-
|
|
41
|
-
def create_many(self, items: list[ItemT]) -> list[ItemT]:
|
|
42
|
-
return [self.create(item) for item in items]
|
|
43
|
-
|
|
44
|
-
def read(self, item_id: str) -> ItemT:
|
|
45
|
-
raw = self.database.read(item_id)
|
|
46
|
-
|
|
47
|
-
if not raw:
|
|
48
|
-
raise DoesNotExistError(item_id)
|
|
49
|
-
|
|
50
|
-
return self.table.load(raw)
|
|
51
|
-
|
|
52
|
-
def update(self, item: ItemT) -> None:
|
|
53
|
-
self.database.update(
|
|
54
|
-
self.table.get_id(item),
|
|
55
|
-
self.table.to_dict(item),
|
|
56
|
-
)
|
|
57
|
-
|
|
58
|
-
def update_many(self, items: list[ItemT]) -> None:
|
|
59
|
-
for item in items:
|
|
60
|
-
self.update(item)
|
|
61
|
-
|
|
62
|
-
def delete(self, item_id: str) -> None:
|
|
63
|
-
result = self.database.delete(item_id)
|
|
64
|
-
if result.deleted_count == 0:
|
|
65
|
-
raise DoesNotExistError(item_id)
|
|
66
|
-
|
|
67
|
-
def delete_all(self) -> None:
|
|
68
|
-
self.database.delete_all()
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
class MongoTable(Generic[ItemT]):
|
|
72
|
-
def to_dict(self, item: ItemT) -> Dict[str, Any]:
|
|
73
|
-
raise NotImplementedError("Not implemented")
|
|
74
|
-
|
|
75
|
-
def load(self, data: Dict[str, Any]) -> ItemT:
|
|
76
|
-
raise NotImplementedError("Not implemented")
|
|
77
|
-
|
|
78
|
-
def get_id(self, item: ItemT) -> str:
|
|
79
|
-
raise NotImplementedError("Not implemented")
|
|
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
|