apexdevkit 1.31.1__tar.gz → 1.32.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.
Files changed (65) hide show
  1. {apexdevkit-1.31.1 → apexdevkit-1.32.1}/PKG-INFO +1 -1
  2. {apexdevkit-1.31.1 → apexdevkit-1.32.1}/apexdevkit/key_fn.py +3 -0
  3. apexdevkit-1.32.1/apexdevkit/sync/__init__.py +19 -0
  4. apexdevkit-1.32.1/apexdevkit/sync/observer.py +89 -0
  5. apexdevkit-1.32.1/apexdevkit/sync/source.py +122 -0
  6. apexdevkit-1.32.1/apexdevkit/sync/sync.py +78 -0
  7. apexdevkit-1.32.1/apexdevkit/sync/target.py +82 -0
  8. {apexdevkit-1.31.1 → apexdevkit-1.32.1}/pyproject.toml +1 -1
  9. {apexdevkit-1.31.1 → apexdevkit-1.32.1}/LICENSE +0 -0
  10. {apexdevkit-1.31.1 → apexdevkit-1.32.1}/README.md +0 -0
  11. {apexdevkit-1.31.1 → apexdevkit-1.32.1}/apexdevkit/__init__.py +0 -0
  12. {apexdevkit-1.31.1 → apexdevkit-1.32.1}/apexdevkit/annotation/__init__.py +0 -0
  13. {apexdevkit-1.31.1 → apexdevkit-1.32.1}/apexdevkit/annotation/deprecate.py +0 -0
  14. {apexdevkit-1.31.1 → apexdevkit-1.32.1}/apexdevkit/date.py +0 -0
  15. {apexdevkit-1.31.1 → apexdevkit-1.32.1}/apexdevkit/environment.py +0 -0
  16. {apexdevkit-1.31.1 → apexdevkit-1.32.1}/apexdevkit/error.py +0 -0
  17. {apexdevkit-1.31.1 → apexdevkit-1.32.1}/apexdevkit/fastapi/__init__.py +0 -0
  18. {apexdevkit-1.31.1 → apexdevkit-1.32.1}/apexdevkit/fastapi/builder.py +0 -0
  19. {apexdevkit-1.31.1 → apexdevkit-1.32.1}/apexdevkit/fastapi/dependable.py +0 -0
  20. {apexdevkit-1.31.1 → apexdevkit-1.32.1}/apexdevkit/fastapi/docs.py +0 -0
  21. {apexdevkit-1.31.1 → apexdevkit-1.32.1}/apexdevkit/fastapi/name.py +0 -0
  22. {apexdevkit-1.31.1 → apexdevkit-1.32.1}/apexdevkit/fastapi/resource.py +0 -0
  23. {apexdevkit-1.31.1 → apexdevkit-1.32.1}/apexdevkit/fastapi/response.py +0 -0
  24. {apexdevkit-1.31.1 → apexdevkit-1.32.1}/apexdevkit/fastapi/router.py +0 -0
  25. {apexdevkit-1.31.1 → apexdevkit-1.32.1}/apexdevkit/fastapi/schema.py +0 -0
  26. {apexdevkit-1.31.1 → apexdevkit-1.32.1}/apexdevkit/fastapi/service.py +0 -0
  27. {apexdevkit-1.31.1 → apexdevkit-1.32.1}/apexdevkit/fluent.py +0 -0
  28. {apexdevkit-1.31.1 → apexdevkit-1.32.1}/apexdevkit/formatter.py +0 -0
  29. {apexdevkit-1.31.1 → apexdevkit-1.32.1}/apexdevkit/http/__init__.py +0 -0
  30. {apexdevkit-1.31.1 → apexdevkit-1.32.1}/apexdevkit/http/fake.py +0 -0
  31. {apexdevkit-1.31.1 → apexdevkit-1.32.1}/apexdevkit/http/fluent.py +0 -0
  32. {apexdevkit-1.31.1 → apexdevkit-1.32.1}/apexdevkit/http/httpx/__init__.py +0 -0
  33. {apexdevkit-1.31.1 → apexdevkit-1.32.1}/apexdevkit/http/httpx/client.py +0 -0
  34. {apexdevkit-1.31.1 → apexdevkit-1.32.1}/apexdevkit/http/httpx/hooks.py +0 -0
  35. {apexdevkit-1.31.1 → apexdevkit-1.32.1}/apexdevkit/http/json.py +0 -0
  36. {apexdevkit-1.31.1 → apexdevkit-1.32.1}/apexdevkit/http/url.py +0 -0
  37. {apexdevkit-1.31.1 → apexdevkit-1.32.1}/apexdevkit/py.typed +0 -0
  38. {apexdevkit-1.31.1 → apexdevkit-1.32.1}/apexdevkit/query/__init__.py +0 -0
  39. {apexdevkit-1.31.1 → apexdevkit-1.32.1}/apexdevkit/query/generator.py +0 -0
  40. {apexdevkit-1.31.1 → apexdevkit-1.32.1}/apexdevkit/query/query.py +0 -0
  41. {apexdevkit-1.31.1 → apexdevkit-1.32.1}/apexdevkit/repository/__init__.py +0 -0
  42. {apexdevkit-1.31.1 → apexdevkit-1.32.1}/apexdevkit/repository/core/__init__.py +0 -0
  43. {apexdevkit-1.31.1 → apexdevkit-1.32.1}/apexdevkit/repository/core/database.py +0 -0
  44. {apexdevkit-1.31.1 → apexdevkit-1.32.1}/apexdevkit/repository/core/decorator.py +0 -0
  45. {apexdevkit-1.31.1 → apexdevkit-1.32.1}/apexdevkit/repository/core/interface.py +0 -0
  46. {apexdevkit-1.31.1 → apexdevkit-1.32.1}/apexdevkit/repository/core/mixin.py +0 -0
  47. {apexdevkit-1.31.1 → apexdevkit-1.32.1}/apexdevkit/repository/core/multi.py +0 -0
  48. {apexdevkit-1.31.1 → apexdevkit-1.32.1}/apexdevkit/repository/in_memory/__init__.py +0 -0
  49. {apexdevkit-1.31.1 → apexdevkit-1.32.1}/apexdevkit/repository/in_memory/mixin.py +0 -0
  50. {apexdevkit-1.31.1 → apexdevkit-1.32.1}/apexdevkit/repository/in_memory/repository.py +0 -0
  51. {apexdevkit-1.31.1 → apexdevkit-1.32.1}/apexdevkit/repository/in_memory/store.py +0 -0
  52. {apexdevkit-1.31.1 → apexdevkit-1.32.1}/apexdevkit/repository/sql/__init__.py +0 -0
  53. {apexdevkit-1.31.1 → apexdevkit-1.32.1}/apexdevkit/repository/sql/connector.py +0 -0
  54. {apexdevkit-1.31.1 → apexdevkit-1.32.1}/apexdevkit/repository/sql/field.py +0 -0
  55. {apexdevkit-1.31.1 → apexdevkit-1.32.1}/apexdevkit/repository/sql/mssql.py +0 -0
  56. {apexdevkit-1.31.1 → apexdevkit-1.32.1}/apexdevkit/repository/sql/sqlite.py +0 -0
  57. {apexdevkit-1.31.1 → apexdevkit-1.32.1}/apexdevkit/security/__init__.py +0 -0
  58. {apexdevkit-1.31.1 → apexdevkit-1.32.1}/apexdevkit/security/core.py +0 -0
  59. {apexdevkit-1.31.1 → apexdevkit-1.32.1}/apexdevkit/security/hashing.py +0 -0
  60. {apexdevkit-1.31.1 → apexdevkit-1.32.1}/apexdevkit/server.py +0 -0
  61. {apexdevkit-1.31.1 → apexdevkit-1.32.1}/apexdevkit/testing/__init__.py +0 -0
  62. {apexdevkit-1.31.1 → apexdevkit-1.32.1}/apexdevkit/testing/database.py +0 -0
  63. {apexdevkit-1.31.1 → apexdevkit-1.32.1}/apexdevkit/testing/fake.py +0 -0
  64. {apexdevkit-1.31.1 → apexdevkit-1.32.1}/apexdevkit/testing/rest.py +0 -0
  65. {apexdevkit-1.31.1 → apexdevkit-1.32.1}/apexdevkit/value.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: apexdevkit
3
- Version: 1.31.1
3
+ Version: 1.32.1
4
4
  Summary: Apex Development Tools for python.
5
5
  License-File: LICENSE
6
6
  Author: Apex Dev
@@ -1,8 +1,11 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from collections.abc import Callable
3
4
  from dataclasses import dataclass
4
5
  from typing import Any
5
6
 
7
+ KeyFn = Callable[[Any], str]
8
+
6
9
 
7
10
  @dataclass(frozen=True)
8
11
  class AttributeKey:
@@ -0,0 +1,19 @@
1
+ from .observer import SyncObserver
2
+ from .source import EmptySource, Source, SourceDecorator, SourceFailing, SourcePreSet
3
+ from .sync import Sync, SyncProbe
4
+ from .target import NoTarget, Target, TargetDecorator, TargetFailing
5
+
6
+ __all__ = [
7
+ "Sync",
8
+ "SyncObserver",
9
+ "SyncProbe",
10
+ "EmptySource",
11
+ "Source",
12
+ "SourceDecorator",
13
+ "SourceFailing",
14
+ "SourcePreSet",
15
+ "NoTarget",
16
+ "Target",
17
+ "TargetDecorator",
18
+ "TargetFailing",
19
+ ]
@@ -0,0 +1,89 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Callable
4
+ from dataclasses import dataclass, field
5
+ from typing import Protocol, Self
6
+
7
+
8
+ @dataclass(frozen=True)
9
+ class ObservableSync:
10
+ observers: list[SyncObserver] = field(default_factory=list)
11
+
12
+ def attach(self, observer: SyncObserver, when: bool = True) -> Self:
13
+ if when:
14
+ self.observers.append(observer)
15
+
16
+ return self
17
+
18
+ def before_prune(self, n_items: int) -> None:
19
+ for observer in self.observers:
20
+ observer.before_prune(n_items)
21
+
22
+ def after_prune(self, n_items: int) -> None:
23
+ _ = n_items
24
+
25
+ for observer in self.observers:
26
+ observer.after_prune()
27
+
28
+ def before_load(self, n_items: int) -> None:
29
+ for observer in self.observers:
30
+ observer.before_load(n_items)
31
+
32
+ def after_load(self, n_items: int) -> None:
33
+ _ = n_items
34
+
35
+ for observer in self.observers:
36
+ observer.after_load()
37
+
38
+ def before_update(self, n_items: int) -> None:
39
+ for observer in self.observers:
40
+ observer.before_update(n_items)
41
+
42
+ def after_update(self, n_items: int) -> None:
43
+ _ = n_items
44
+
45
+ for observer in self.observers:
46
+ observer.after_update()
47
+
48
+
49
+ class SyncObserver(Protocol): # pragma: no cover
50
+ def before_prune(self, n_items: int) -> None:
51
+ pass
52
+
53
+ def after_prune(self) -> None:
54
+ pass
55
+
56
+ def before_load(self, n_items: int) -> None:
57
+ pass
58
+
59
+ def after_load(self) -> None:
60
+ pass
61
+
62
+ def before_update(self, n_items: int) -> None:
63
+ pass
64
+
65
+ def after_update(self) -> None:
66
+ pass
67
+
68
+
69
+ @dataclass(frozen=True)
70
+ class DefaultLog:
71
+ echo: Callable[[str], None] = print
72
+
73
+ def before_prune(self, n_items: int) -> None:
74
+ self.echo(f"Total # of absent items: {n_items}")
75
+
76
+ def after_prune(self) -> None:
77
+ self.echo("Pruning completed")
78
+
79
+ def before_load(self, n_items: int) -> None:
80
+ self.echo(f"Total # of new items: {n_items}")
81
+
82
+ def after_load(self) -> None:
83
+ self.echo("Loading completed")
84
+
85
+ def before_update(self, n_items: int) -> None:
86
+ self.echo(f"Total # of updates: {n_items}")
87
+
88
+ def after_update(self) -> None:
89
+ self.echo("Updating completed")
@@ -0,0 +1,122 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Iterable
4
+ from dataclasses import dataclass, field
5
+ from typing import Generic, Protocol, TypeVar
6
+
7
+ from apexdevkit.key_fn import AttributeKey, KeyFn
8
+
9
+ T = TypeVar("T")
10
+
11
+
12
+ class Source(Protocol): # pragma: no cover
13
+ def absent(self) -> Iterable[T]:
14
+ pass
15
+
16
+ def new(self) -> Iterable[T]:
17
+ pass
18
+
19
+ def updates(self) -> Iterable[T]:
20
+ pass
21
+
22
+
23
+ @dataclass(frozen=True)
24
+ class EmptySource: # pragma: no cover
25
+ def absent(self) -> Iterable[T]:
26
+ return []
27
+
28
+ def new(self) -> Iterable[T]:
29
+ return []
30
+
31
+ def updates(self) -> Iterable[T]:
32
+ return []
33
+
34
+
35
+ @dataclass(frozen=True, kw_only=True)
36
+ class SourceDecorator: # pragma: no cover
37
+ inner: Source = field(default_factory=EmptySource)
38
+
39
+ def absent(self) -> Iterable[T]:
40
+ return self.inner.absent()
41
+
42
+ def new(self) -> Iterable[T]:
43
+ return self.inner.new()
44
+
45
+ def updates(self) -> Iterable[T]:
46
+ return self.inner.updates()
47
+
48
+
49
+ @dataclass(frozen=True, kw_only=True)
50
+ class SourcePreSet(Generic[T]):
51
+ removals: Iterable[T]
52
+ additions: Iterable[T]
53
+ changes: Iterable[T]
54
+
55
+ def absent(self) -> Iterable[T]:
56
+ return self.removals
57
+
58
+ def new(self) -> Iterable[T]:
59
+ return self.additions
60
+
61
+ def updates(self) -> Iterable[T]:
62
+ return self.changes
63
+
64
+
65
+ class SourceFailing:
66
+ @staticmethod
67
+ def on_everything(using: Source | None = None) -> Source:
68
+ return SourceFailing.on_absent(
69
+ using=SourceFailing.on_new(
70
+ using=SourceFailing.on_update(
71
+ using=using or EmptySource(),
72
+ ),
73
+ ),
74
+ )
75
+
76
+ @staticmethod
77
+ def on_absent(using: Source | None = None) -> Source:
78
+ return SourceFailing.OnAbsent(inner=using or EmptySource())
79
+
80
+ @dataclass(frozen=True, kw_only=True)
81
+ class OnAbsent(SourceDecorator):
82
+ def absent(self) -> Iterable[T]: # pragma: no cover
83
+ raise RuntimeError("Should not request absent!")
84
+
85
+ @staticmethod
86
+ def on_new(using: Source | None = None) -> Source:
87
+ return SourceFailing.OnNew(inner=using or EmptySource())
88
+
89
+ @dataclass(frozen=True, kw_only=True)
90
+ class OnNew(SourceDecorator):
91
+ def new(self) -> Iterable[T]: # pragma: no cover
92
+ raise RuntimeError("Should not request new!")
93
+
94
+ @staticmethod
95
+ def on_update(using: Source | None = None) -> Source:
96
+ return SourceFailing.OnUpdate(inner=using or EmptySource())
97
+
98
+ @dataclass(frozen=True, kw_only=True)
99
+ class OnUpdate(SourceDecorator):
100
+ def updates(self) -> Iterable[T]: # pragma: no cover
101
+ raise RuntimeError("Should not request updates!")
102
+
103
+
104
+ @dataclass(frozen=True, kw_only=True)
105
+ class SourceDiscriminator(Generic[T]):
106
+ current: Iterable[T]
107
+ latest: Iterable[T]
108
+
109
+ key_fn: KeyFn = AttributeKey("id")
110
+
111
+ def absent(self) -> Iterable[T]:
112
+ index = {self.key_fn(item) for item in self.latest}
113
+
114
+ return [item for item in self.current if self.key_fn(item) not in index]
115
+
116
+ def new(self) -> Iterable[T]:
117
+ index = {self.key_fn(item) for item in self.current}
118
+
119
+ return [item for item in self.latest if self.key_fn(item) not in index]
120
+
121
+ def updates(self) -> Iterable[T]:
122
+ return set(self.latest).difference(self.current)
@@ -0,0 +1,78 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Callable, Collection, Iterable
4
+ from dataclasses import dataclass
5
+ from typing import Any, Self
6
+
7
+ from .observer import DefaultLog, ObservableSync
8
+ from .source import Source
9
+ from .target import Target
10
+
11
+
12
+ @dataclass(frozen=True, kw_only=True)
13
+ class Sync(ObservableSync):
14
+ source: Source
15
+ target: Target
16
+
17
+ dry_run: bool = False
18
+
19
+ def with_log(self, using: Callable[[str], None]) -> Self:
20
+ return self.attach(DefaultLog(echo=using))
21
+
22
+ def run(self, prune: bool = True, load: bool = True, update: bool = True) -> None:
23
+ if prune:
24
+ self.prune()
25
+
26
+ if load:
27
+ self.load()
28
+
29
+ if update:
30
+ self.update()
31
+
32
+ def prune(self) -> None:
33
+ (
34
+ SyncProbe(source=set(self.source.absent()))
35
+ .disable(when=self.dry_run)
36
+ .notify(using=self.before_prune)
37
+ .send(using=self.target.prune)
38
+ .notify(using=self.after_prune)
39
+ )
40
+
41
+ def load(self) -> None:
42
+ (
43
+ SyncProbe(source=set(self.source.new()))
44
+ .disable(when=self.dry_run)
45
+ .notify(using=self.before_load)
46
+ .send(using=self.target.load)
47
+ .notify(using=self.after_load)
48
+ )
49
+
50
+ def update(self) -> None:
51
+ (
52
+ SyncProbe(source=set(self.source.updates()))
53
+ .disable(when=self.dry_run)
54
+ .notify(using=self.before_update)
55
+ .send(using=self.target.renew)
56
+ .notify(using=self.after_update)
57
+ )
58
+
59
+
60
+ @dataclass(frozen=True, kw_only=True)
61
+ class SyncProbe:
62
+ source: Collection[Any]
63
+
64
+ is_enabled: bool = True
65
+
66
+ def disable(self, when: bool) -> SyncProbe:
67
+ return SyncProbe(source=self.source, is_enabled=not when)
68
+
69
+ def notify(self, using: Callable[[int], None]) -> SyncProbe:
70
+ using(len(self.source))
71
+
72
+ return self
73
+
74
+ def send(self, using: Callable[[Iterable[Any]], None]) -> SyncProbe:
75
+ if self.is_enabled and len(self.source) > 0:
76
+ using(self.source)
77
+
78
+ return self
@@ -0,0 +1,82 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Iterable
4
+ from dataclasses import dataclass, field
5
+ from typing import Protocol, TypeVar
6
+
7
+ T = TypeVar("T")
8
+
9
+
10
+ class Target(Protocol): # pragma: no cover
11
+ def prune(self, source: Iterable[T]) -> None:
12
+ pass
13
+
14
+ def load(self, source: Iterable[T]) -> None:
15
+ pass
16
+
17
+ def renew(self, source: Iterable[T]) -> None:
18
+ pass
19
+
20
+
21
+ class NoTarget: # pragma: no cover
22
+ def prune(self, source: Iterable[T]) -> None:
23
+ pass
24
+
25
+ def load(self, source: Iterable[T]) -> None:
26
+ pass
27
+
28
+ def renew(self, source: Iterable[T]) -> None:
29
+ pass
30
+
31
+
32
+ @dataclass(frozen=True, kw_only=True)
33
+ class TargetDecorator:
34
+ inner: Target = field(default_factory=NoTarget)
35
+
36
+ def prune(self, source: Iterable[T]) -> None:
37
+ self.inner.prune(source)
38
+
39
+ def load(self, source: Iterable[T]) -> None:
40
+ self.inner.load(source)
41
+
42
+ def renew(self, source: Iterable[T]) -> None:
43
+ self.inner.renew(source)
44
+
45
+
46
+ class TargetFailing:
47
+ @staticmethod
48
+ def on_everything(using: Target | None = None) -> Target:
49
+ return TargetFailing.on_prune(
50
+ using=TargetFailing.on_load(
51
+ using=TargetFailing.on_update(
52
+ using=using or NoTarget(),
53
+ ),
54
+ ),
55
+ )
56
+
57
+ @staticmethod
58
+ def on_prune(using: Target | None = None) -> Target:
59
+ return TargetFailing.OnPrune(inner=using or NoTarget())
60
+
61
+ @dataclass(frozen=True, kw_only=True)
62
+ class OnPrune(TargetDecorator):
63
+ def prune(self, source: Iterable[T]) -> None: # pragma: no cover
64
+ raise RuntimeError(source)
65
+
66
+ @staticmethod
67
+ def on_load(using: Target | None = None) -> Target:
68
+ return TargetFailing.OnLoad(inner=using or NoTarget())
69
+
70
+ @dataclass(frozen=True, kw_only=True)
71
+ class OnLoad(TargetDecorator):
72
+ def load(self, source: Iterable[T]) -> None: # pragma: no cover
73
+ raise RuntimeError(source)
74
+
75
+ @staticmethod
76
+ def on_update(using: Target | None = None) -> Target:
77
+ return TargetFailing.OnUpdate(inner=using or NoTarget())
78
+
79
+ @dataclass(frozen=True, kw_only=True)
80
+ class OnUpdate(TargetDecorator):
81
+ def renew(self, source: Iterable[T]) -> None: # pragma: no cover
82
+ raise RuntimeError(source)
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "apexdevkit"
3
- version = "1.31.1"
3
+ version = "1.32.1"
4
4
  description = "Apex Development Tools for python."
5
5
  readme = "README.md"
6
6
  authors = [
File without changes
File without changes