apexdevkit 1.14.1__tar.gz → 1.14.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.14.1 → apexdevkit-1.14.3}/PKG-INFO +1 -1
- {apexdevkit-1.14.1 → apexdevkit-1.14.3}/apexdevkit/fastapi/builder.py +4 -2
- {apexdevkit-1.14.1 → apexdevkit-1.14.3}/apexdevkit/fastapi/dependable.py +20 -22
- {apexdevkit-1.14.1 → apexdevkit-1.14.3}/apexdevkit/fastapi/resource.py +1 -1
- {apexdevkit-1.14.1 → apexdevkit-1.14.3}/apexdevkit/fastapi/response.py +1 -1
- {apexdevkit-1.14.1 → apexdevkit-1.14.3}/apexdevkit/fastapi/schema.py +1 -1
- {apexdevkit-1.14.1 → apexdevkit-1.14.3}/apexdevkit/fastapi/service.py +18 -14
- {apexdevkit-1.14.1 → apexdevkit-1.14.3}/apexdevkit/fluent.py +1 -1
- {apexdevkit-1.14.1 → apexdevkit-1.14.3}/apexdevkit/formatter.py +7 -6
- {apexdevkit-1.14.1 → apexdevkit-1.14.3}/apexdevkit/http/fake.py +1 -1
- {apexdevkit-1.14.1 → apexdevkit-1.14.3}/apexdevkit/http/httpx.py +1 -1
- {apexdevkit-1.14.1 → apexdevkit-1.14.3}/apexdevkit/key_fn.py +1 -1
- {apexdevkit-1.14.1 → apexdevkit-1.14.3}/apexdevkit/repository/__init__.py +2 -2
- {apexdevkit-1.14.1 → apexdevkit-1.14.3}/apexdevkit/repository/connector.py +3 -3
- apexdevkit-1.14.3/apexdevkit/repository/database.py +94 -0
- {apexdevkit-1.14.1 → apexdevkit-1.14.3}/apexdevkit/repository/in_memory.py +69 -33
- {apexdevkit-1.14.1 → apexdevkit-1.14.3}/apexdevkit/repository/mongo.py +1 -1
- {apexdevkit-1.14.1 → apexdevkit-1.14.3}/apexdevkit/repository/sqlite.py +1 -1
- {apexdevkit-1.14.1 → apexdevkit-1.14.3}/apexdevkit/testing/database.py +6 -6
- {apexdevkit-1.14.1 → apexdevkit-1.14.3}/apexdevkit/testing/fake.py +2 -2
- {apexdevkit-1.14.1 → apexdevkit-1.14.3}/apexdevkit/testing/rest.py +3 -3
- {apexdevkit-1.14.1 → apexdevkit-1.14.3}/pyproject.toml +1 -1
- apexdevkit-1.14.1/apexdevkit/repository/database.py +0 -90
- {apexdevkit-1.14.1 → apexdevkit-1.14.3}/LICENSE +0 -0
- {apexdevkit-1.14.1 → apexdevkit-1.14.3}/README.md +0 -0
- {apexdevkit-1.14.1 → apexdevkit-1.14.3}/apexdevkit/__init__.py +0 -0
- {apexdevkit-1.14.1 → apexdevkit-1.14.3}/apexdevkit/annotation/__init__.py +0 -0
- {apexdevkit-1.14.1 → apexdevkit-1.14.3}/apexdevkit/annotation/deprecate.py +0 -0
- {apexdevkit-1.14.1 → apexdevkit-1.14.3}/apexdevkit/environment.py +0 -0
- {apexdevkit-1.14.1 → apexdevkit-1.14.3}/apexdevkit/error.py +0 -0
- {apexdevkit-1.14.1 → apexdevkit-1.14.3}/apexdevkit/fastapi/__init__.py +0 -0
- {apexdevkit-1.14.1 → apexdevkit-1.14.3}/apexdevkit/fastapi/docs.py +0 -0
- {apexdevkit-1.14.1 → apexdevkit-1.14.3}/apexdevkit/fastapi/router.py +0 -0
- {apexdevkit-1.14.1 → apexdevkit-1.14.3}/apexdevkit/http/__init__.py +0 -0
- {apexdevkit-1.14.1 → apexdevkit-1.14.3}/apexdevkit/http/fluent.py +0 -0
- {apexdevkit-1.14.1 → apexdevkit-1.14.3}/apexdevkit/http/json.py +0 -0
- {apexdevkit-1.14.1 → apexdevkit-1.14.3}/apexdevkit/http/url.py +0 -0
- {apexdevkit-1.14.1 → apexdevkit-1.14.3}/apexdevkit/py.typed +0 -0
- {apexdevkit-1.14.1 → apexdevkit-1.14.3}/apexdevkit/repository/base.py +0 -0
- {apexdevkit-1.14.1 → apexdevkit-1.14.3}/apexdevkit/repository/decorator.py +0 -0
- {apexdevkit-1.14.1 → apexdevkit-1.14.3}/apexdevkit/repository/interface.py +0 -0
- {apexdevkit-1.14.1 → apexdevkit-1.14.3}/apexdevkit/testing/__init__.py +0 -0
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
from abc import ABC, abstractmethod
|
|
2
4
|
from dataclasses import dataclass, field
|
|
3
5
|
from typing import Any, Self
|
|
@@ -53,12 +55,12 @@ class RestfulServiceBuilder(ABC):
|
|
|
53
55
|
parent_id: str = field(init=False)
|
|
54
56
|
user: Any = field(init=False)
|
|
55
57
|
|
|
56
|
-
def with_user(self, user: Any) ->
|
|
58
|
+
def with_user(self, user: Any) -> RestfulServiceBuilder:
|
|
57
59
|
self.user = user
|
|
58
60
|
|
|
59
61
|
return self
|
|
60
62
|
|
|
61
|
-
def with_parent(self, identity: str) ->
|
|
63
|
+
def with_parent(self, identity: str) -> RestfulServiceBuilder:
|
|
62
64
|
self.parent_id = identity
|
|
63
65
|
|
|
64
66
|
return self
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
from dataclasses import dataclass
|
|
2
|
-
from typing import Annotated, Any, Callable, Protocol
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import Annotated, Any, Callable, Protocol
|
|
3
3
|
|
|
4
4
|
from fastapi import Depends, Path
|
|
5
5
|
from fastapi.requests import Request
|
|
@@ -23,7 +23,7 @@ class _Dependency(Protocol):
|
|
|
23
23
|
pass
|
|
24
24
|
|
|
25
25
|
|
|
26
|
-
@dataclass
|
|
26
|
+
@dataclass(frozen=True)
|
|
27
27
|
class ServiceDependency:
|
|
28
28
|
dependency: _Dependency
|
|
29
29
|
|
|
@@ -36,7 +36,7 @@ class ServiceDependency:
|
|
|
36
36
|
return Annotated[RestfulService, Depends(_)]
|
|
37
37
|
|
|
38
38
|
|
|
39
|
-
@dataclass
|
|
39
|
+
@dataclass(frozen=True)
|
|
40
40
|
class ParentDependency:
|
|
41
41
|
parent: RestfulName
|
|
42
42
|
dependency: _Dependency
|
|
@@ -56,7 +56,7 @@ class ParentDependency:
|
|
|
56
56
|
return Annotated[RestfulServiceBuilder, Depends(_)]
|
|
57
57
|
|
|
58
58
|
|
|
59
|
-
@dataclass
|
|
59
|
+
@dataclass(frozen=True)
|
|
60
60
|
class UserDependency:
|
|
61
61
|
extract_user: Callable[..., Any]
|
|
62
62
|
dependency: _Dependency
|
|
@@ -71,7 +71,7 @@ class UserDependency:
|
|
|
71
71
|
return Annotated[RestfulServiceBuilder, Depends(_)]
|
|
72
72
|
|
|
73
73
|
|
|
74
|
-
@dataclass
|
|
74
|
+
@dataclass(frozen=True)
|
|
75
75
|
class InfraDependency:
|
|
76
76
|
infra: RestfulServiceBuilder
|
|
77
77
|
|
|
@@ -82,26 +82,24 @@ class InfraDependency:
|
|
|
82
82
|
return Annotated[RestfulServiceBuilder, Depends(_)]
|
|
83
83
|
|
|
84
84
|
|
|
85
|
-
@dataclass
|
|
85
|
+
@dataclass(frozen=True)
|
|
86
86
|
class DependableBuilder:
|
|
87
|
-
dependency: _Dependency =
|
|
87
|
+
dependency: _Dependency | None = None
|
|
88
88
|
|
|
89
|
-
def from_infra(self, value: RestfulServiceBuilder) ->
|
|
90
|
-
|
|
89
|
+
def from_infra(self, value: RestfulServiceBuilder) -> "DependableBuilder":
|
|
90
|
+
return DependableBuilder(InfraDependency(value))
|
|
91
91
|
|
|
92
|
-
|
|
92
|
+
def with_parent(self, value: RestfulName) -> "DependableBuilder":
|
|
93
|
+
if self.dependency is None:
|
|
94
|
+
raise RuntimeError("RestfulServiceBuilder type not set")
|
|
95
|
+
return DependableBuilder(ParentDependency(value, self.dependency))
|
|
93
96
|
|
|
94
|
-
def
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
return dependable
|
|
99
|
-
|
|
100
|
-
def with_user(self, extract_user: Callable[..., Any]) -> Self:
|
|
101
|
-
dependable = DependableBuilder()
|
|
102
|
-
dependable.dependency = UserDependency(extract_user, self.dependency)
|
|
103
|
-
|
|
104
|
-
return dependable
|
|
97
|
+
def with_user(self, extract_user: Callable[..., Any]) -> "DependableBuilder":
|
|
98
|
+
if self.dependency is None:
|
|
99
|
+
raise RuntimeError("RestfulServiceBuilder type not set")
|
|
100
|
+
return DependableBuilder(UserDependency(extract_user, self.dependency))
|
|
105
101
|
|
|
106
102
|
def as_dependable(self) -> type[RestfulService]:
|
|
103
|
+
if self.dependency is None:
|
|
104
|
+
raise RuntimeError("RestfulServiceBuilder type not set")
|
|
107
105
|
return ServiceDependency(self.dependency).as_dependable()
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
from abc import ABC
|
|
2
|
-
from dataclasses import dataclass
|
|
3
|
-
from typing import Any, Dict, Generic, Iterable,
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from typing import Any, Dict, Generic, Iterable, TypeVar
|
|
4
6
|
|
|
5
7
|
from apexdevkit.formatter import Formatter
|
|
6
8
|
from apexdevkit.repository.decorator import BatchRepositoryDecorator
|
|
@@ -50,26 +52,28 @@ class RestfulService(ABC): # pragma: no cover
|
|
|
50
52
|
ItemT = TypeVar("ItemT")
|
|
51
53
|
|
|
52
54
|
|
|
53
|
-
@dataclass
|
|
55
|
+
@dataclass(frozen=True)
|
|
54
56
|
class RestfulRepositoryBuilder(Generic[ItemT]):
|
|
55
|
-
formatter: Formatter[dict[str, Any], ItemT] =
|
|
56
|
-
repository: Repository[ItemT] =
|
|
57
|
-
|
|
58
|
-
def with_formatter(self, formatter: Formatter[dict[str, Any], ItemT]) -> Self:
|
|
59
|
-
self.formatter = formatter
|
|
60
|
-
|
|
61
|
-
return self
|
|
57
|
+
formatter: Formatter[dict[str, Any], ItemT] | None = None
|
|
58
|
+
repository: Repository[ItemT] | None = None
|
|
62
59
|
|
|
63
|
-
def
|
|
64
|
-
self
|
|
60
|
+
def with_formatter(
|
|
61
|
+
self, formatter: Formatter[dict[str, Any], ItemT]
|
|
62
|
+
) -> RestfulRepositoryBuilder[ItemT]:
|
|
63
|
+
return RestfulRepositoryBuilder[ItemT](formatter, self.repository)
|
|
65
64
|
|
|
66
|
-
|
|
65
|
+
def with_repository(
|
|
66
|
+
self, repository: Repository[ItemT]
|
|
67
|
+
) -> RestfulRepositoryBuilder[ItemT]:
|
|
68
|
+
return RestfulRepositoryBuilder[ItemT](self.formatter, repository)
|
|
67
69
|
|
|
68
70
|
def build(self) -> RestfulService:
|
|
71
|
+
if self.formatter is None or self.repository is None:
|
|
72
|
+
raise RuntimeError("Formatter or repository not provided.")
|
|
69
73
|
return _RestfulRepository(self.formatter, self.repository)
|
|
70
74
|
|
|
71
75
|
|
|
72
|
-
@dataclass
|
|
76
|
+
@dataclass(frozen=True)
|
|
73
77
|
class _RestfulRepository(RestfulService, Generic[ItemT]):
|
|
74
78
|
formatter: Formatter[dict[str, Any], ItemT]
|
|
75
79
|
repository: Repository[ItemT]
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import pickle
|
|
1
2
|
from copy import deepcopy
|
|
2
3
|
from dataclasses import asdict, dataclass, field
|
|
3
4
|
from typing import Any, Generic, Protocol, Self, TypeVar
|
|
4
5
|
|
|
5
6
|
_SourceT = TypeVar("_SourceT")
|
|
6
7
|
_TargetT = TypeVar("_TargetT")
|
|
8
|
+
_ItemT = TypeVar("_ItemT")
|
|
7
9
|
|
|
8
10
|
|
|
9
11
|
class Formatter(Protocol[_SourceT, _TargetT]): # pragma: no cover
|
|
@@ -14,13 +16,12 @@ class Formatter(Protocol[_SourceT, _TargetT]): # pragma: no cover
|
|
|
14
16
|
pass
|
|
15
17
|
|
|
16
18
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
return source
|
|
19
|
+
class PickleFormatter(Generic[_ItemT]):
|
|
20
|
+
def dump(self, item: _ItemT) -> bytes:
|
|
21
|
+
return pickle.dumps(item)
|
|
21
22
|
|
|
22
|
-
def
|
|
23
|
-
return
|
|
23
|
+
def load(self, raw: bytes) -> _ItemT:
|
|
24
|
+
return pickle.loads(raw) # type: ignore
|
|
24
25
|
|
|
25
26
|
|
|
26
27
|
@dataclass
|
|
@@ -7,7 +7,7 @@ from apexdevkit.repository.database import (
|
|
|
7
7
|
DatabaseCommand,
|
|
8
8
|
)
|
|
9
9
|
from apexdevkit.repository.in_memory import (
|
|
10
|
-
|
|
10
|
+
InMemoryByteStore,
|
|
11
11
|
InMemoryRepository,
|
|
12
12
|
KeyValueStore,
|
|
13
13
|
)
|
|
@@ -20,7 +20,7 @@ __all__ = [
|
|
|
20
20
|
"Database",
|
|
21
21
|
"DatabaseCommand",
|
|
22
22
|
"InMemoryRepository",
|
|
23
|
-
"
|
|
23
|
+
"InMemoryByteStore",
|
|
24
24
|
"KeyValueStore",
|
|
25
25
|
"Repository",
|
|
26
26
|
"RepositoryBase",
|
|
@@ -8,7 +8,7 @@ from pymongo import MongoClient
|
|
|
8
8
|
from apexdevkit.repository import Connection
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
@dataclass
|
|
11
|
+
@dataclass(frozen=True)
|
|
12
12
|
class SqliteFileConnector:
|
|
13
13
|
dsn: str
|
|
14
14
|
|
|
@@ -19,7 +19,7 @@ class SqliteFileConnector:
|
|
|
19
19
|
return connection
|
|
20
20
|
|
|
21
21
|
|
|
22
|
-
@dataclass
|
|
22
|
+
@dataclass(frozen=True)
|
|
23
23
|
class SqliteInMemoryConnector:
|
|
24
24
|
dsn: str = ":memory:"
|
|
25
25
|
|
|
@@ -34,7 +34,7 @@ class SqliteInMemoryConnector:
|
|
|
34
34
|
return connection
|
|
35
35
|
|
|
36
36
|
|
|
37
|
-
@dataclass
|
|
37
|
+
@dataclass(frozen=True)
|
|
38
38
|
class PyMongoConnector:
|
|
39
39
|
dsn: str
|
|
40
40
|
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from copy import deepcopy
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
from typing import Any, ContextManager, Iterable, Protocol
|
|
6
|
+
|
|
7
|
+
_RawData = dict[str, Any]
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass(frozen=True)
|
|
11
|
+
class Database:
|
|
12
|
+
connector: Connector
|
|
13
|
+
|
|
14
|
+
def execute(self, command: DatabaseCommand) -> _CommandExecutor:
|
|
15
|
+
return Database._CommandExecutor(self.connector, command)
|
|
16
|
+
|
|
17
|
+
@dataclass(frozen=True)
|
|
18
|
+
class _CommandExecutor:
|
|
19
|
+
connector: Connector
|
|
20
|
+
command: DatabaseCommand
|
|
21
|
+
|
|
22
|
+
def fetch_none(self) -> None:
|
|
23
|
+
with self.connector.connect() as connection:
|
|
24
|
+
cursor: Cursor = connection.cursor()
|
|
25
|
+
cursor.execute(self.command.value, self.command.payload)
|
|
26
|
+
cursor.close()
|
|
27
|
+
|
|
28
|
+
def fetch_one(self) -> _RawData:
|
|
29
|
+
with self.connector.connect() as connection:
|
|
30
|
+
cursor: Cursor = connection.cursor()
|
|
31
|
+
cursor.execute(self.command.value, self.command.payload)
|
|
32
|
+
raw = cursor.fetchone()
|
|
33
|
+
cursor.close()
|
|
34
|
+
|
|
35
|
+
return dict(raw or {})
|
|
36
|
+
|
|
37
|
+
def fetch_all(self) -> Iterable[_RawData]:
|
|
38
|
+
with self.connector.connect() as connection:
|
|
39
|
+
cursor: Cursor = connection.cursor()
|
|
40
|
+
cursor.execute(self.command.value, self.command.payload)
|
|
41
|
+
raw = cursor.fetchall()
|
|
42
|
+
cursor.close()
|
|
43
|
+
|
|
44
|
+
return [dict(raw or {}) for raw in raw]
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class Connector(Protocol): # pragma: no cover
|
|
48
|
+
def connect(self) -> ContextManager[Connection]:
|
|
49
|
+
pass
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class Connection(Protocol): # pragma: no cover
|
|
53
|
+
def cursor(self) -> Cursor:
|
|
54
|
+
pass
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class Cursor(Protocol): # pragma: no cover
|
|
58
|
+
def execute(self, *args: Any, **kwargs: Any) -> Any:
|
|
59
|
+
pass
|
|
60
|
+
|
|
61
|
+
def executemany(self, *args: Any, **kwargs: Any) -> Any:
|
|
62
|
+
pass
|
|
63
|
+
|
|
64
|
+
def fetchone(self, *args: Any, **kwargs: Any) -> Any:
|
|
65
|
+
pass
|
|
66
|
+
|
|
67
|
+
def fetchall(self, *args: Any, **kwargs: Any) -> Any:
|
|
68
|
+
pass
|
|
69
|
+
|
|
70
|
+
def close(self) -> None:
|
|
71
|
+
pass
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@dataclass(frozen=True)
|
|
75
|
+
class DatabaseCommand:
|
|
76
|
+
value: str = field(default_factory=str)
|
|
77
|
+
payload: _RawData | list[_RawData] = field(default_factory=dict)
|
|
78
|
+
|
|
79
|
+
def with_data(
|
|
80
|
+
self, value: _RawData | None = None, **fields: Any
|
|
81
|
+
) -> DatabaseCommand:
|
|
82
|
+
assert isinstance(self.payload, dict)
|
|
83
|
+
|
|
84
|
+
payload = deepcopy(self.payload)
|
|
85
|
+
payload.update(value or {})
|
|
86
|
+
payload.update(fields)
|
|
87
|
+
|
|
88
|
+
return DatabaseCommand(self.value, payload)
|
|
89
|
+
|
|
90
|
+
def with_collection(self, value: list[_RawData]) -> DatabaseCommand:
|
|
91
|
+
return DatabaseCommand(self.value, value)
|
|
92
|
+
|
|
93
|
+
def __str__(self) -> str: # pragma: no cover
|
|
94
|
+
return self.value
|
|
@@ -1,54 +1,65 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from contextlib import suppress
|
|
3
4
|
from dataclasses import dataclass, field
|
|
4
5
|
from typing import Any, Callable, Generic, Iterable, Iterator, Protocol, Self
|
|
5
6
|
|
|
6
7
|
from apexdevkit.error import DoesNotExistError, ExistsError
|
|
7
|
-
from apexdevkit.formatter import Formatter,
|
|
8
|
+
from apexdevkit.formatter import Formatter, PickleFormatter
|
|
8
9
|
from apexdevkit.key_fn import AttributeKey
|
|
9
10
|
from apexdevkit.repository import RepositoryBase
|
|
10
11
|
from apexdevkit.repository.interface import ItemT, Repository
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
_Raw = dict[str, Any]
|
|
13
|
+
_KeyFunction = Callable[[ItemT], str]
|
|
14
14
|
|
|
15
15
|
|
|
16
|
-
@dataclass
|
|
16
|
+
@dataclass(frozen=True)
|
|
17
17
|
class InMemoryRepository(Generic[ItemT]):
|
|
18
|
-
store: KeyValueStore[ItemT] = field(default_factory=lambda:
|
|
19
|
-
keys: list[
|
|
20
|
-
seeds:
|
|
21
|
-
|
|
22
|
-
def
|
|
23
|
-
return
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
18
|
+
store: KeyValueStore[ItemT] = field(default_factory=lambda: InMemoryByteStore())
|
|
19
|
+
keys: list[_KeyFunction[ItemT]] = field(default_factory=list)
|
|
20
|
+
seeds: frozenset[ItemT] = field(default_factory=frozenset)
|
|
21
|
+
|
|
22
|
+
def with_namespace(self, value: str) -> InMemoryRepository[ItemT]:
|
|
23
|
+
return InMemoryRepository[ItemT](
|
|
24
|
+
store=StoreNamespace(value, self.store),
|
|
25
|
+
keys=self.keys,
|
|
26
|
+
seeds=self.seeds,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
def with_store(self, value: KeyValueStore[ItemT]) -> InMemoryRepository[ItemT]:
|
|
30
|
+
return InMemoryRepository[ItemT](
|
|
31
|
+
store=value,
|
|
32
|
+
keys=self.keys,
|
|
33
|
+
seeds=self.seeds,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
def and_key(self, function: _KeyFunction[ItemT]) -> InMemoryRepository[ItemT]:
|
|
31
37
|
return self.with_key(function)
|
|
32
38
|
|
|
33
|
-
def with_key(self, function:
|
|
34
|
-
|
|
39
|
+
def with_key(self, function: _KeyFunction[ItemT]) -> InMemoryRepository[ItemT]:
|
|
40
|
+
return InMemoryRepository[ItemT](
|
|
41
|
+
store=self.store,
|
|
42
|
+
keys=[*self.keys, function],
|
|
43
|
+
seeds=self.seeds,
|
|
44
|
+
)
|
|
35
45
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
def and_seeded(self, *items: ItemT) -> Self:
|
|
46
|
+
def and_seeded(self, *items: ItemT) -> InMemoryRepository[ItemT]:
|
|
39
47
|
return self.with_seeded(*items)
|
|
40
48
|
|
|
41
|
-
def with_seeded(self, *items: ItemT) ->
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
49
|
+
def with_seeded(self, *items: ItemT) -> InMemoryRepository[ItemT]:
|
|
50
|
+
return InMemoryRepository(
|
|
51
|
+
store=self.store,
|
|
52
|
+
keys=self.keys,
|
|
53
|
+
seeds=self.seeds.union(set(items)),
|
|
54
|
+
)
|
|
45
55
|
|
|
46
56
|
def build(self) -> Repository[ItemT]:
|
|
47
57
|
return self._seed(self._create())
|
|
48
58
|
|
|
49
59
|
def _seed(self, repository: Repository[ItemT]) -> Repository[ItemT]:
|
|
50
60
|
for seed in self.seeds:
|
|
51
|
-
|
|
61
|
+
with suppress(ExistsError):
|
|
62
|
+
repository.create(seed)
|
|
52
63
|
|
|
53
64
|
return repository
|
|
54
65
|
|
|
@@ -80,9 +91,10 @@ class KeyValueStore(Protocol[ItemT]): # pragma: no cover
|
|
|
80
91
|
|
|
81
92
|
|
|
82
93
|
@dataclass
|
|
83
|
-
class
|
|
84
|
-
formatter: Formatter[
|
|
85
|
-
|
|
94
|
+
class InMemoryByteStore(Generic[ItemT]):
|
|
95
|
+
formatter: Formatter[bytes, ItemT] = field(default_factory=PickleFormatter)
|
|
96
|
+
|
|
97
|
+
items: dict[str, bytes] = field(default_factory=dict)
|
|
86
98
|
|
|
87
99
|
def count(self) -> int:
|
|
88
100
|
return len(self.items)
|
|
@@ -101,10 +113,34 @@ class InMemoryKeyValueStore(Generic[ItemT]):
|
|
|
101
113
|
yield self.formatter.load(raw)
|
|
102
114
|
|
|
103
115
|
|
|
116
|
+
@dataclass
|
|
117
|
+
class StoreNamespace(Generic[ItemT]):
|
|
118
|
+
name: str
|
|
119
|
+
inner: KeyValueStore[ItemT]
|
|
120
|
+
|
|
121
|
+
def count(self) -> int:
|
|
122
|
+
return self.inner.count()
|
|
123
|
+
|
|
124
|
+
def values(self) -> Iterable[ItemT]:
|
|
125
|
+
return self.inner.values()
|
|
126
|
+
|
|
127
|
+
def set(self, key: str, value: ItemT) -> None:
|
|
128
|
+
self.inner.set(self._expand(key), value)
|
|
129
|
+
|
|
130
|
+
def get(self, key: str) -> ItemT:
|
|
131
|
+
return self.inner.get(self._expand(key))
|
|
132
|
+
|
|
133
|
+
def drop(self, key: str) -> None:
|
|
134
|
+
self.inner.drop(self._expand(key))
|
|
135
|
+
|
|
136
|
+
def _expand(self, key: str) -> str:
|
|
137
|
+
return "-".join([self.name, key])
|
|
138
|
+
|
|
139
|
+
|
|
104
140
|
@dataclass
|
|
105
141
|
class _SingleKeyRepository(RepositoryBase[ItemT]):
|
|
106
142
|
store: KeyValueStore[ItemT]
|
|
107
|
-
pk:
|
|
143
|
+
pk: _KeyFunction[ItemT]
|
|
108
144
|
|
|
109
145
|
def bind(self, **kwargs: Any) -> Self: # pragma: no cover
|
|
110
146
|
return self
|
|
@@ -150,7 +186,7 @@ class _SingleKeyRepository(RepositoryBase[ItemT]):
|
|
|
150
186
|
class _ManyKeyRepository(RepositoryBase[ItemT]):
|
|
151
187
|
store: KeyValueStore[ItemT]
|
|
152
188
|
|
|
153
|
-
keys: list[
|
|
189
|
+
keys: list[_KeyFunction[ItemT]] = field(default_factory=list)
|
|
154
190
|
|
|
155
191
|
def bind(self, **kwargs: Any) -> Self: # pragma: no cover
|
|
156
192
|
return self
|
|
@@ -171,7 +207,7 @@ class _ManyKeyRepository(RepositoryBase[ItemT]):
|
|
|
171
207
|
|
|
172
208
|
error.fire()
|
|
173
209
|
|
|
174
|
-
def _pk(self, item: ItemT) ->
|
|
210
|
+
def _pk(self, item: ItemT) -> str:
|
|
175
211
|
return self.keys[0](item)
|
|
176
212
|
|
|
177
213
|
def update(self, item: ItemT) -> None:
|
|
@@ -9,7 +9,7 @@ from apexdevkit.repository import Database, DatabaseCommand, RepositoryBase
|
|
|
9
9
|
from apexdevkit.repository.interface import ItemT
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
@dataclass
|
|
12
|
+
@dataclass(frozen=True)
|
|
13
13
|
class SqliteRepository(RepositoryBase[ItemT]):
|
|
14
14
|
db: Database
|
|
15
15
|
table: SqlTable[ItemT]
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
from contextlib import nullcontext
|
|
2
4
|
from dataclasses import dataclass, field
|
|
3
5
|
from typing import Any, ContextManager, Self
|
|
@@ -5,15 +7,13 @@ from typing import Any, ContextManager, Self
|
|
|
5
7
|
from apexdevkit.repository import DatabaseCommand
|
|
6
8
|
|
|
7
9
|
|
|
8
|
-
@dataclass
|
|
10
|
+
@dataclass(frozen=True)
|
|
9
11
|
class FakeConnector:
|
|
10
12
|
commands: list[tuple[str, Any]] = field(default_factory=list)
|
|
11
|
-
results: list[Any] = field(
|
|
12
|
-
|
|
13
|
-
def with_result(self, values: Any) -> Self:
|
|
14
|
-
self.results = [values, *self.results]
|
|
13
|
+
results: list[Any] = field(default_factory=list)
|
|
15
14
|
|
|
16
|
-
|
|
15
|
+
def with_result(self, values: Any) -> FakeConnector:
|
|
16
|
+
return FakeConnector(self.commands, [values, *self.results])
|
|
17
17
|
|
|
18
18
|
def execute(self, command: str, data: Any) -> None:
|
|
19
19
|
self.commands.append((command, data))
|
|
@@ -9,7 +9,7 @@ from apexdevkit.http import JsonDict
|
|
|
9
9
|
ItemT = TypeVar("ItemT")
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
@dataclass
|
|
12
|
+
@dataclass(frozen=True)
|
|
13
13
|
class Fake:
|
|
14
14
|
faker: Faker = field(default_factory=Faker)
|
|
15
15
|
|
|
@@ -50,7 +50,7 @@ class Fake:
|
|
|
50
50
|
return bool(self.faker.boolean())
|
|
51
51
|
|
|
52
52
|
|
|
53
|
-
@dataclass
|
|
53
|
+
@dataclass(frozen=True)
|
|
54
54
|
class FakeResource(Generic[ItemT]):
|
|
55
55
|
item_type: Type[ItemT] = field()
|
|
56
56
|
fake: Fake = field(default_factory=Fake)
|
|
@@ -8,7 +8,7 @@ from apexdevkit.http import Http, HttpUrl, JsonDict
|
|
|
8
8
|
from apexdevkit.http.fluent import HttpMethod, HttpResponse
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
@dataclass
|
|
11
|
+
@dataclass(frozen=True)
|
|
12
12
|
class RestResource:
|
|
13
13
|
http: Http
|
|
14
14
|
name: RestfulName
|
|
@@ -95,13 +95,13 @@ class RestResource:
|
|
|
95
95
|
)
|
|
96
96
|
|
|
97
97
|
|
|
98
|
-
@dataclass
|
|
98
|
+
@dataclass(frozen=True)
|
|
99
99
|
class RestCollection(RestResource):
|
|
100
100
|
def sub_resource(self, name: str) -> RestItem:
|
|
101
101
|
return RestItem(self.http.with_endpoint(self.name.plural), RestfulName(name))
|
|
102
102
|
|
|
103
103
|
|
|
104
|
-
@dataclass
|
|
104
|
+
@dataclass(frozen=True)
|
|
105
105
|
class RestItem(RestResource):
|
|
106
106
|
def sub_resource(self, name: str) -> RestItem:
|
|
107
107
|
return RestItem(self.http.with_endpoint(self.name.singular), RestfulName(name))
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from dataclasses import dataclass, field
|
|
4
|
-
from typing import Any, ContextManager, Iterable, Protocol, Self
|
|
5
|
-
|
|
6
|
-
_RawData = dict[str, Any]
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
@dataclass
|
|
10
|
-
class Database:
|
|
11
|
-
connector: Connector
|
|
12
|
-
|
|
13
|
-
command: DatabaseCommand = field(init=False)
|
|
14
|
-
|
|
15
|
-
def execute(self, command: DatabaseCommand) -> Self:
|
|
16
|
-
self.command = command
|
|
17
|
-
|
|
18
|
-
return self
|
|
19
|
-
|
|
20
|
-
def fetch_none(self) -> None:
|
|
21
|
-
with self.connector.connect() as connection:
|
|
22
|
-
cursor: Cursor = connection.cursor()
|
|
23
|
-
cursor.execute(self.command.value, self.command.payload)
|
|
24
|
-
cursor.close()
|
|
25
|
-
|
|
26
|
-
def fetch_one(self) -> _RawData:
|
|
27
|
-
with self.connector.connect() as connection:
|
|
28
|
-
cursor: Cursor = connection.cursor()
|
|
29
|
-
cursor.execute(self.command.value, self.command.payload)
|
|
30
|
-
raw = cursor.fetchone()
|
|
31
|
-
cursor.close()
|
|
32
|
-
|
|
33
|
-
return dict(raw or {})
|
|
34
|
-
|
|
35
|
-
def fetch_all(self) -> Iterable[_RawData]:
|
|
36
|
-
with self.connector.connect() as connection:
|
|
37
|
-
cursor: Cursor = connection.cursor()
|
|
38
|
-
cursor.execute(self.command.value, self.command.payload)
|
|
39
|
-
raw = cursor.fetchall()
|
|
40
|
-
cursor.close()
|
|
41
|
-
|
|
42
|
-
return [dict(raw or {}) for raw in raw]
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
class Connector(Protocol): # pragma: no cover
|
|
46
|
-
def connect(self) -> ContextManager[Connection]:
|
|
47
|
-
pass
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
class Connection(Protocol): # pragma: no cover
|
|
51
|
-
def cursor(self) -> Cursor:
|
|
52
|
-
pass
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
class Cursor(Protocol): # pragma: no cover
|
|
56
|
-
def execute(self, *args: Any, **kwargs: Any) -> Any:
|
|
57
|
-
pass
|
|
58
|
-
|
|
59
|
-
def executemany(self, *args: Any, **kwargs: Any) -> Any:
|
|
60
|
-
pass
|
|
61
|
-
|
|
62
|
-
def fetchone(self, *args: Any, **kwargs: Any) -> Any:
|
|
63
|
-
pass
|
|
64
|
-
|
|
65
|
-
def fetchall(self, *args: Any, **kwargs: Any) -> Any:
|
|
66
|
-
pass
|
|
67
|
-
|
|
68
|
-
def close(self) -> None:
|
|
69
|
-
pass
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
@dataclass
|
|
73
|
-
class DatabaseCommand:
|
|
74
|
-
value: str
|
|
75
|
-
payload: _RawData | list[_RawData] = field(init=False, default_factory=dict)
|
|
76
|
-
|
|
77
|
-
def with_data(self, value: _RawData | None = None, **fields: Any) -> Self:
|
|
78
|
-
assert isinstance(self.payload, dict)
|
|
79
|
-
self.payload.update(value or {})
|
|
80
|
-
self.payload.update(fields)
|
|
81
|
-
|
|
82
|
-
return self
|
|
83
|
-
|
|
84
|
-
def with_collection(self, value: list[_RawData]) -> Self:
|
|
85
|
-
self.payload = value
|
|
86
|
-
|
|
87
|
-
return self
|
|
88
|
-
|
|
89
|
-
def __str__(self) -> str: # pragma: no cover
|
|
90
|
-
return self.value
|
|
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
|