apexdevkit 1.29.1__tar.gz → 1.29.3__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.29.1 → apexdevkit-1.29.3}/PKG-INFO +1 -1
- {apexdevkit-1.29.1 → apexdevkit-1.29.3}/apexdevkit/fastapi/service.py +3 -5
- {apexdevkit-1.29.1 → apexdevkit-1.29.3}/apexdevkit/formatter.py +13 -13
- {apexdevkit-1.29.1 → apexdevkit-1.29.3}/apexdevkit/repository/__init__.py +9 -5
- {apexdevkit-1.29.1 → apexdevkit-1.29.3}/apexdevkit/repository/core/__init__.py +9 -3
- apexdevkit-1.29.3/apexdevkit/repository/core/decorator.py +63 -0
- apexdevkit-1.29.3/apexdevkit/repository/core/interface.py +42 -0
- apexdevkit-1.29.3/apexdevkit/repository/core/mixin.py +24 -0
- {apexdevkit-1.29.1 → apexdevkit-1.29.3}/apexdevkit/repository/core/multi.py +3 -2
- apexdevkit-1.29.3/apexdevkit/repository/in_memory/__init__.py +10 -0
- apexdevkit-1.29.3/apexdevkit/repository/in_memory/mixin.py +20 -0
- apexdevkit-1.29.3/apexdevkit/repository/in_memory/repository.py +69 -0
- {apexdevkit-1.29.1 → apexdevkit-1.29.3}/apexdevkit/repository/in_memory/store.py +2 -16
- {apexdevkit-1.29.1 → apexdevkit-1.29.3}/apexdevkit/repository/sql/mssql.py +10 -5
- {apexdevkit-1.29.1 → apexdevkit-1.29.3}/apexdevkit/repository/sql/sqlite.py +10 -4
- {apexdevkit-1.29.1 → apexdevkit-1.29.3}/pyproject.toml +1 -1
- apexdevkit-1.29.1/apexdevkit/repository/core/decorator.py +0 -37
- apexdevkit-1.29.1/apexdevkit/repository/core/interface.py +0 -55
- apexdevkit-1.29.1/apexdevkit/repository/in_memory/__init__.py +0 -8
- apexdevkit-1.29.1/apexdevkit/repository/in_memory/builder.py +0 -62
- apexdevkit-1.29.1/apexdevkit/repository/in_memory/multi_key.py +0 -53
- apexdevkit-1.29.1/apexdevkit/repository/in_memory/single_key.py +0 -51
- {apexdevkit-1.29.1 → apexdevkit-1.29.3}/LICENSE +0 -0
- {apexdevkit-1.29.1 → apexdevkit-1.29.3}/README.md +0 -0
- {apexdevkit-1.29.1 → apexdevkit-1.29.3}/apexdevkit/__init__.py +0 -0
- {apexdevkit-1.29.1 → apexdevkit-1.29.3}/apexdevkit/annotation/__init__.py +0 -0
- {apexdevkit-1.29.1 → apexdevkit-1.29.3}/apexdevkit/annotation/deprecate.py +0 -0
- {apexdevkit-1.29.1 → apexdevkit-1.29.3}/apexdevkit/date.py +0 -0
- {apexdevkit-1.29.1 → apexdevkit-1.29.3}/apexdevkit/environment.py +0 -0
- {apexdevkit-1.29.1 → apexdevkit-1.29.3}/apexdevkit/error.py +0 -0
- {apexdevkit-1.29.1 → apexdevkit-1.29.3}/apexdevkit/fastapi/__init__.py +0 -0
- {apexdevkit-1.29.1 → apexdevkit-1.29.3}/apexdevkit/fastapi/builder.py +0 -0
- {apexdevkit-1.29.1 → apexdevkit-1.29.3}/apexdevkit/fastapi/dependable.py +0 -0
- {apexdevkit-1.29.1 → apexdevkit-1.29.3}/apexdevkit/fastapi/docs.py +0 -0
- {apexdevkit-1.29.1 → apexdevkit-1.29.3}/apexdevkit/fastapi/name.py +0 -0
- {apexdevkit-1.29.1 → apexdevkit-1.29.3}/apexdevkit/fastapi/resource.py +0 -0
- {apexdevkit-1.29.1 → apexdevkit-1.29.3}/apexdevkit/fastapi/response.py +0 -0
- {apexdevkit-1.29.1 → apexdevkit-1.29.3}/apexdevkit/fastapi/router.py +0 -0
- {apexdevkit-1.29.1 → apexdevkit-1.29.3}/apexdevkit/fastapi/schema.py +0 -0
- {apexdevkit-1.29.1 → apexdevkit-1.29.3}/apexdevkit/fluent.py +0 -0
- {apexdevkit-1.29.1 → apexdevkit-1.29.3}/apexdevkit/http/__init__.py +0 -0
- {apexdevkit-1.29.1 → apexdevkit-1.29.3}/apexdevkit/http/fake.py +0 -0
- {apexdevkit-1.29.1 → apexdevkit-1.29.3}/apexdevkit/http/fluent.py +0 -0
- {apexdevkit-1.29.1 → apexdevkit-1.29.3}/apexdevkit/http/httpx/__init__.py +0 -0
- {apexdevkit-1.29.1 → apexdevkit-1.29.3}/apexdevkit/http/httpx/client.py +0 -0
- {apexdevkit-1.29.1 → apexdevkit-1.29.3}/apexdevkit/http/httpx/hooks.py +0 -0
- {apexdevkit-1.29.1 → apexdevkit-1.29.3}/apexdevkit/http/json.py +0 -0
- {apexdevkit-1.29.1 → apexdevkit-1.29.3}/apexdevkit/http/url.py +0 -0
- {apexdevkit-1.29.1 → apexdevkit-1.29.3}/apexdevkit/id.py +0 -0
- {apexdevkit-1.29.1 → apexdevkit-1.29.3}/apexdevkit/key_fn.py +0 -0
- {apexdevkit-1.29.1 → apexdevkit-1.29.3}/apexdevkit/py.typed +0 -0
- {apexdevkit-1.29.1 → apexdevkit-1.29.3}/apexdevkit/query/__init__.py +0 -0
- {apexdevkit-1.29.1 → apexdevkit-1.29.3}/apexdevkit/query/generator.py +0 -0
- {apexdevkit-1.29.1 → apexdevkit-1.29.3}/apexdevkit/query/query.py +0 -0
- {apexdevkit-1.29.1 → apexdevkit-1.29.3}/apexdevkit/repository/core/database.py +0 -0
- {apexdevkit-1.29.1 → apexdevkit-1.29.3}/apexdevkit/repository/sql/__init__.py +0 -0
- {apexdevkit-1.29.1 → apexdevkit-1.29.3}/apexdevkit/repository/sql/connector.py +0 -0
- {apexdevkit-1.29.1 → apexdevkit-1.29.3}/apexdevkit/repository/sql/field.py +0 -0
- {apexdevkit-1.29.1 → apexdevkit-1.29.3}/apexdevkit/server.py +0 -0
- {apexdevkit-1.29.1 → apexdevkit-1.29.3}/apexdevkit/synchronization.py +0 -0
- {apexdevkit-1.29.1 → apexdevkit-1.29.3}/apexdevkit/testing/__init__.py +0 -0
- {apexdevkit-1.29.1 → apexdevkit-1.29.3}/apexdevkit/testing/database.py +0 -0
- {apexdevkit-1.29.1 → apexdevkit-1.29.3}/apexdevkit/testing/fake.py +0 -0
- {apexdevkit-1.29.1 → apexdevkit-1.29.3}/apexdevkit/testing/rest.py +0 -0
- {apexdevkit-1.29.1 → apexdevkit-1.29.3}/apexdevkit/value.py +0 -0
|
@@ -3,10 +3,11 @@ 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, Generic
|
|
6
|
+
from typing import Any, Generic
|
|
7
7
|
|
|
8
8
|
from apexdevkit.formatter import Formatter
|
|
9
9
|
from apexdevkit.repository import BruteForceBatch, Repository
|
|
10
|
+
from apexdevkit.repository.core import ItemT
|
|
10
11
|
|
|
11
12
|
RawItem = Mapping[str, Any]
|
|
12
13
|
RawCollection = Iterable[RawItem]
|
|
@@ -58,9 +59,6 @@ class RestfulService: # pragma: no cover
|
|
|
58
59
|
raise NotImplementedError(self.delete_one.__name__)
|
|
59
60
|
|
|
60
61
|
|
|
61
|
-
ItemT = TypeVar("ItemT")
|
|
62
|
-
|
|
63
|
-
|
|
64
62
|
@dataclass(frozen=True)
|
|
65
63
|
class RestfulRepository(RestfulService, Generic[ItemT]):
|
|
66
64
|
repository: Repository[ItemT]
|
|
@@ -69,7 +67,7 @@ class RestfulRepository(RestfulService, Generic[ItemT]):
|
|
|
69
67
|
|
|
70
68
|
@cached_property
|
|
71
69
|
def _batch(self) -> BruteForceBatch[ItemT]:
|
|
72
|
-
return BruteForceBatch(self.repository)
|
|
70
|
+
return BruteForceBatch(inner=self.repository)
|
|
73
71
|
|
|
74
72
|
def create_one(self, item: RawItem) -> RawItem:
|
|
75
73
|
return self.formatter.dump(self.repository.create(self.formatter.load(item)))
|
|
@@ -85,8 +85,8 @@ class DataclassFormatter(Generic[_TargetT]):
|
|
|
85
85
|
|
|
86
86
|
return self
|
|
87
87
|
|
|
88
|
-
def load(self,
|
|
89
|
-
|
|
88
|
+
def load(self, source: Mapping[str, Any]) -> _TargetT:
|
|
89
|
+
source = FluentDict[Any](deepcopy(source)).select(
|
|
90
90
|
*self.resource.__annotations__.keys(),
|
|
91
91
|
"id",
|
|
92
92
|
"idempotency_id",
|
|
@@ -95,27 +95,27 @@ class DataclassFormatter(Generic[_TargetT]):
|
|
|
95
95
|
for key in fields(self.resource): # type: ignore
|
|
96
96
|
types = get_type_hints(self.resource)
|
|
97
97
|
key_type = types[key.name]
|
|
98
|
-
if key.name not in
|
|
98
|
+
if key.name not in source:
|
|
99
99
|
continue
|
|
100
100
|
if key.name in self.sub_formatters:
|
|
101
|
-
|
|
102
|
-
self.sub_formatters[key.name].load(
|
|
103
|
-
if
|
|
104
|
-
else
|
|
101
|
+
source[key.name] = (
|
|
102
|
+
self.sub_formatters[key.name].load(source.pop(key.name))
|
|
103
|
+
if source[key.name]
|
|
104
|
+
else source[key.name]
|
|
105
105
|
)
|
|
106
106
|
elif is_dataclass(key_type):
|
|
107
|
-
|
|
107
|
+
source[key.name] = DataclassFormatter(key_type).load(source[key.name]) # type: ignore
|
|
108
108
|
else:
|
|
109
109
|
args = get_args(key_type)
|
|
110
110
|
if len(args) == 1 and is_dataclass(args[0]):
|
|
111
|
-
|
|
112
|
-
|
|
111
|
+
source[key.name] = ListFormatter(DataclassFormatter(args[0])).load( # type: ignore
|
|
112
|
+
source[key.name]
|
|
113
113
|
)
|
|
114
114
|
|
|
115
|
-
return self.resource(**
|
|
115
|
+
return self.resource(**source)
|
|
116
116
|
|
|
117
|
-
def dump(self,
|
|
118
|
-
return asdict(
|
|
117
|
+
def dump(self, target: _TargetT) -> Mapping[str, Any]:
|
|
118
|
+
return asdict(target) # type: ignore
|
|
119
119
|
|
|
120
120
|
|
|
121
121
|
class ValueFormatter:
|
|
@@ -1,22 +1,25 @@
|
|
|
1
|
-
from
|
|
1
|
+
from .core import (
|
|
2
2
|
BruteForceBatch,
|
|
3
3
|
Connection,
|
|
4
4
|
Connector,
|
|
5
5
|
Cursor,
|
|
6
6
|
Database,
|
|
7
7
|
DatabaseCommand,
|
|
8
|
+
NoRepository,
|
|
8
9
|
Repository,
|
|
9
|
-
RepositoryBase,
|
|
10
10
|
RepositoryDecorator,
|
|
11
11
|
)
|
|
12
|
-
from
|
|
12
|
+
from .core.interface import Entity
|
|
13
|
+
from .in_memory import (
|
|
14
|
+
CacheMixin,
|
|
13
15
|
InMemoryByteStore,
|
|
14
16
|
InMemoryRepository,
|
|
15
17
|
KeyValueStore,
|
|
16
18
|
)
|
|
17
|
-
from
|
|
19
|
+
from .sql import MsSqlRepository, SqliteRepository
|
|
18
20
|
|
|
19
21
|
__all__ = [
|
|
22
|
+
"Entity",
|
|
20
23
|
# Core
|
|
21
24
|
"BruteForceBatch",
|
|
22
25
|
"Connection",
|
|
@@ -24,10 +27,11 @@ __all__ = [
|
|
|
24
27
|
"Cursor",
|
|
25
28
|
"Database",
|
|
26
29
|
"DatabaseCommand",
|
|
30
|
+
"NoRepository",
|
|
27
31
|
"Repository",
|
|
28
|
-
"RepositoryBase",
|
|
29
32
|
"RepositoryDecorator",
|
|
30
33
|
# In-memory
|
|
34
|
+
"CacheMixin",
|
|
31
35
|
"InMemoryByteStore",
|
|
32
36
|
"InMemoryRepository",
|
|
33
37
|
"KeyValueStore",
|
|
@@ -6,8 +6,9 @@ from .database import (
|
|
|
6
6
|
Database,
|
|
7
7
|
DatabaseCommand,
|
|
8
8
|
)
|
|
9
|
-
from .decorator import BruteForceBatch, RepositoryDecorator
|
|
10
|
-
from .interface import
|
|
9
|
+
from .decorator import BruteForceBatch, NoRepository, RepositoryDecorator
|
|
10
|
+
from .interface import Entity, ItemT, KeyFn, Repository
|
|
11
|
+
from .mixin import ContainsMixin
|
|
11
12
|
|
|
12
13
|
__all__ = [
|
|
13
14
|
# Database
|
|
@@ -21,6 +22,11 @@ __all__ = [
|
|
|
21
22
|
"RepositoryDecorator",
|
|
22
23
|
"BruteForceBatch",
|
|
23
24
|
# Interface
|
|
25
|
+
"Entity",
|
|
26
|
+
"ItemT",
|
|
27
|
+
"KeyFn",
|
|
24
28
|
"Repository",
|
|
25
|
-
"
|
|
29
|
+
"NoRepository",
|
|
30
|
+
# Mixin
|
|
31
|
+
"ContainsMixin",
|
|
26
32
|
]
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
from collections.abc import Iterable, Iterator
|
|
2
|
+
from dataclasses import dataclass, field
|
|
3
|
+
|
|
4
|
+
from .interface import ItemT, Repository
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class NoRepository(Repository[ItemT]): # pragma: no cover
|
|
8
|
+
def create(self, item: ItemT) -> ItemT:
|
|
9
|
+
raise NotImplementedError
|
|
10
|
+
|
|
11
|
+
def read(self, item_id: str) -> ItemT:
|
|
12
|
+
raise NotImplementedError
|
|
13
|
+
|
|
14
|
+
def update(self, item: ItemT) -> None:
|
|
15
|
+
raise NotImplementedError
|
|
16
|
+
|
|
17
|
+
def delete(self, item_id: str) -> None:
|
|
18
|
+
raise NotImplementedError
|
|
19
|
+
|
|
20
|
+
def __iter__(self) -> Iterator[ItemT]:
|
|
21
|
+
raise NotImplementedError
|
|
22
|
+
|
|
23
|
+
def __len__(self) -> int:
|
|
24
|
+
raise NotImplementedError
|
|
25
|
+
|
|
26
|
+
def __contains__(self, item: object) -> bool:
|
|
27
|
+
raise NotImplementedError
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass(frozen=True, kw_only=True)
|
|
31
|
+
class RepositoryDecorator(Repository[ItemT]): # pragma: no cover
|
|
32
|
+
inner: Repository[ItemT] = field(default_factory=NoRepository)
|
|
33
|
+
|
|
34
|
+
def create(self, item: ItemT) -> ItemT:
|
|
35
|
+
return self.inner.create(item)
|
|
36
|
+
|
|
37
|
+
def read(self, item_id: str) -> ItemT:
|
|
38
|
+
return self.inner.read(item_id)
|
|
39
|
+
|
|
40
|
+
def update(self, item: ItemT) -> None:
|
|
41
|
+
self.inner.update(item)
|
|
42
|
+
|
|
43
|
+
def delete(self, item_id: str) -> None:
|
|
44
|
+
self.inner.delete(item_id)
|
|
45
|
+
|
|
46
|
+
def __iter__(self) -> Iterator[ItemT]:
|
|
47
|
+
return iter(self.inner)
|
|
48
|
+
|
|
49
|
+
def __len__(self) -> int:
|
|
50
|
+
return len(self.inner)
|
|
51
|
+
|
|
52
|
+
def __contains__(self, item: object) -> bool:
|
|
53
|
+
return item in self.inner
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@dataclass(frozen=True, kw_only=True)
|
|
57
|
+
class BruteForceBatch(RepositoryDecorator[ItemT]):
|
|
58
|
+
def create_many(self, items: Iterable[ItemT]) -> Iterable[ItemT]:
|
|
59
|
+
return [self.inner.create(item) for item in items]
|
|
60
|
+
|
|
61
|
+
def update_many(self, items: Iterable[ItemT]) -> None:
|
|
62
|
+
for item in items:
|
|
63
|
+
self.inner.update(item)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Callable, Collection, Iterable
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
from typing import Protocol, TypeVar
|
|
6
|
+
from uuid import uuid4
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _uuid() -> str:
|
|
10
|
+
return str(uuid4())
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass(frozen=True, kw_only=True)
|
|
14
|
+
class Entity:
|
|
15
|
+
id: str = field(default_factory=_uuid)
|
|
16
|
+
idempotency_id: str | None = field(default=None)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
ItemT = TypeVar("ItemT", bound=Entity)
|
|
20
|
+
KeyFn = Callable[[ItemT], str]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class Repository(Collection[ItemT], Protocol[ItemT]): # pragma: no cover
|
|
24
|
+
def create(self, item: ItemT) -> ItemT:
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
def read(self, item_id: str) -> ItemT:
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
def update(self, item: ItemT) -> None:
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
def delete(self, item_id: str) -> None:
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class BatchRepository(Repository[ItemT], Protocol[ItemT]): # pragma: no cover
|
|
38
|
+
def create_many(self, items: Iterable[ItemT]) -> Iterable[ItemT]:
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
def update_many(self, items: Iterable[ItemT]) -> None:
|
|
42
|
+
pass
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from typing import Generic
|
|
2
|
+
|
|
3
|
+
from apexdevkit.error import DoesNotExistError
|
|
4
|
+
from apexdevkit.repository.core import Entity, ItemT
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ContainsMixin(Generic[ItemT]):
|
|
8
|
+
def __contains__(self, item: object) -> bool:
|
|
9
|
+
match item:
|
|
10
|
+
case Entity():
|
|
11
|
+
return self.contains_id(item.id)
|
|
12
|
+
case _:
|
|
13
|
+
return False
|
|
14
|
+
|
|
15
|
+
def contains_id(self, value: str) -> bool:
|
|
16
|
+
try:
|
|
17
|
+
self.read(value)
|
|
18
|
+
except DoesNotExistError:
|
|
19
|
+
return False
|
|
20
|
+
|
|
21
|
+
return True
|
|
22
|
+
|
|
23
|
+
def read(self, item_id: str) -> ItemT:
|
|
24
|
+
raise NotImplementedError
|
|
@@ -7,7 +7,8 @@ from typing import Generic
|
|
|
7
7
|
from apexdevkit.error import DoesNotExistError
|
|
8
8
|
from apexdevkit.formatter import Formatter
|
|
9
9
|
|
|
10
|
-
from .
|
|
10
|
+
from . import ContainsMixin
|
|
11
|
+
from .interface import ItemT, Repository
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
@dataclass(frozen=True)
|
|
@@ -20,7 +21,7 @@ class NoFormatter(Generic[ItemT]):
|
|
|
20
21
|
|
|
21
22
|
|
|
22
23
|
@dataclass(frozen=True)
|
|
23
|
-
class MultipleRepository(
|
|
24
|
+
class MultipleRepository(ContainsMixin[ItemT], Repository[ItemT]):
|
|
24
25
|
repositories: list[_InnerRepository[ItemT]]
|
|
25
26
|
|
|
26
27
|
def create(self, item: ItemT) -> ItemT:
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from collections import defaultdict
|
|
2
|
+
from collections.abc import MutableMapping
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from functools import cached_property
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from .store import InMemoryByteStore, KeyValueStore
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass(frozen=True)
|
|
11
|
+
class CacheMixin:
|
|
12
|
+
@cached_property
|
|
13
|
+
def _entries(self) -> MutableMapping[str, KeyValueStore[Any]]:
|
|
14
|
+
return defaultdict(InMemoryByteStore)
|
|
15
|
+
|
|
16
|
+
def store_for(self, name: str) -> KeyValueStore[Any]:
|
|
17
|
+
return self._entries[name]
|
|
18
|
+
|
|
19
|
+
def clear(self) -> None:
|
|
20
|
+
self._entries.clear()
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Iterator
|
|
4
|
+
from contextlib import suppress
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
|
|
7
|
+
from apexdevkit.error import DoesNotExistError, ExistsError
|
|
8
|
+
from apexdevkit.key_fn import AttributeKey
|
|
9
|
+
from apexdevkit.repository.core import ContainsMixin, ItemT, KeyFn, Repository
|
|
10
|
+
|
|
11
|
+
from .store import InMemoryByteStore, KeyValueStore
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass(frozen=True)
|
|
15
|
+
class InMemoryRepository(ContainsMixin[ItemT], Repository[ItemT]):
|
|
16
|
+
store: KeyValueStore[ItemT] = field(default_factory=InMemoryByteStore)
|
|
17
|
+
keys: list[KeyFn[ItemT]] = field(default_factory=lambda: [AttributeKey("id")])
|
|
18
|
+
|
|
19
|
+
def and_key(self, function: KeyFn[ItemT]) -> InMemoryRepository[ItemT]:
|
|
20
|
+
return self.with_key(function)
|
|
21
|
+
|
|
22
|
+
def with_key(self, function: KeyFn[ItemT]) -> InMemoryRepository[ItemT]:
|
|
23
|
+
self.keys.append(function)
|
|
24
|
+
|
|
25
|
+
return self
|
|
26
|
+
|
|
27
|
+
def and_seeded(self, *items: ItemT) -> InMemoryRepository[ItemT]:
|
|
28
|
+
return self.with_seeded(*items)
|
|
29
|
+
|
|
30
|
+
def with_seeded(self, *items: ItemT) -> InMemoryRepository[ItemT]:
|
|
31
|
+
for seed in items:
|
|
32
|
+
with suppress(ExistsError):
|
|
33
|
+
self.create(seed)
|
|
34
|
+
|
|
35
|
+
return self
|
|
36
|
+
|
|
37
|
+
def create(self, item: ItemT) -> ItemT:
|
|
38
|
+
self._ensure_does_not_exist(item)
|
|
39
|
+
self.store.set(item.id, item)
|
|
40
|
+
|
|
41
|
+
return item
|
|
42
|
+
|
|
43
|
+
def _ensure_does_not_exist(self, new: ItemT) -> None:
|
|
44
|
+
for existing in self:
|
|
45
|
+
for key in self.keys:
|
|
46
|
+
if key(new) == key(existing):
|
|
47
|
+
ExistsError(existing).with_duplicate(key).fire()
|
|
48
|
+
|
|
49
|
+
def update(self, item: ItemT) -> None:
|
|
50
|
+
self.delete(item.id)
|
|
51
|
+
self.create(item)
|
|
52
|
+
|
|
53
|
+
def delete(self, item_id: str) -> None:
|
|
54
|
+
item = self.read(item_id)
|
|
55
|
+
self.store.drop(item.id)
|
|
56
|
+
|
|
57
|
+
def read(self, item_id: str) -> ItemT:
|
|
58
|
+
for key in self.keys:
|
|
59
|
+
for item in self:
|
|
60
|
+
if key(item) == str(item_id):
|
|
61
|
+
return item
|
|
62
|
+
|
|
63
|
+
raise DoesNotExistError(item_id)
|
|
64
|
+
|
|
65
|
+
def __iter__(self) -> Iterator[ItemT]:
|
|
66
|
+
return iter(self.store.values())
|
|
67
|
+
|
|
68
|
+
def __len__(self) -> int:
|
|
69
|
+
return self.store.count()
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from collections import
|
|
4
|
-
from collections.abc import Iterable, MutableMapping
|
|
3
|
+
from collections.abc import Iterable
|
|
5
4
|
from dataclasses import dataclass, field
|
|
6
|
-
from typing import
|
|
5
|
+
from typing import Generic, Protocol
|
|
7
6
|
|
|
8
7
|
from apexdevkit.formatter import Formatter, PickleFormatter
|
|
9
8
|
from apexdevkit.repository.core.interface import ItemT
|
|
@@ -47,16 +46,3 @@ class InMemoryByteStore(Generic[ItemT]):
|
|
|
47
46
|
def values(self) -> Iterable[ItemT]:
|
|
48
47
|
for raw in self.items.values():
|
|
49
48
|
yield self.formatter.load(raw)
|
|
50
|
-
|
|
51
|
-
@dataclass
|
|
52
|
-
class Cache:
|
|
53
|
-
items: MutableMapping[str, InMemoryByteStore[Any]] = field(init=False)
|
|
54
|
-
|
|
55
|
-
def __post_init__(self) -> None:
|
|
56
|
-
self.items = defaultdict(InMemoryByteStore)
|
|
57
|
-
|
|
58
|
-
def store_for(self, name: str) -> InMemoryByteStore[Any]:
|
|
59
|
-
return self.items[name]
|
|
60
|
-
|
|
61
|
-
def clear(self) -> None:
|
|
62
|
-
self.items.clear()
|
|
@@ -2,20 +2,25 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from collections.abc import Iterable, Iterator, Mapping
|
|
4
4
|
from dataclasses import dataclass
|
|
5
|
-
from typing import Any, Generic
|
|
5
|
+
from typing import Any, Generic
|
|
6
6
|
|
|
7
7
|
from pymssql.exceptions import DatabaseError, OperationalError
|
|
8
8
|
|
|
9
9
|
from apexdevkit.error import DoesNotExistError, ExistsError
|
|
10
10
|
from apexdevkit.formatter import Formatter
|
|
11
|
-
from apexdevkit.repository import
|
|
12
|
-
|
|
11
|
+
from apexdevkit.repository.core import (
|
|
12
|
+
ContainsMixin,
|
|
13
|
+
Database,
|
|
14
|
+
DatabaseCommand,
|
|
15
|
+
ItemT,
|
|
16
|
+
Repository,
|
|
17
|
+
)
|
|
13
18
|
|
|
14
|
-
|
|
19
|
+
from .field import NotNone, SqlFieldManager, _SqlField
|
|
15
20
|
|
|
16
21
|
|
|
17
22
|
@dataclass
|
|
18
|
-
class MsSqlRepository(
|
|
23
|
+
class MsSqlRepository(ContainsMixin[ItemT], Repository[ItemT]):
|
|
19
24
|
db: Database
|
|
20
25
|
table: SqlTable[ItemT]
|
|
21
26
|
|
|
@@ -7,13 +7,19 @@ from typing import Any, Generic
|
|
|
7
7
|
|
|
8
8
|
from apexdevkit.error import DoesNotExistError, ExistsError
|
|
9
9
|
from apexdevkit.formatter import Formatter
|
|
10
|
-
from apexdevkit.repository import
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
from apexdevkit.repository.core import (
|
|
11
|
+
ContainsMixin,
|
|
12
|
+
Database,
|
|
13
|
+
DatabaseCommand,
|
|
14
|
+
ItemT,
|
|
15
|
+
Repository,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
from .field import NotNone, SqlFieldManager, _SqlField
|
|
13
19
|
|
|
14
20
|
|
|
15
21
|
@dataclass(frozen=True)
|
|
16
|
-
class SqliteRepository(
|
|
22
|
+
class SqliteRepository(ContainsMixin[ItemT], Repository[ItemT]):
|
|
17
23
|
db: Database
|
|
18
24
|
table: SqlTable[ItemT]
|
|
19
25
|
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
from collections.abc import Iterable, Iterator
|
|
2
|
-
from dataclasses import dataclass
|
|
3
|
-
from typing import Generic
|
|
4
|
-
|
|
5
|
-
from .interface import ItemT, Repository
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
@dataclass
|
|
9
|
-
class RepositoryDecorator(Generic[ItemT]): # pragma: no cover
|
|
10
|
-
inner: Repository[ItemT]
|
|
11
|
-
|
|
12
|
-
def create(self, item: ItemT) -> ItemT:
|
|
13
|
-
return self.inner.create(item)
|
|
14
|
-
|
|
15
|
-
def read(self, item_id: str) -> ItemT:
|
|
16
|
-
return self.inner.read(item_id)
|
|
17
|
-
|
|
18
|
-
def update(self, item: ItemT) -> None:
|
|
19
|
-
self.inner.update(item)
|
|
20
|
-
|
|
21
|
-
def delete(self, item_id: str) -> None:
|
|
22
|
-
self.inner.delete(item_id)
|
|
23
|
-
|
|
24
|
-
def __iter__(self) -> Iterator[ItemT]:
|
|
25
|
-
return self.inner.__iter__()
|
|
26
|
-
|
|
27
|
-
def __len__(self) -> int:
|
|
28
|
-
return self.inner.__len__()
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
class BruteForceBatch(RepositoryDecorator[ItemT]):
|
|
32
|
-
def create_many(self, items: Iterable[ItemT]) -> Iterable[ItemT]:
|
|
33
|
-
return [self.inner.create(item) for item in items]
|
|
34
|
-
|
|
35
|
-
def update_many(self, items: Iterable[ItemT]) -> None:
|
|
36
|
-
for item in items:
|
|
37
|
-
self.inner.update(item)
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from collections.abc import Callable, Iterable, Iterator
|
|
4
|
-
from typing import Generic, Protocol, TypeVar
|
|
5
|
-
|
|
6
|
-
ItemT = TypeVar("ItemT")
|
|
7
|
-
KeyFn = Callable[[ItemT], str]
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class Repository(Protocol[ItemT]): # pragma: no cover
|
|
11
|
-
def create(self, item: ItemT) -> ItemT:
|
|
12
|
-
pass
|
|
13
|
-
|
|
14
|
-
def read(self, item_id: str) -> ItemT:
|
|
15
|
-
pass
|
|
16
|
-
|
|
17
|
-
def update(self, item: ItemT) -> None:
|
|
18
|
-
pass
|
|
19
|
-
|
|
20
|
-
def delete(self, item_id: str) -> None:
|
|
21
|
-
pass
|
|
22
|
-
|
|
23
|
-
def __iter__(self) -> Iterator[ItemT]:
|
|
24
|
-
pass
|
|
25
|
-
|
|
26
|
-
def __len__(self) -> int:
|
|
27
|
-
pass
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
class BatchRepository(Repository[ItemT], Protocol[ItemT]): # pragma: no cover
|
|
31
|
-
def create_many(self, items: Iterable[ItemT]) -> Iterable[ItemT]:
|
|
32
|
-
pass
|
|
33
|
-
|
|
34
|
-
def update_many(self, items: Iterable[ItemT]) -> None:
|
|
35
|
-
pass
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
class RepositoryBase(Generic[ItemT]): # pragma: no cover
|
|
39
|
-
def create(self, item: ItemT) -> ItemT:
|
|
40
|
-
raise NotImplementedError
|
|
41
|
-
|
|
42
|
-
def read(self, item_id: str) -> ItemT:
|
|
43
|
-
raise NotImplementedError
|
|
44
|
-
|
|
45
|
-
def update(self, item: ItemT) -> None:
|
|
46
|
-
raise NotImplementedError
|
|
47
|
-
|
|
48
|
-
def delete(self, item_id: str) -> None:
|
|
49
|
-
raise NotImplementedError
|
|
50
|
-
|
|
51
|
-
def __iter__(self) -> Iterator[ItemT]:
|
|
52
|
-
raise NotImplementedError
|
|
53
|
-
|
|
54
|
-
def __len__(self) -> int:
|
|
55
|
-
raise NotImplementedError
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from contextlib import suppress
|
|
4
|
-
from dataclasses import dataclass, field
|
|
5
|
-
from typing import Generic
|
|
6
|
-
|
|
7
|
-
from apexdevkit.error import ExistsError
|
|
8
|
-
from apexdevkit.key_fn import AttributeKey
|
|
9
|
-
from apexdevkit.repository.core.interface import ItemT, KeyFn, Repository
|
|
10
|
-
|
|
11
|
-
from .multi_key import MultiKeyRepository
|
|
12
|
-
from .single_key import SingleKeyRepository
|
|
13
|
-
from .store import InMemoryByteStore, KeyValueStore
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
@dataclass(frozen=True)
|
|
17
|
-
class InMemoryRepository(Generic[ItemT]):
|
|
18
|
-
store: KeyValueStore[ItemT] = field(default_factory=lambda: InMemoryByteStore())
|
|
19
|
-
keys: list[KeyFn[ItemT]] = field(default_factory=list)
|
|
20
|
-
seeds: frozenset[ItemT] = field(default_factory=frozenset)
|
|
21
|
-
|
|
22
|
-
def with_store(self, value: KeyValueStore[ItemT]) -> InMemoryRepository[ItemT]:
|
|
23
|
-
return InMemoryRepository(store=value, keys=self.keys, seeds=self.seeds)
|
|
24
|
-
|
|
25
|
-
def and_key(self, function: KeyFn[ItemT]) -> InMemoryRepository[ItemT]:
|
|
26
|
-
return self.with_key(function)
|
|
27
|
-
|
|
28
|
-
def with_key(self, function: KeyFn[ItemT]) -> InMemoryRepository[ItemT]:
|
|
29
|
-
return InMemoryRepository(
|
|
30
|
-
store=self.store,
|
|
31
|
-
keys=[*self.keys, function],
|
|
32
|
-
seeds=self.seeds,
|
|
33
|
-
)
|
|
34
|
-
|
|
35
|
-
def and_seeded(self, *items: ItemT) -> InMemoryRepository[ItemT]:
|
|
36
|
-
return self.with_seeded(*items)
|
|
37
|
-
|
|
38
|
-
def with_seeded(self, *items: ItemT) -> InMemoryRepository[ItemT]:
|
|
39
|
-
return InMemoryRepository(
|
|
40
|
-
store=self.store,
|
|
41
|
-
keys=self.keys,
|
|
42
|
-
seeds=self.seeds.union(set(items)),
|
|
43
|
-
)
|
|
44
|
-
|
|
45
|
-
def build(self) -> Repository[ItemT]:
|
|
46
|
-
return self._seed(self._create())
|
|
47
|
-
|
|
48
|
-
def _seed(self, repository: Repository[ItemT]) -> Repository[ItemT]:
|
|
49
|
-
for seed in self.seeds:
|
|
50
|
-
with suppress(ExistsError):
|
|
51
|
-
repository.create(seed)
|
|
52
|
-
|
|
53
|
-
return repository
|
|
54
|
-
|
|
55
|
-
def _create(self) -> Repository[ItemT]:
|
|
56
|
-
match len(self.keys):
|
|
57
|
-
case 0:
|
|
58
|
-
return SingleKeyRepository(self.store, pk=AttributeKey("id"))
|
|
59
|
-
case 1:
|
|
60
|
-
return SingleKeyRepository(self.store, pk=self.keys[0])
|
|
61
|
-
case _:
|
|
62
|
-
return MultiKeyRepository(self.store, keys=self.keys)
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from collections.abc import Iterator
|
|
4
|
-
from dataclasses import dataclass, field
|
|
5
|
-
|
|
6
|
-
from apexdevkit.error import DoesNotExistError, ExistsError
|
|
7
|
-
from apexdevkit.repository.core.interface import ItemT, KeyFn, RepositoryBase
|
|
8
|
-
|
|
9
|
-
from .store import KeyValueStore
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
@dataclass
|
|
13
|
-
class MultiKeyRepository(RepositoryBase[ItemT]):
|
|
14
|
-
store: KeyValueStore[ItemT]
|
|
15
|
-
|
|
16
|
-
keys: list[KeyFn[ItemT]] = field(default_factory=list)
|
|
17
|
-
|
|
18
|
-
def create(self, item: ItemT) -> ItemT:
|
|
19
|
-
self._ensure_does_not_exist(item)
|
|
20
|
-
self.store.set(self._pk(item), item)
|
|
21
|
-
|
|
22
|
-
return item
|
|
23
|
-
|
|
24
|
-
def _ensure_does_not_exist(self, new: ItemT) -> None:
|
|
25
|
-
for existing in self:
|
|
26
|
-
for key in self.keys:
|
|
27
|
-
if key(new) == key(existing):
|
|
28
|
-
ExistsError(existing).with_duplicate(key).fire()
|
|
29
|
-
|
|
30
|
-
def _pk(self, item: ItemT) -> str:
|
|
31
|
-
return self.keys[0](item)
|
|
32
|
-
|
|
33
|
-
def update(self, item: ItemT) -> None:
|
|
34
|
-
self.delete(self._pk(item))
|
|
35
|
-
self.create(item)
|
|
36
|
-
|
|
37
|
-
def delete(self, item_id: str) -> None:
|
|
38
|
-
item = self.read(item_id)
|
|
39
|
-
self.store.drop(self._pk(item))
|
|
40
|
-
|
|
41
|
-
def read(self, item_id: str) -> ItemT:
|
|
42
|
-
for key in self.keys:
|
|
43
|
-
for item in self:
|
|
44
|
-
if key(item) == str(item_id):
|
|
45
|
-
return item
|
|
46
|
-
|
|
47
|
-
raise DoesNotExistError(item_id)
|
|
48
|
-
|
|
49
|
-
def __iter__(self) -> Iterator[ItemT]:
|
|
50
|
-
return iter(self.store.values())
|
|
51
|
-
|
|
52
|
-
def __len__(self) -> int:
|
|
53
|
-
return self.store.count()
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from collections.abc import Iterator
|
|
4
|
-
from dataclasses import dataclass
|
|
5
|
-
|
|
6
|
-
from apexdevkit.error import DoesNotExistError, ExistsError
|
|
7
|
-
from apexdevkit.repository.core.interface import ItemT, KeyFn, RepositoryBase
|
|
8
|
-
|
|
9
|
-
from .store import KeyValueStore
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
@dataclass
|
|
13
|
-
class SingleKeyRepository(RepositoryBase[ItemT]):
|
|
14
|
-
store: KeyValueStore[ItemT]
|
|
15
|
-
pk: KeyFn[ItemT]
|
|
16
|
-
|
|
17
|
-
def create(self, item: ItemT) -> ItemT:
|
|
18
|
-
self._ensure_does_not_exist(item)
|
|
19
|
-
self.store.set(self.pk(item), item)
|
|
20
|
-
|
|
21
|
-
return item
|
|
22
|
-
|
|
23
|
-
def _ensure_does_not_exist(self, new: ItemT) -> None:
|
|
24
|
-
try:
|
|
25
|
-
existing = self.store.get(self.pk(new))
|
|
26
|
-
except KeyError:
|
|
27
|
-
return
|
|
28
|
-
|
|
29
|
-
ExistsError(existing).with_duplicate(self.pk).fire()
|
|
30
|
-
|
|
31
|
-
def update(self, item: ItemT) -> None:
|
|
32
|
-
self.delete(self.pk(item))
|
|
33
|
-
self.create(item)
|
|
34
|
-
|
|
35
|
-
def delete(self, item_id: str) -> None:
|
|
36
|
-
try:
|
|
37
|
-
self.store.drop(item_id)
|
|
38
|
-
except KeyError as e:
|
|
39
|
-
raise DoesNotExistError(item_id) from e
|
|
40
|
-
|
|
41
|
-
def read(self, item_id: str) -> ItemT:
|
|
42
|
-
try:
|
|
43
|
-
return self.store.get(item_id)
|
|
44
|
-
except KeyError as e:
|
|
45
|
-
raise DoesNotExistError(item_id) from e
|
|
46
|
-
|
|
47
|
-
def __iter__(self) -> Iterator[ItemT]:
|
|
48
|
-
return iter(self.store.values())
|
|
49
|
-
|
|
50
|
-
def __len__(self) -> int:
|
|
51
|
-
return self.store.count()
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|