refresh-engine 0.1.0__py3-none-any.whl
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.
- refresh_engine/__init__.py +169 -0
- refresh_engine/api/__init__.py +43 -0
- refresh_engine/api/abc.py +176 -0
- refresh_engine/api/engine.py +468 -0
- refresh_engine/api/protocols.py +89 -0
- refresh_engine/core/models.py +362 -0
- refresh_engine/detection/__init__.py +21 -0
- refresh_engine/detection/detector.py +54 -0
- refresh_engine/detection/fingerprints.py +115 -0
- refresh_engine/errors.py +57 -0
- refresh_engine/events/__init__.py +6 -0
- refresh_engine/events/models.py +42 -0
- refresh_engine/events/publisher.py +77 -0
- refresh_engine/execution/__init__.py +5 -0
- refresh_engine/execution/coordinator.py +128 -0
- refresh_engine/execution/executor.py +180 -0
- refresh_engine/observability/__init__.py +9 -0
- refresh_engine/observability/logging.py +49 -0
- refresh_engine/observability/metrics.py +44 -0
- refresh_engine/planning/__init__.py +6 -0
- refresh_engine/planning/dependency.py +79 -0
- refresh_engine/planning/planner.py +86 -0
- refresh_engine/py.typed +0 -0
- refresh_engine/scheduling/__init__.py +13 -0
- refresh_engine/scheduling/async_scheduler.py +126 -0
- refresh_engine/scheduling/external.py +17 -0
- refresh_engine/stores/__init__.py +11 -0
- refresh_engine/stores/memory.py +77 -0
- refresh_engine/triggers/__init__.py +16 -0
- refresh_engine/triggers/adapters.py +38 -0
- refresh_engine/triggers/selectors.py +46 -0
- refresh_engine-0.1.0.dist-info/METADATA +95 -0
- refresh_engine-0.1.0.dist-info/RECORD +35 -0
- refresh_engine-0.1.0.dist-info/WHEEL +4 -0
- refresh_engine-0.1.0.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"""Production-grade generic refresh infrastructure."""
|
|
2
|
+
|
|
3
|
+
from refresh_engine.api import (
|
|
4
|
+
AsyncSchedulerABC,
|
|
5
|
+
EventHandler,
|
|
6
|
+
EventPublisherABC,
|
|
7
|
+
FingerprintStrategy,
|
|
8
|
+
FingerprintStrategyABC,
|
|
9
|
+
MetricsSink,
|
|
10
|
+
MetricsSinkABC,
|
|
11
|
+
RefreshOperation,
|
|
12
|
+
RefreshOperationABC,
|
|
13
|
+
ResourceSelector,
|
|
14
|
+
ResourceSelectorABC,
|
|
15
|
+
ResourceSource,
|
|
16
|
+
ResourceSourceABC,
|
|
17
|
+
StateStore,
|
|
18
|
+
StateStoreABC,
|
|
19
|
+
StateTransaction,
|
|
20
|
+
StateTransactionABC,
|
|
21
|
+
)
|
|
22
|
+
from refresh_engine.api.engine import RefreshEngine
|
|
23
|
+
from refresh_engine.core.models import (
|
|
24
|
+
Change,
|
|
25
|
+
ChangeSet,
|
|
26
|
+
ChangeType,
|
|
27
|
+
DiscoveryResult,
|
|
28
|
+
FailurePolicy,
|
|
29
|
+
ImpactSet,
|
|
30
|
+
OverlapPolicy,
|
|
31
|
+
PlanAction,
|
|
32
|
+
RefreshConfig,
|
|
33
|
+
RefreshMode,
|
|
34
|
+
RefreshRequest,
|
|
35
|
+
RefreshResult,
|
|
36
|
+
RefreshStatus,
|
|
37
|
+
Resource,
|
|
38
|
+
ResourceFingerprint,
|
|
39
|
+
ResourceSnapshot,
|
|
40
|
+
ResourceState,
|
|
41
|
+
RetryConfig,
|
|
42
|
+
TimeoutConfig,
|
|
43
|
+
TriggerSource,
|
|
44
|
+
)
|
|
45
|
+
from refresh_engine.detection import (
|
|
46
|
+
ChangeDetector,
|
|
47
|
+
CompositeHash,
|
|
48
|
+
ContentHash,
|
|
49
|
+
ETagFingerprint,
|
|
50
|
+
MetadataHash,
|
|
51
|
+
VersionTokenFingerprint,
|
|
52
|
+
)
|
|
53
|
+
from refresh_engine.errors import (
|
|
54
|
+
CancellationError,
|
|
55
|
+
ChangeDetectionError,
|
|
56
|
+
ConfigurationError,
|
|
57
|
+
DependencyCycleError,
|
|
58
|
+
DependencyError,
|
|
59
|
+
DiscoveryError,
|
|
60
|
+
ExecutionError,
|
|
61
|
+
FingerprintError,
|
|
62
|
+
PlanningError,
|
|
63
|
+
RefreshError,
|
|
64
|
+
SchedulerError,
|
|
65
|
+
SnapshotError,
|
|
66
|
+
StateStoreError,
|
|
67
|
+
)
|
|
68
|
+
from refresh_engine.events import EventKind, EventPublisher, RefreshEvent
|
|
69
|
+
from refresh_engine.observability import (
|
|
70
|
+
InMemoryMetrics,
|
|
71
|
+
StructlogEventHandler,
|
|
72
|
+
default_redactor,
|
|
73
|
+
)
|
|
74
|
+
from refresh_engine.planning import (
|
|
75
|
+
DependencyGraph,
|
|
76
|
+
ImpactAnalyzer,
|
|
77
|
+
RefreshPlanner,
|
|
78
|
+
)
|
|
79
|
+
from refresh_engine.scheduling import (
|
|
80
|
+
AsyncScheduler,
|
|
81
|
+
ExternalSchedulerAdapter,
|
|
82
|
+
)
|
|
83
|
+
from refresh_engine.stores import InMemoryStateStore
|
|
84
|
+
from refresh_engine.triggers import (
|
|
85
|
+
EventTrigger,
|
|
86
|
+
IDSelector,
|
|
87
|
+
PredicateSelector,
|
|
88
|
+
QueryTrigger,
|
|
89
|
+
TagSelector,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
__version__ = "0.1.0"
|
|
93
|
+
|
|
94
|
+
__all__ = [
|
|
95
|
+
"AsyncScheduler",
|
|
96
|
+
"AsyncSchedulerABC",
|
|
97
|
+
"CancellationError",
|
|
98
|
+
"Change",
|
|
99
|
+
"ChangeDetectionError",
|
|
100
|
+
"ChangeDetector",
|
|
101
|
+
"ChangeSet",
|
|
102
|
+
"ChangeType",
|
|
103
|
+
"CompositeHash",
|
|
104
|
+
"ConfigurationError",
|
|
105
|
+
"ContentHash",
|
|
106
|
+
"DependencyCycleError",
|
|
107
|
+
"DependencyError",
|
|
108
|
+
"DependencyGraph",
|
|
109
|
+
"DiscoveryError",
|
|
110
|
+
"DiscoveryResult",
|
|
111
|
+
"ETagFingerprint",
|
|
112
|
+
"EventHandler",
|
|
113
|
+
"EventKind",
|
|
114
|
+
"EventPublisher",
|
|
115
|
+
"EventPublisherABC",
|
|
116
|
+
"EventTrigger",
|
|
117
|
+
"ExecutionError",
|
|
118
|
+
"ExternalSchedulerAdapter",
|
|
119
|
+
"FailurePolicy",
|
|
120
|
+
"FingerprintError",
|
|
121
|
+
"FingerprintStrategy",
|
|
122
|
+
"FingerprintStrategyABC",
|
|
123
|
+
"IDSelector",
|
|
124
|
+
"ImpactAnalyzer",
|
|
125
|
+
"ImpactSet",
|
|
126
|
+
"InMemoryMetrics",
|
|
127
|
+
"InMemoryStateStore",
|
|
128
|
+
"MetadataHash",
|
|
129
|
+
"MetricsSink",
|
|
130
|
+
"MetricsSinkABC",
|
|
131
|
+
"OverlapPolicy",
|
|
132
|
+
"PlanAction",
|
|
133
|
+
"PlanningError",
|
|
134
|
+
"PredicateSelector",
|
|
135
|
+
"QueryTrigger",
|
|
136
|
+
"RefreshConfig",
|
|
137
|
+
"RefreshEngine",
|
|
138
|
+
"RefreshError",
|
|
139
|
+
"RefreshEvent",
|
|
140
|
+
"RefreshMode",
|
|
141
|
+
"RefreshOperation",
|
|
142
|
+
"RefreshOperationABC",
|
|
143
|
+
"RefreshPlanner",
|
|
144
|
+
"RefreshRequest",
|
|
145
|
+
"RefreshResult",
|
|
146
|
+
"RefreshStatus",
|
|
147
|
+
"Resource",
|
|
148
|
+
"ResourceFingerprint",
|
|
149
|
+
"ResourceSelector",
|
|
150
|
+
"ResourceSelectorABC",
|
|
151
|
+
"ResourceSnapshot",
|
|
152
|
+
"ResourceSource",
|
|
153
|
+
"ResourceSourceABC",
|
|
154
|
+
"ResourceState",
|
|
155
|
+
"RetryConfig",
|
|
156
|
+
"SchedulerError",
|
|
157
|
+
"SnapshotError",
|
|
158
|
+
"StateStore",
|
|
159
|
+
"StateStoreABC",
|
|
160
|
+
"StateStoreError",
|
|
161
|
+
"StateTransaction",
|
|
162
|
+
"StateTransactionABC",
|
|
163
|
+
"StructlogEventHandler",
|
|
164
|
+
"TagSelector",
|
|
165
|
+
"TimeoutConfig",
|
|
166
|
+
"TriggerSource",
|
|
167
|
+
"VersionTokenFingerprint",
|
|
168
|
+
"default_redactor",
|
|
169
|
+
]
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Public extension protocols."""
|
|
2
|
+
|
|
3
|
+
from refresh_engine.api.abc import (
|
|
4
|
+
AsyncSchedulerABC,
|
|
5
|
+
EventPublisherABC,
|
|
6
|
+
FingerprintStrategyABC,
|
|
7
|
+
MetricsSinkABC,
|
|
8
|
+
RefreshOperationABC,
|
|
9
|
+
ResourceSelectorABC,
|
|
10
|
+
ResourceSourceABC,
|
|
11
|
+
StateStoreABC,
|
|
12
|
+
StateTransactionABC,
|
|
13
|
+
)
|
|
14
|
+
from refresh_engine.api.protocols import (
|
|
15
|
+
EventHandler,
|
|
16
|
+
FingerprintStrategy,
|
|
17
|
+
MetricsSink,
|
|
18
|
+
RefreshOperation,
|
|
19
|
+
ResourceSelector,
|
|
20
|
+
ResourceSource,
|
|
21
|
+
StateStore,
|
|
22
|
+
StateTransaction,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
__all__ = [
|
|
26
|
+
"AsyncSchedulerABC",
|
|
27
|
+
"EventHandler",
|
|
28
|
+
"EventPublisherABC",
|
|
29
|
+
"FingerprintStrategy",
|
|
30
|
+
"FingerprintStrategyABC",
|
|
31
|
+
"MetricsSink",
|
|
32
|
+
"MetricsSinkABC",
|
|
33
|
+
"RefreshOperation",
|
|
34
|
+
"RefreshOperationABC",
|
|
35
|
+
"ResourceSelector",
|
|
36
|
+
"ResourceSelectorABC",
|
|
37
|
+
"ResourceSource",
|
|
38
|
+
"ResourceSourceABC",
|
|
39
|
+
"StateStore",
|
|
40
|
+
"StateStoreABC",
|
|
41
|
+
"StateTransaction",
|
|
42
|
+
"StateTransactionABC",
|
|
43
|
+
]
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
"""Nominal abstract base classes for refresh-engine extension points.
|
|
2
|
+
|
|
3
|
+
The protocol layer supports structural typing. These ABCs are optional convenience
|
|
4
|
+
bases for consumers that prefer inheritance, abstract-method enforcement, and shared
|
|
5
|
+
lifecycle semantics.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from abc import ABC, abstractmethod
|
|
11
|
+
from collections.abc import Mapping
|
|
12
|
+
from typing import TYPE_CHECKING
|
|
13
|
+
|
|
14
|
+
from refresh_engine.api.protocols import EventHandler
|
|
15
|
+
from refresh_engine.core.models import (
|
|
16
|
+
DiscoveryResult,
|
|
17
|
+
PlanAction,
|
|
18
|
+
RefreshRequest,
|
|
19
|
+
Resource,
|
|
20
|
+
ResourceFingerprint,
|
|
21
|
+
ResourceSnapshot,
|
|
22
|
+
ResourceState,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
if TYPE_CHECKING:
|
|
26
|
+
from refresh_engine.events.models import RefreshEvent
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class ResourceSourceABC(ABC):
|
|
30
|
+
"""Base class for asynchronous streaming resource sources."""
|
|
31
|
+
|
|
32
|
+
@abstractmethod
|
|
33
|
+
async def discover(self) -> DiscoveryResult:
|
|
34
|
+
"""Start a discovery and report whether it is complete."""
|
|
35
|
+
|
|
36
|
+
@abstractmethod
|
|
37
|
+
async def snapshot(self, resource: Resource) -> ResourceSnapshot:
|
|
38
|
+
"""Return observable state for one discovered resource."""
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class FingerprintStrategyABC(ABC):
|
|
42
|
+
"""Base class for deterministic strong fingerprint strategies."""
|
|
43
|
+
|
|
44
|
+
@abstractmethod
|
|
45
|
+
async def fingerprint(self, snapshot: ResourceSnapshot) -> ResourceFingerprint:
|
|
46
|
+
"""Create a deterministic fingerprint."""
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class RefreshOperationABC(ABC):
|
|
50
|
+
"""Base class for application-defined refresh side effects."""
|
|
51
|
+
|
|
52
|
+
@abstractmethod
|
|
53
|
+
async def __call__(
|
|
54
|
+
self,
|
|
55
|
+
resource: Resource | None,
|
|
56
|
+
snapshot: ResourceSnapshot | None,
|
|
57
|
+
action: PlanAction,
|
|
58
|
+
request: RefreshRequest,
|
|
59
|
+
) -> None:
|
|
60
|
+
"""Apply a refresh or deletion operation."""
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class StateTransactionABC(ABC):
|
|
64
|
+
"""Base class for an isolated state transaction."""
|
|
65
|
+
|
|
66
|
+
@abstractmethod
|
|
67
|
+
async def put(self, state: ResourceState) -> None:
|
|
68
|
+
"""Stage a state upsert."""
|
|
69
|
+
|
|
70
|
+
@abstractmethod
|
|
71
|
+
async def delete(self, resource_id: str) -> None:
|
|
72
|
+
"""Stage a state deletion."""
|
|
73
|
+
|
|
74
|
+
@abstractmethod
|
|
75
|
+
async def commit(self) -> None:
|
|
76
|
+
"""Atomically commit staged changes."""
|
|
77
|
+
|
|
78
|
+
@abstractmethod
|
|
79
|
+
async def rollback(self) -> None:
|
|
80
|
+
"""Discard staged changes."""
|
|
81
|
+
|
|
82
|
+
async def __aenter__(self) -> StateTransactionABC:
|
|
83
|
+
return self
|
|
84
|
+
|
|
85
|
+
async def __aexit__(
|
|
86
|
+
self,
|
|
87
|
+
exc_type: type[BaseException] | None,
|
|
88
|
+
exc: BaseException | None,
|
|
89
|
+
traceback: object | None,
|
|
90
|
+
) -> None:
|
|
91
|
+
if exc_type is None:
|
|
92
|
+
await self.commit()
|
|
93
|
+
else:
|
|
94
|
+
await self.rollback()
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class StateStoreABC(ABC):
|
|
98
|
+
"""Base class for transactional state persistence."""
|
|
99
|
+
|
|
100
|
+
@abstractmethod
|
|
101
|
+
async def load_all(self) -> Mapping[str, ResourceState]:
|
|
102
|
+
"""Load the last successful state."""
|
|
103
|
+
|
|
104
|
+
@abstractmethod
|
|
105
|
+
def transaction(self) -> StateTransactionABC:
|
|
106
|
+
"""Create an isolated transaction."""
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class ResourceSelectorABC(ABC):
|
|
110
|
+
"""Base class for reusable resource selection."""
|
|
111
|
+
|
|
112
|
+
@abstractmethod
|
|
113
|
+
def matches(self, resource: Resource) -> bool:
|
|
114
|
+
"""Return whether the resource is selected."""
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class MetricsSinkABC(ABC):
|
|
118
|
+
"""Base class for provider-neutral metric sinks."""
|
|
119
|
+
|
|
120
|
+
@abstractmethod
|
|
121
|
+
def increment(
|
|
122
|
+
self,
|
|
123
|
+
name: str,
|
|
124
|
+
value: int = 1,
|
|
125
|
+
attributes: Mapping[str, str] | None = None,
|
|
126
|
+
) -> None:
|
|
127
|
+
"""Increment a counter."""
|
|
128
|
+
|
|
129
|
+
@abstractmethod
|
|
130
|
+
def observe(
|
|
131
|
+
self,
|
|
132
|
+
name: str,
|
|
133
|
+
value: float,
|
|
134
|
+
attributes: Mapping[str, str] | None = None,
|
|
135
|
+
) -> None:
|
|
136
|
+
"""Record a numeric observation."""
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class EventPublisherABC(ABC):
|
|
140
|
+
"""Base class for lifecycle event transports."""
|
|
141
|
+
|
|
142
|
+
@abstractmethod
|
|
143
|
+
def subscribe(self, handler: EventHandler) -> None:
|
|
144
|
+
"""Register an event handler."""
|
|
145
|
+
|
|
146
|
+
@abstractmethod
|
|
147
|
+
def unsubscribe(self, handler: EventHandler) -> None:
|
|
148
|
+
"""Remove an event handler."""
|
|
149
|
+
|
|
150
|
+
@abstractmethod
|
|
151
|
+
async def publish(self, event: RefreshEvent) -> None:
|
|
152
|
+
"""Queue an event for delivery."""
|
|
153
|
+
|
|
154
|
+
@abstractmethod
|
|
155
|
+
async def drain(self) -> None:
|
|
156
|
+
"""Wait for queued event delivery."""
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
class AsyncSchedulerABC(ABC):
|
|
160
|
+
"""Base class for scheduler lifecycles owned by an event loop."""
|
|
161
|
+
|
|
162
|
+
@abstractmethod
|
|
163
|
+
async def start(self) -> None:
|
|
164
|
+
"""Start accepting scheduled work."""
|
|
165
|
+
|
|
166
|
+
@abstractmethod
|
|
167
|
+
async def pause(self) -> None:
|
|
168
|
+
"""Pause future scheduled work."""
|
|
169
|
+
|
|
170
|
+
@abstractmethod
|
|
171
|
+
async def resume(self) -> None:
|
|
172
|
+
"""Resume future scheduled work."""
|
|
173
|
+
|
|
174
|
+
@abstractmethod
|
|
175
|
+
async def stop(self) -> None:
|
|
176
|
+
"""Stop and release owned tasks."""
|