apexdevkit 1.32.7__tar.gz → 1.33.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {apexdevkit-1.32.7 → apexdevkit-1.33.1}/PKG-INFO +1 -1
- {apexdevkit-1.32.7 → apexdevkit-1.33.1}/apexdevkit/repository/sql/connector.py +7 -6
- {apexdevkit-1.32.7 → apexdevkit-1.33.1}/apexdevkit/repository/sql/mssql.py +5 -3
- {apexdevkit-1.32.7 → apexdevkit-1.33.1}/apexdevkit/repository/sql/sqlite.py +5 -3
- {apexdevkit-1.32.7 → apexdevkit-1.33.1}/apexdevkit/sync/observer.py +4 -3
- {apexdevkit-1.32.7 → apexdevkit-1.33.1}/apexdevkit/sync/source.py +18 -7
- {apexdevkit-1.32.7 → apexdevkit-1.33.1}/apexdevkit/sync/sync.py +63 -10
- {apexdevkit-1.32.7 → apexdevkit-1.33.1}/pyproject.toml +1 -1
- {apexdevkit-1.32.7 → apexdevkit-1.33.1}/LICENSE +0 -0
- {apexdevkit-1.32.7 → apexdevkit-1.33.1}/README.md +0 -0
- {apexdevkit-1.32.7 → apexdevkit-1.33.1}/apexdevkit/__init__.py +0 -0
- {apexdevkit-1.32.7 → apexdevkit-1.33.1}/apexdevkit/annotation/__init__.py +0 -0
- {apexdevkit-1.32.7 → apexdevkit-1.33.1}/apexdevkit/annotation/deprecate.py +0 -0
- {apexdevkit-1.32.7 → apexdevkit-1.33.1}/apexdevkit/date.py +0 -0
- {apexdevkit-1.32.7 → apexdevkit-1.33.1}/apexdevkit/environment.py +0 -0
- {apexdevkit-1.32.7 → apexdevkit-1.33.1}/apexdevkit/error.py +0 -0
- {apexdevkit-1.32.7 → apexdevkit-1.33.1}/apexdevkit/fastapi/__init__.py +0 -0
- {apexdevkit-1.32.7 → apexdevkit-1.33.1}/apexdevkit/fastapi/builder.py +0 -0
- {apexdevkit-1.32.7 → apexdevkit-1.33.1}/apexdevkit/fastapi/dependable.py +0 -0
- {apexdevkit-1.32.7 → apexdevkit-1.33.1}/apexdevkit/fastapi/docs.py +0 -0
- {apexdevkit-1.32.7 → apexdevkit-1.33.1}/apexdevkit/fastapi/name.py +0 -0
- {apexdevkit-1.32.7 → apexdevkit-1.33.1}/apexdevkit/fastapi/resource.py +0 -0
- {apexdevkit-1.32.7 → apexdevkit-1.33.1}/apexdevkit/fastapi/response.py +0 -0
- {apexdevkit-1.32.7 → apexdevkit-1.33.1}/apexdevkit/fastapi/router.py +0 -0
- {apexdevkit-1.32.7 → apexdevkit-1.33.1}/apexdevkit/fastapi/schema.py +0 -0
- {apexdevkit-1.32.7 → apexdevkit-1.33.1}/apexdevkit/fastapi/service.py +0 -0
- {apexdevkit-1.32.7 → apexdevkit-1.33.1}/apexdevkit/fluent.py +0 -0
- {apexdevkit-1.32.7 → apexdevkit-1.33.1}/apexdevkit/formatter.py +0 -0
- {apexdevkit-1.32.7 → apexdevkit-1.33.1}/apexdevkit/http/__init__.py +0 -0
- {apexdevkit-1.32.7 → apexdevkit-1.33.1}/apexdevkit/http/fake.py +0 -0
- {apexdevkit-1.32.7 → apexdevkit-1.33.1}/apexdevkit/http/fluent.py +0 -0
- {apexdevkit-1.32.7 → apexdevkit-1.33.1}/apexdevkit/http/httpx/__init__.py +0 -0
- {apexdevkit-1.32.7 → apexdevkit-1.33.1}/apexdevkit/http/httpx/client.py +0 -0
- {apexdevkit-1.32.7 → apexdevkit-1.33.1}/apexdevkit/http/httpx/hooks.py +0 -0
- {apexdevkit-1.32.7 → apexdevkit-1.33.1}/apexdevkit/http/json.py +0 -0
- {apexdevkit-1.32.7 → apexdevkit-1.33.1}/apexdevkit/http/url.py +0 -0
- {apexdevkit-1.32.7 → apexdevkit-1.33.1}/apexdevkit/key_fn.py +0 -0
- {apexdevkit-1.32.7 → apexdevkit-1.33.1}/apexdevkit/py.typed +0 -0
- {apexdevkit-1.32.7 → apexdevkit-1.33.1}/apexdevkit/query/__init__.py +0 -0
- {apexdevkit-1.32.7 → apexdevkit-1.33.1}/apexdevkit/query/generator.py +0 -0
- {apexdevkit-1.32.7 → apexdevkit-1.33.1}/apexdevkit/query/query.py +0 -0
- {apexdevkit-1.32.7 → apexdevkit-1.33.1}/apexdevkit/repository/__init__.py +0 -0
- {apexdevkit-1.32.7 → apexdevkit-1.33.1}/apexdevkit/repository/core/__init__.py +0 -0
- {apexdevkit-1.32.7 → apexdevkit-1.33.1}/apexdevkit/repository/core/database.py +0 -0
- {apexdevkit-1.32.7 → apexdevkit-1.33.1}/apexdevkit/repository/core/decorator.py +0 -0
- {apexdevkit-1.32.7 → apexdevkit-1.33.1}/apexdevkit/repository/core/interface.py +0 -0
- {apexdevkit-1.32.7 → apexdevkit-1.33.1}/apexdevkit/repository/core/mixin.py +0 -0
- {apexdevkit-1.32.7 → apexdevkit-1.33.1}/apexdevkit/repository/core/multi.py +0 -0
- {apexdevkit-1.32.7 → apexdevkit-1.33.1}/apexdevkit/repository/in_memory/__init__.py +0 -0
- {apexdevkit-1.32.7 → apexdevkit-1.33.1}/apexdevkit/repository/in_memory/mixin.py +0 -0
- {apexdevkit-1.32.7 → apexdevkit-1.33.1}/apexdevkit/repository/in_memory/repository.py +0 -0
- {apexdevkit-1.32.7 → apexdevkit-1.33.1}/apexdevkit/repository/in_memory/store.py +0 -0
- {apexdevkit-1.32.7 → apexdevkit-1.33.1}/apexdevkit/repository/sql/__init__.py +0 -0
- {apexdevkit-1.32.7 → apexdevkit-1.33.1}/apexdevkit/repository/sql/field.py +0 -0
- {apexdevkit-1.32.7 → apexdevkit-1.33.1}/apexdevkit/security/__init__.py +0 -0
- {apexdevkit-1.32.7 → apexdevkit-1.33.1}/apexdevkit/security/core.py +0 -0
- {apexdevkit-1.32.7 → apexdevkit-1.33.1}/apexdevkit/security/hashing.py +0 -0
- {apexdevkit-1.32.7 → apexdevkit-1.33.1}/apexdevkit/server.py +0 -0
- {apexdevkit-1.32.7 → apexdevkit-1.33.1}/apexdevkit/sync/__init__.py +0 -0
- {apexdevkit-1.32.7 → apexdevkit-1.33.1}/apexdevkit/sync/target.py +0 -0
- {apexdevkit-1.32.7 → apexdevkit-1.33.1}/apexdevkit/testing/__init__.py +0 -0
- {apexdevkit-1.32.7 → apexdevkit-1.33.1}/apexdevkit/testing/database.py +0 -0
- {apexdevkit-1.32.7 → apexdevkit-1.33.1}/apexdevkit/testing/fake.py +0 -0
- {apexdevkit-1.32.7 → apexdevkit-1.33.1}/apexdevkit/testing/rest.py +0 -0
- {apexdevkit-1.32.7 → apexdevkit-1.33.1}/apexdevkit/value.py +0 -0
|
@@ -8,13 +8,14 @@ from typing import Any
|
|
|
8
8
|
|
|
9
9
|
import pymssql
|
|
10
10
|
|
|
11
|
+
from apexdevkit.environment import environment_variable
|
|
11
12
|
from apexdevkit.repository import Connection
|
|
12
13
|
from apexdevkit.repository.core.database import ConnectionContextManager
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
@dataclass(frozen=True)
|
|
16
17
|
class SqliteFileConnector:
|
|
17
|
-
dsn: str
|
|
18
|
+
dsn: str = environment_variable("DSN")
|
|
18
19
|
|
|
19
20
|
def connect(self) -> AbstractContextManager[Connection]:
|
|
20
21
|
connection = sqlite3.connect(self.dsn)
|
|
@@ -40,12 +41,12 @@ class SqliteInMemoryConnector:
|
|
|
40
41
|
|
|
41
42
|
@dataclass(frozen=True)
|
|
42
43
|
class MsSqlConnector:
|
|
43
|
-
db_host: str
|
|
44
|
-
db_user: str
|
|
45
|
-
db_password: str
|
|
46
|
-
db_name: str
|
|
44
|
+
db_host: str = environment_variable("DB_HOST")
|
|
45
|
+
db_user: str = environment_variable("DB_USER")
|
|
46
|
+
db_password: str = environment_variable("DB_PASSWORD")
|
|
47
|
+
db_name: str = environment_variable("DB_NAME")
|
|
48
|
+
db_port: str = environment_variable("DB_PORT", default="1433")
|
|
47
49
|
db_tds_version = "7.0"
|
|
48
|
-
db_port: str = "1433"
|
|
49
50
|
|
|
50
51
|
def connect(self) -> AbstractContextManager[Connection]:
|
|
51
52
|
return ConnectionContextManager(self._connection())
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from collections.abc import Iterable, Iterator, Mapping
|
|
4
|
-
from dataclasses import dataclass
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
5
|
from typing import Any, Generic
|
|
6
6
|
|
|
7
7
|
from pymssql.exceptions import DatabaseError, OperationalError
|
|
@@ -16,14 +16,16 @@ from apexdevkit.repository.core import (
|
|
|
16
16
|
Repository,
|
|
17
17
|
)
|
|
18
18
|
|
|
19
|
+
from .connector import MsSqlConnector
|
|
19
20
|
from .field import NotNone, SqlFieldManager, _SqlField
|
|
20
21
|
|
|
21
22
|
|
|
22
|
-
@dataclass
|
|
23
|
+
@dataclass(frozen=True, kw_only=True)
|
|
23
24
|
class MsSqlRepository(ContainsMixin[ItemT], Repository[ItemT]):
|
|
24
|
-
db: Database
|
|
25
25
|
table: SqlTable[ItemT]
|
|
26
26
|
|
|
27
|
+
db: Database = field(default_factory=lambda: Database(connector=MsSqlConnector()))
|
|
28
|
+
|
|
27
29
|
def __iter__(self) -> Iterator[ItemT]:
|
|
28
30
|
for raw in self.db.execute(self.table.select_all()).fetch_all():
|
|
29
31
|
yield self.table.load(raw)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from collections.abc import Iterable, Iterator, Mapping
|
|
4
|
-
from dataclasses import dataclass
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
5
|
from sqlite3 import IntegrityError
|
|
6
6
|
from typing import Any, Generic
|
|
7
7
|
|
|
@@ -15,14 +15,16 @@ from apexdevkit.repository.core import (
|
|
|
15
15
|
Repository,
|
|
16
16
|
)
|
|
17
17
|
|
|
18
|
+
from .connector import SqliteFileConnector
|
|
18
19
|
from .field import NotNone, SqlFieldManager, _SqlField
|
|
19
20
|
|
|
20
21
|
|
|
21
|
-
@dataclass(frozen=True)
|
|
22
|
+
@dataclass(frozen=True, kw_only=True)
|
|
22
23
|
class SqliteRepository(ContainsMixin[ItemT], Repository[ItemT]):
|
|
23
|
-
db: Database
|
|
24
24
|
table: SqlTable[ItemT]
|
|
25
25
|
|
|
26
|
+
db: Database = field(default_factory=lambda: Database(SqliteFileConnector()))
|
|
27
|
+
|
|
26
28
|
def __iter__(self) -> Iterator[ItemT]:
|
|
27
29
|
for raw in self.db.execute(self.table.select_all()).fetch_all():
|
|
28
30
|
yield self.table.load(raw)
|
|
@@ -68,22 +68,23 @@ class SyncObserver(Protocol): # pragma: no cover
|
|
|
68
68
|
|
|
69
69
|
@dataclass(frozen=True)
|
|
70
70
|
class DefaultLog:
|
|
71
|
+
name: str = "items"
|
|
71
72
|
echo: Callable[[str], None] = print
|
|
72
73
|
|
|
73
74
|
def before_prune(self, n_items: int) -> None:
|
|
74
|
-
self.echo(f"Total # of absent
|
|
75
|
+
self.echo(f"Total # of absent {self.name}: {n_items}")
|
|
75
76
|
|
|
76
77
|
def after_prune(self) -> None:
|
|
77
78
|
self.echo("Pruning completed")
|
|
78
79
|
|
|
79
80
|
def before_load(self, n_items: int) -> None:
|
|
80
|
-
self.echo(f"Total # of new
|
|
81
|
+
self.echo(f"Total # of new {self.name}: {n_items}")
|
|
81
82
|
|
|
82
83
|
def after_load(self) -> None:
|
|
83
84
|
self.echo("Loading completed")
|
|
84
85
|
|
|
85
86
|
def before_update(self, n_items: int) -> None:
|
|
86
|
-
self.echo(f"Total # of
|
|
87
|
+
self.echo(f"Total # of changed {self.name}: {n_items}")
|
|
87
88
|
|
|
88
89
|
def after_update(self) -> None:
|
|
89
90
|
self.echo("Updating completed")
|
|
@@ -2,7 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from collections.abc import Iterable
|
|
4
4
|
from dataclasses import dataclass, field
|
|
5
|
-
from typing import Generic, Protocol, TypeVar
|
|
5
|
+
from typing import Any, Generic, Protocol, TypeVar
|
|
6
6
|
|
|
7
7
|
from apexdevkit.key_fn import AttributeKey, KeyFn
|
|
8
8
|
|
|
@@ -109,17 +109,28 @@ class SourceDiscriminator(Generic[T]):
|
|
|
109
109
|
current: Iterable[T]
|
|
110
110
|
latest: Iterable[T]
|
|
111
111
|
|
|
112
|
-
key_fn: KeyFn =
|
|
112
|
+
key_fn: KeyFn | None = None
|
|
113
113
|
|
|
114
114
|
def absent(self) -> Iterable[T]:
|
|
115
|
-
index = {self.
|
|
115
|
+
index = {self.key_of(item) for item in self.latest}
|
|
116
116
|
|
|
117
|
-
return [item for item in self.current if self.
|
|
117
|
+
return [item for item in self.current if self.key_of(item) not in index]
|
|
118
118
|
|
|
119
119
|
def new(self) -> Iterable[T]:
|
|
120
|
-
index = {self.
|
|
120
|
+
index = {self.key_of(item) for item in self.current}
|
|
121
121
|
|
|
122
|
-
return [item for item in self.latest if self.
|
|
122
|
+
return [item for item in self.latest if self.key_of(item) not in index]
|
|
123
123
|
|
|
124
124
|
def updates(self) -> Iterable[T]:
|
|
125
|
-
|
|
125
|
+
index = {self.key_of(item) for item in self.current}
|
|
126
|
+
|
|
127
|
+
return [
|
|
128
|
+
item
|
|
129
|
+
for item in set(self.latest).difference(self.current)
|
|
130
|
+
if self.key_of(item) in index
|
|
131
|
+
]
|
|
132
|
+
|
|
133
|
+
def key_of(self, item: Any) -> str:
|
|
134
|
+
key_fn = self.key_fn or AttributeKey("id")
|
|
135
|
+
|
|
136
|
+
return key_fn(item)
|
|
@@ -4,8 +4,10 @@ from collections.abc import Callable, Collection, Iterable
|
|
|
4
4
|
from dataclasses import dataclass, field, replace
|
|
5
5
|
from typing import Any, Generic, Self, TypeVar
|
|
6
6
|
|
|
7
|
+
from apexdevkit.key_fn import AttributeKey, KeyFn
|
|
8
|
+
|
|
7
9
|
from .observer import DefaultLog, ObservableSync
|
|
8
|
-
from .source import EmptySource, Source, SourcePreSet
|
|
10
|
+
from .source import EmptySource, Source, SourceDiscriminator, SourcePreSet
|
|
9
11
|
from .target import IterableTarget, NoTarget, Target
|
|
10
12
|
|
|
11
13
|
T = TypeVar("T")
|
|
@@ -18,6 +20,12 @@ class Sync(ObservableSync, Generic[T]):
|
|
|
18
20
|
|
|
19
21
|
dry_run: bool = False
|
|
20
22
|
|
|
23
|
+
def purge(self) -> _Purge[T]:
|
|
24
|
+
return _Purge[T](sync=self)
|
|
25
|
+
|
|
26
|
+
def discriminate(self, latest: Iterable[T]) -> _Discriminate[T]:
|
|
27
|
+
return _Discriminate[T](sync=self, latest=latest)
|
|
28
|
+
|
|
21
29
|
def and_target(self, value: Target[T]) -> Sync[T]:
|
|
22
30
|
return self.with_target(value)
|
|
23
31
|
|
|
@@ -30,19 +38,12 @@ class Sync(ObservableSync, Generic[T]):
|
|
|
30
38
|
def with_source(self, value: Source[T]) -> Sync[T]:
|
|
31
39
|
return replace(self, source=value)
|
|
32
40
|
|
|
33
|
-
def with_log(self, using: Callable[[str], None]) -> Self:
|
|
34
|
-
return self.attach(DefaultLog(echo=using))
|
|
41
|
+
def with_log(self, using: Callable[[str], None], named: str = "items") -> Self:
|
|
42
|
+
return self.attach(DefaultLog(name=named, echo=using))
|
|
35
43
|
|
|
36
44
|
def dry(self, *, when: bool = True) -> Sync[T]:
|
|
37
45
|
return replace(self, dry_run=when)
|
|
38
46
|
|
|
39
|
-
def purge(self, target: IterableTarget[T]) -> None:
|
|
40
|
-
(
|
|
41
|
-
self.with_target(target)
|
|
42
|
-
.and_source(SourcePreSet[T](removals=target))
|
|
43
|
-
.run(load=False, update=False)
|
|
44
|
-
)
|
|
45
|
-
|
|
46
47
|
def run(self, prune: bool = True, load: bool = True, update: bool = True) -> None:
|
|
47
48
|
if prune:
|
|
48
49
|
self.prune()
|
|
@@ -81,6 +82,58 @@ class Sync(ObservableSync, Generic[T]):
|
|
|
81
82
|
)
|
|
82
83
|
|
|
83
84
|
|
|
85
|
+
@dataclass(frozen=True, kw_only=True)
|
|
86
|
+
class _Purge(Generic[T]):
|
|
87
|
+
sync: Sync[T]
|
|
88
|
+
|
|
89
|
+
def target(self, value: IterableTarget[T]) -> _Purge[T]:
|
|
90
|
+
return _Purge[T](
|
|
91
|
+
sync=replace(
|
|
92
|
+
self.sync,
|
|
93
|
+
target=value,
|
|
94
|
+
source=SourcePreSet[T](removals=list(value)),
|
|
95
|
+
)
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
def __call__(self) -> None:
|
|
99
|
+
self.run()
|
|
100
|
+
|
|
101
|
+
def run(self) -> None:
|
|
102
|
+
self.sync.prune()
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
@dataclass
|
|
106
|
+
class _Discriminate(Generic[T]):
|
|
107
|
+
sync: Sync[T]
|
|
108
|
+
latest: Iterable[T]
|
|
109
|
+
|
|
110
|
+
current: Iterable[T] = field(default_factory=list)
|
|
111
|
+
|
|
112
|
+
def against(self, target: IterableTarget[T]) -> _Discriminate[T]:
|
|
113
|
+
return self.target(target).and_current(list(target))
|
|
114
|
+
|
|
115
|
+
def target(self, value: Target[T]) -> _Discriminate[T]:
|
|
116
|
+
return replace(self, sync=self.sync.with_target(value))
|
|
117
|
+
|
|
118
|
+
def and_current(self, value: Iterable[T]) -> _Discriminate[T]:
|
|
119
|
+
return replace(self, current=value)
|
|
120
|
+
|
|
121
|
+
def default(self) -> Sync[T]:
|
|
122
|
+
return self.using_attribute(name="id")
|
|
123
|
+
|
|
124
|
+
def using_attribute(self, name: str) -> Sync[T]:
|
|
125
|
+
return self.using(key_fn=AttributeKey(name))
|
|
126
|
+
|
|
127
|
+
def using(self, key_fn: KeyFn) -> Sync[T]:
|
|
128
|
+
return self.sync.with_source(
|
|
129
|
+
SourceDiscriminator(
|
|
130
|
+
current=self.current,
|
|
131
|
+
latest=self.latest,
|
|
132
|
+
key_fn=key_fn,
|
|
133
|
+
)
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
|
|
84
137
|
@dataclass(frozen=True, kw_only=True)
|
|
85
138
|
class SyncProbe:
|
|
86
139
|
source: Collection[Any]
|
|
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
|
|
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
|