coalestra 0.5.0__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.
- coalestra-0.5.0/CHANGELOG.md +60 -0
- coalestra-0.5.0/CONTRIBUTING.md +9 -0
- coalestra-0.5.0/LICENSE +21 -0
- coalestra-0.5.0/MANIFEST.in +11 -0
- coalestra-0.5.0/Makefile +28 -0
- coalestra-0.5.0/PKG-INFO +456 -0
- coalestra-0.5.0/README.md +428 -0
- coalestra-0.5.0/benchmarks/benchmark_local_sources.py +48 -0
- coalestra-0.5.0/benchmarks/benchmark_snapshot.py +60 -0
- coalestra-0.5.0/docs/alphora-integration.pt-BR.md +400 -0
- coalestra-0.5.0/docs/architecture.md +240 -0
- coalestra-0.5.0/docs/implementation-4-5-6.pt-BR.md +70 -0
- coalestra-0.5.0/docs/implementation-7-8-9.pt-BR.md +86 -0
- coalestra-0.5.0/docs/integration-readiness.pt-BR.md +69 -0
- coalestra-0.5.0/docs/migration-0.3.md +74 -0
- coalestra-0.5.0/docs/migration-0.4.md +111 -0
- coalestra-0.5.0/docs/migration-0.5.md +54 -0
- coalestra-0.5.0/docs/public-api.md +350 -0
- coalestra-0.5.0/examples/alphora_adapter_example.py +145 -0
- coalestra-0.5.0/examples/capacity_circuit_publisher.py +61 -0
- coalestra-0.5.0/examples/generic_example.py +70 -0
- coalestra-0.5.0/examples/identity_refresh_observability.py +65 -0
- coalestra-0.5.0/examples/session_batch_derived.py +62 -0
- coalestra-0.5.0/pyproject.toml +68 -0
- coalestra-0.5.0/scripts/check_test_count.py +33 -0
- coalestra-0.5.0/setup.cfg +4 -0
- coalestra-0.5.0/src/coalestra/__init__.py +154 -0
- coalestra-0.5.0/src/coalestra/adapters/__init__.py +5 -0
- coalestra-0.5.0/src/coalestra/adapters/batch_source.py +94 -0
- coalestra-0.5.0/src/coalestra/adapters/callable_source.py +64 -0
- coalestra-0.5.0/src/coalestra/adapters/derived_source.py +78 -0
- coalestra-0.5.0/src/coalestra/cache/__init__.py +16 -0
- coalestra-0.5.0/src/coalestra/cache/memory.py +220 -0
- coalestra-0.5.0/src/coalestra/cache/publisher.py +343 -0
- coalestra-0.5.0/src/coalestra/concurrency/__init__.py +3 -0
- coalestra-0.5.0/src/coalestra/concurrency/capacity.py +137 -0
- coalestra-0.5.0/src/coalestra/core/__init__.py +96 -0
- coalestra-0.5.0/src/coalestra/core/clock.py +11 -0
- coalestra-0.5.0/src/coalestra/core/diagnostics.py +131 -0
- coalestra-0.5.0/src/coalestra/core/errors.py +92 -0
- coalestra-0.5.0/src/coalestra/core/health.py +23 -0
- coalestra-0.5.0/src/coalestra/core/keys.py +211 -0
- coalestra-0.5.0/src/coalestra/core/models.py +179 -0
- coalestra-0.5.0/src/coalestra/core/protocols.py +143 -0
- coalestra-0.5.0/src/coalestra/core/quality.py +19 -0
- coalestra-0.5.0/src/coalestra/core/request.py +45 -0
- coalestra-0.5.0/src/coalestra/observability/__init__.py +28 -0
- coalestra-0.5.0/src/coalestra/observability/buffered.py +285 -0
- coalestra-0.5.0/src/coalestra/observability/events.py +17 -0
- coalestra-0.5.0/src/coalestra/observability/metrics.py +63 -0
- coalestra-0.5.0/src/coalestra/orchestration/__init__.py +5 -0
- coalestra-0.5.0/src/coalestra/orchestration/builder.py +1621 -0
- coalestra-0.5.0/src/coalestra/orchestration/policy.py +26 -0
- coalestra-0.5.0/src/coalestra/orchestration/session.py +166 -0
- coalestra-0.5.0/src/coalestra/orchestration/singleflight.py +113 -0
- coalestra-0.5.0/src/coalestra/py.typed +0 -0
- coalestra-0.5.0/src/coalestra/resilience/__init__.py +25 -0
- coalestra-0.5.0/src/coalestra/resilience/circuit_breaker.py +228 -0
- coalestra-0.5.0/src/coalestra/resilience/policy.py +85 -0
- coalestra-0.5.0/src/coalestra/resilience/retry.py +96 -0
- coalestra-0.5.0/src/coalestra/sync.py +335 -0
- coalestra-0.5.0/src/coalestra.egg-info/PKG-INFO +456 -0
- coalestra-0.5.0/src/coalestra.egg-info/SOURCES.txt +87 -0
- coalestra-0.5.0/src/coalestra.egg-info/dependency_links.txt +1 -0
- coalestra-0.5.0/src/coalestra.egg-info/requires.txt +6 -0
- coalestra-0.5.0/src/coalestra.egg-info/top_level.txt +1 -0
- coalestra-0.5.0/tests/test_batch_source.py +231 -0
- coalestra-0.5.0/tests/test_buffered_observability_v04.py +97 -0
- coalestra-0.5.0/tests/test_builder.py +215 -0
- coalestra-0.5.0/tests/test_cache.py +61 -0
- coalestra-0.5.0/tests/test_cache_v04.py +128 -0
- coalestra-0.5.0/tests/test_callable_source.py +33 -0
- coalestra-0.5.0/tests/test_circuit_breaker.py +66 -0
- coalestra-0.5.0/tests/test_circuit_scopes.py +294 -0
- coalestra-0.5.0/tests/test_concurrency.py +42 -0
- coalestra-0.5.0/tests/test_deadline.py +48 -0
- coalestra-0.5.0/tests/test_derived_source.py +249 -0
- coalestra-0.5.0/tests/test_diagnostics_v04.py +53 -0
- coalestra-0.5.0/tests/test_global_capacity.py +148 -0
- coalestra-0.5.0/tests/test_integration_v05.py +343 -0
- coalestra-0.5.0/tests/test_matrix_v05.py +245 -0
- coalestra-0.5.0/tests/test_models.py +21 -0
- coalestra-0.5.0/tests/test_policy.py +21 -0
- coalestra-0.5.0/tests/test_publisher.py +170 -0
- coalestra-0.5.0/tests/test_refresh_v04.py +140 -0
- coalestra-0.5.0/tests/test_resource_keys_v04.py +91 -0
- coalestra-0.5.0/tests/test_session.py +192 -0
- coalestra-0.5.0/tests/test_singleflight.py +73 -0
- coalestra-0.5.0/tests/test_sync.py +78 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.5.0 - 2026-06-13
|
|
4
|
+
|
|
5
|
+
- Added `SnapshotRequest` with required and optional resource semantics.
|
|
6
|
+
- Added partial snapshots to `SnapshotBuildError` for immediate fallback and diagnostics.
|
|
7
|
+
- Added `SnapshotBuilder.build_request()` and synchronous/session equivalents.
|
|
8
|
+
- Added `BuilderHealth` and health snapshots covering cache, capacity, circuits, refreshes, and single-flight work.
|
|
9
|
+
- Added configurable, bounded LRU caching for `source.supports()` decisions.
|
|
10
|
+
- Added `max_batch_size` with capacity-aware chunk dispatch for batch sources.
|
|
11
|
+
- Added deadline-aware retry backoff and exact retry-attempt diagnostics.
|
|
12
|
+
- Added `ObservationPolicy` for future-timestamp precision in sources and event publication.
|
|
13
|
+
- Fixed bulk publication so the newest duplicate update wins regardless of input order.
|
|
14
|
+
- Preserved each `ResourceKey` normalizer across qualifier transformations.
|
|
15
|
+
- Added optional inline execution for guaranteed non-blocking synchronous source adapters.
|
|
16
|
+
- Added managed component lifecycle and corrected synchronous builder shutdown.
|
|
17
|
+
- Expanded snapshot diagnostics with support-cache, retry, chunk, and timestamp-rejection counters.
|
|
18
|
+
- Added an enforced 500-test minimum; the release suite contains 618 collected tests.
|
|
19
|
+
|
|
20
|
+
## 0.4.0 - 2026-06-13
|
|
21
|
+
|
|
22
|
+
- Made `ResourceKey` case-preserving by default and added configurable `KeyNormalizer` policies.
|
|
23
|
+
- Added immutable, order-independent resource qualifiers for parameterized resources.
|
|
24
|
+
- Added `LEGACY_KEY_NORMALIZER` and migration helpers for 0.1-0.3 behavior.
|
|
25
|
+
- Added `BatchAsyncCache`, cache `get_many`/`set_many`/`invalidate_many`, namespace invalidation, pruning, and cache statistics.
|
|
26
|
+
- Added a bounded default memory-cache size with LRU eviction and automatic removal of fully expired entries.
|
|
27
|
+
- Added `RefreshMode.BLOCKING`, `STALE_WHILE_REVALIDATE`, and `REFRESH_AHEAD`.
|
|
28
|
+
- Added cancellation-safe background refresh scheduling, deduplication, lifecycle methods, and synchronous waiting.
|
|
29
|
+
- Added immutable `SnapshotDiagnostics` with per-session cache, source, batch, refresh, coalescing, latency, and observation-skew data.
|
|
30
|
+
- Added `BufferedEventSink` and `BufferedMetricsSink` with bounded queues and explicit overflow policies.
|
|
31
|
+
- Updated bulk publication to use cache batch operations under stable striped locking.
|
|
32
|
+
- Prevented background refreshes from replacing cache state with stale results.
|
|
33
|
+
- Expanded the test suite to 72 scenarios.
|
|
34
|
+
|
|
35
|
+
## 0.2.0 - 2026-06-13
|
|
36
|
+
|
|
37
|
+
- Added `BatchSnapshotSource` and `CallableBatchSource`.
|
|
38
|
+
- Added partial batch results with automatic fallback for omitted resources.
|
|
39
|
+
- Added per-key single-flight reservations for overlapping batch requests.
|
|
40
|
+
- Added asynchronous `SnapshotSession` for incremental multi-stage acquisition.
|
|
41
|
+
- Added `SyncSnapshotSession` and `SyncSnapshotBuilder.session()`.
|
|
42
|
+
- Added one identity, deadline, concurrency budget, and pinned-value memo per session.
|
|
43
|
+
- Added explicit retry of session errors through `retry_errors=True`.
|
|
44
|
+
- Added `DerivedSource` and `CallableDerivedSource`.
|
|
45
|
+
- Added recursive dependency resolution, dependency sharing, derived-value caching, and source fallback.
|
|
46
|
+
- Added direct and indirect dependency-cycle detection.
|
|
47
|
+
- Added source protocol validation for invalid batch responses and dependencies.
|
|
48
|
+
- Expanded the test suite to 37 scenarios.
|
|
49
|
+
|
|
50
|
+
## 0.1.0 - 2026-06-13
|
|
51
|
+
|
|
52
|
+
- Initial architecture.
|
|
53
|
+
- Concurrent snapshot construction.
|
|
54
|
+
- Request coalescing through single-flight execution.
|
|
55
|
+
- Per-resource freshness policies.
|
|
56
|
+
- Source priority and fallback.
|
|
57
|
+
- Retry and circuit-breaker policies.
|
|
58
|
+
- Stale-on-error support.
|
|
59
|
+
- In-memory metrics and structured events.
|
|
60
|
+
- Generic callable adapters and an Alphora integration example.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
1. Create a virtual environment.
|
|
4
|
+
2. Install the project with `python -m pip install -e ".[dev]"`.
|
|
5
|
+
3. Run `make quality` before opening a pull request.
|
|
6
|
+
4. Keep the core independent from HTTP clients, exchanges, databases, and application models.
|
|
7
|
+
5. Add tests for concurrency, fallback, freshness, and failure behavior.
|
|
8
|
+
|
|
9
|
+
Public APIs must remain protocol-oriented. Application-specific integrations belong in adapters or in the consuming application.
|
coalestra-0.5.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Igor Souza
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
include LICENSE
|
|
2
|
+
include README.md
|
|
3
|
+
include CHANGELOG.md
|
|
4
|
+
include CONTRIBUTING.md
|
|
5
|
+
include Makefile
|
|
6
|
+
recursive-include docs *.md
|
|
7
|
+
recursive-include examples *.py
|
|
8
|
+
recursive-include benchmarks *.py
|
|
9
|
+
recursive-include tests *.py
|
|
10
|
+
|
|
11
|
+
recursive-include scripts *.py
|
coalestra-0.5.0/Makefile
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
.PHONY: install format lint typecheck test test-count benchmark build quality
|
|
2
|
+
|
|
3
|
+
install:
|
|
4
|
+
python -m pip install -e ".[dev]"
|
|
5
|
+
|
|
6
|
+
format:
|
|
7
|
+
python -m ruff format .
|
|
8
|
+
|
|
9
|
+
lint:
|
|
10
|
+
python -m ruff check .
|
|
11
|
+
|
|
12
|
+
typecheck:
|
|
13
|
+
python -m mypy
|
|
14
|
+
|
|
15
|
+
test:
|
|
16
|
+
python -m pytest
|
|
17
|
+
|
|
18
|
+
test-count:
|
|
19
|
+
python scripts/check_test_count.py --minimum 500
|
|
20
|
+
|
|
21
|
+
benchmark:
|
|
22
|
+
PYTHONPATH=src python benchmarks/benchmark_snapshot.py
|
|
23
|
+
PYTHONPATH=src python benchmarks/benchmark_local_sources.py
|
|
24
|
+
|
|
25
|
+
build:
|
|
26
|
+
python -m build
|
|
27
|
+
|
|
28
|
+
quality: format lint typecheck test-count test build
|
coalestra-0.5.0/PKG-INFO
ADDED
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: coalestra
|
|
3
|
+
Version: 0.5.0
|
|
4
|
+
Summary: Integration-ready consistent snapshots with required/optional resources, bounded batch dispatch, precise deadlines, and fast local sources.
|
|
5
|
+
Author: Igor Souza
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Documentation, https://github.com/igors93/coalestra
|
|
8
|
+
Project-URL: Repository, https://github.com/igors93/coalestra
|
|
9
|
+
Project-URL: Issues, https://github.com/igors93/coalestra/issues
|
|
10
|
+
Keywords: snapshot,cache,batch,single-flight,integration,deadline,observability,orchestration,asyncio
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Typing :: Typed
|
|
19
|
+
Requires-Python: >=3.10
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: build>=1.2; extra == "dev"
|
|
24
|
+
Requires-Dist: mypy>=1.10; extra == "dev"
|
|
25
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
26
|
+
Requires-Dist: ruff>=0.6; extra == "dev"
|
|
27
|
+
Dynamic: license-file
|
|
28
|
+
|
|
29
|
+
# Coalestra
|
|
30
|
+
|
|
31
|
+
**Coalestra** is a dependency-free Python library for building consistent operational snapshots from prioritized read-only sources.
|
|
32
|
+
|
|
33
|
+
It coalesces duplicate requests, batches compatible resources, derives values from existing resources, bounds read concurrency, isolates failing source partitions, and accepts event-driven updates directly into its cache. The core is domain-agnostic and contains no knowledge of HTTP, SQL, Redis, Binance, trading, or Alphora.
|
|
34
|
+
|
|
35
|
+
## Capabilities
|
|
36
|
+
|
|
37
|
+
- Immutable snapshots with provenance and freshness metadata.
|
|
38
|
+
- Single-stage builds and incremental multi-stage `SnapshotSession` workflows.
|
|
39
|
+
- Per-key single-flight coalescing across overlapping requests.
|
|
40
|
+
- Single-resource, batch, and derived source contracts in one priority chain.
|
|
41
|
+
- Recursive dependencies, dependency sharing, and cycle detection.
|
|
42
|
+
- Builder-wide concurrency limits shared by all builds and sessions.
|
|
43
|
+
- Optional per-source concurrency limits.
|
|
44
|
+
- Source-specific retry and circuit-breaker policies.
|
|
45
|
+
- Circuit isolation by source, namespace, subject, or full resource.
|
|
46
|
+
- Case-preserving resource identity with configurable normalization and qualifiers.
|
|
47
|
+
- Batch cache reads/writes, bounded LRU storage, pruning, invalidation, and cache statistics.
|
|
48
|
+
- Per-resource TTL, stale windows, stale-on-error fallback, and background refresh modes.
|
|
49
|
+
- Direct asynchronous and synchronous event publication into the cache.
|
|
50
|
+
- Monotonic publication that rejects older or duplicate events by default.
|
|
51
|
+
- Consolidated immutable diagnostics on every snapshot.
|
|
52
|
+
- Buffered event and metrics sinks that keep downstream I/O outside the acquisition path.
|
|
53
|
+
- Replaceable cache, clock, event, and metrics interfaces.
|
|
54
|
+
- Async API plus persistent synchronous facades.
|
|
55
|
+
- Required/optional resource requests for integration-safe partial snapshots.
|
|
56
|
+
- Deadline-aware retries, bounded batch chunking, and future-timestamp validation.
|
|
57
|
+
- Health snapshots for cache, circuits, capacity, refreshes, and in-flight work.
|
|
58
|
+
- Fast inline execution for explicitly non-blocking local synchronous sources.
|
|
59
|
+
- Strict static typing and no runtime dependencies.
|
|
60
|
+
|
|
61
|
+
## Installation
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
python -m pip install -e ".[dev]"
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Python 3.10 or newer is supported.
|
|
68
|
+
|
|
69
|
+
## Integration-ready requests
|
|
70
|
+
|
|
71
|
+
Use `SnapshotRequest` to separate resources that must exist from resources that may fail without aborting the unit of work:
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
from coalestra import SnapshotRequest
|
|
75
|
+
|
|
76
|
+
request = SnapshotRequest(
|
|
77
|
+
required=[ACCOUNT, ALL_POSITIONS],
|
|
78
|
+
optional=[MARKET_HEALTH, LEARNING_CONTEXT],
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
snapshot = await builder.build_request(request, deadline_seconds=3.0)
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Only required failures raise `SnapshotBuildError`. The exception exposes a partial `snapshot`, so already resolved values and diagnostics are not lost. Sessions and synchronous facades expose the same request API.
|
|
85
|
+
|
|
86
|
+
## Fast local sources and bounded batches
|
|
87
|
+
|
|
88
|
+
Synchronous adapters run in worker threads by default. Lock-protected, non-blocking in-memory reads can opt into inline execution:
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
local_source = CallableBatchSource(
|
|
92
|
+
name="market-state",
|
|
93
|
+
priority=100,
|
|
94
|
+
supports=supports_market,
|
|
95
|
+
fetcher=read_local_market_state,
|
|
96
|
+
run_sync_in_thread=False,
|
|
97
|
+
max_batch_size=100,
|
|
98
|
+
)
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Do not use inline execution for network, filesystem, database, or any potentially blocking operation. Large batches are split into capacity-aware waves, avoiding unbounded task creation.
|
|
102
|
+
|
|
103
|
+
## Timestamp precision
|
|
104
|
+
|
|
105
|
+
`ObservationPolicy` rejects observations too far in the future, preventing clock errors from making values artificially fresh. Small accepted clock differences are recorded as `clock_skew_seconds` metadata.
|
|
106
|
+
|
|
107
|
+
## Generic resource identity
|
|
108
|
+
|
|
109
|
+
`ResourceKey` preserves case by default, trims surrounding whitespace and supports immutable qualifiers:
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
from coalestra import ResourceKey
|
|
113
|
+
|
|
114
|
+
candles = ResourceKey(
|
|
115
|
+
"market",
|
|
116
|
+
"candles",
|
|
117
|
+
"BTCUSDT",
|
|
118
|
+
{"interval": "1m", "limit": 500},
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
assert candles.qualifier("interval") == "1m"
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Qualifiers are normalized into a sorted tuple, so mapping insertion order does not affect equality or hashing. Systems with case-insensitive identity can opt in to a normalizer:
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
from coalestra import CASE_INSENSITIVE_KEY_NORMALIZER, ResourceKey
|
|
128
|
+
|
|
129
|
+
key = ResourceKey(
|
|
130
|
+
"Tenant-A",
|
|
131
|
+
"DocumentId",
|
|
132
|
+
"/Path/File",
|
|
133
|
+
normalizer=CASE_INSENSITIVE_KEY_NORMALIZER,
|
|
134
|
+
)
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
`LEGACY_KEY_NORMALIZER` and `ResourceKey.legacy(...)` reproduce Coalestra 0.1-0.3 behavior.
|
|
138
|
+
|
|
139
|
+
## Batch cache operations and refresh policies
|
|
140
|
+
|
|
141
|
+
`AsyncMemoryCache` performs multi-key reads and writes under one lock, defaults to a bounded 10,000-entry LRU, removes fully expired entries on access, and exposes statistics and namespace invalidation. Custom caches may implement `BatchAsyncCache`; older single-key caches remain supported.
|
|
142
|
+
|
|
143
|
+
Freshness policies support three refresh modes:
|
|
144
|
+
|
|
145
|
+
```python
|
|
146
|
+
from coalestra import FreshnessPolicy, RefreshMode
|
|
147
|
+
|
|
148
|
+
policy = FreshnessPolicy(
|
|
149
|
+
ttl_seconds=5.0,
|
|
150
|
+
max_stale_seconds=30.0,
|
|
151
|
+
refresh_mode=RefreshMode.STALE_WHILE_REVALIDATE,
|
|
152
|
+
)
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
- `BLOCKING`: wait for a fresh source when the cache is outside TTL.
|
|
156
|
+
- `STALE_WHILE_REVALIDATE`: return an acceptable stale value and refresh it in the background.
|
|
157
|
+
- `REFRESH_AHEAD`: return a fresh value and refresh it before TTL expiry.
|
|
158
|
+
|
|
159
|
+
Long-lived asynchronous applications can call `await builder.wait_for_refreshes()`. `SyncSnapshotBuilder.close()` waits for pending refreshes before stopping its event loop.
|
|
160
|
+
|
|
161
|
+
## Snapshot diagnostics
|
|
162
|
+
|
|
163
|
+
Every snapshot contains immutable acquisition diagnostics:
|
|
164
|
+
|
|
165
|
+
```python
|
|
166
|
+
snapshot = await builder.build(keys)
|
|
167
|
+
print(snapshot.diagnostics.cache_hits)
|
|
168
|
+
print(snapshot.diagnostics.source_calls_by_source)
|
|
169
|
+
print(snapshot.diagnostics.observation_skew_ms)
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Diagnostics include requested, resolved and failed resource counts; cache hits/misses and batch operations; stale values and coalesced requests; source, batch and derived calls; refresh outcomes; per-source latency totals; total duration; and observation-time skew.
|
|
173
|
+
|
|
174
|
+
## Buffered observability
|
|
175
|
+
|
|
176
|
+
Wrap a potentially slow sink so logging or metrics export does not run on the acquisition path:
|
|
177
|
+
|
|
178
|
+
```python
|
|
179
|
+
from coalestra import BufferedEventSink, BufferedMetricsSink
|
|
180
|
+
|
|
181
|
+
events = BufferedEventSink(file_event_sink, max_pending=10_000)
|
|
182
|
+
metrics = BufferedMetricsSink(prometheus_adapter, max_pending=10_000)
|
|
183
|
+
|
|
184
|
+
builder = SnapshotBuilder(sources, events=events, metrics=metrics)
|
|
185
|
+
|
|
186
|
+
# During shutdown
|
|
187
|
+
events.close()
|
|
188
|
+
metrics.close()
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
The default overflow policy drops the oldest queued record. `DROP_NEWEST` and `RAISE` are also available. Delivery failures are counted and never injected into resource resolution.
|
|
192
|
+
|
|
193
|
+
## Minimal build
|
|
194
|
+
|
|
195
|
+
```python
|
|
196
|
+
import asyncio
|
|
197
|
+
|
|
198
|
+
from coalestra import CallableSource, ResourceKey, SnapshotBuilder
|
|
199
|
+
|
|
200
|
+
PRICE = ResourceKey("market", "price", "BTCUSDT")
|
|
201
|
+
|
|
202
|
+
builder = SnapshotBuilder(
|
|
203
|
+
[
|
|
204
|
+
CallableSource(
|
|
205
|
+
name="rest",
|
|
206
|
+
priority=10,
|
|
207
|
+
supports=lambda key: key == PRICE,
|
|
208
|
+
fetcher=lambda _key, _context: {"price": "65000.00"},
|
|
209
|
+
)
|
|
210
|
+
]
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
snapshot = asyncio.run(builder.build([PRICE]))
|
|
214
|
+
print(snapshot.value(PRICE, dict))
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
## Batch sources
|
|
218
|
+
|
|
219
|
+
A batch source receives every unresolved compatible key available at its priority level. It may return a partial mapping; omitted resources continue through lower-priority sources.
|
|
220
|
+
|
|
221
|
+
```python
|
|
222
|
+
from coalestra import CallableBatchSource
|
|
223
|
+
|
|
224
|
+
async def fetch_prices(keys, _context):
|
|
225
|
+
symbols = [key.subject for key in keys]
|
|
226
|
+
response = await remote_api.fetch_prices(symbols)
|
|
227
|
+
return {key: response[key.subject] for key in keys if key.subject in response}
|
|
228
|
+
|
|
229
|
+
price_source = CallableBatchSource(
|
|
230
|
+
name="price-api",
|
|
231
|
+
priority=100,
|
|
232
|
+
supports=lambda key: key.namespace == "market" and key.name == "price",
|
|
233
|
+
fetcher=fetch_prices,
|
|
234
|
+
)
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
## Incremental sessions
|
|
238
|
+
|
|
239
|
+
A session keeps one identity, creation time, deadline, and pinned-value memo across multiple stages. Read capacity is controlled by the long-lived builder and is therefore shared with every other active build and session.
|
|
240
|
+
|
|
241
|
+
```python
|
|
242
|
+
async with builder.session(
|
|
243
|
+
snapshot_id="cycle-42",
|
|
244
|
+
deadline_seconds=3.0,
|
|
245
|
+
metadata={"tenant": "example"},
|
|
246
|
+
) as session:
|
|
247
|
+
baseline = await session.resolve(baseline_keys, strict=False)
|
|
248
|
+
selected = choose_resources_from(baseline)
|
|
249
|
+
final = await session.resolve(selected, strict=False)
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
Successful values remain pinned inside the session. Existing errors can be retried explicitly:
|
|
253
|
+
|
|
254
|
+
```python
|
|
255
|
+
await session.resolve([KEY], retry_errors=True)
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
## Derived resources
|
|
259
|
+
|
|
260
|
+
Derived sources declare dependencies and calculate a resource from an immutable dependency snapshot.
|
|
261
|
+
|
|
262
|
+
```python
|
|
263
|
+
from coalestra import CallableDerivedSource, ResourceKey
|
|
264
|
+
|
|
265
|
+
EXCHANGE_INFO = ResourceKey("exchange", "info")
|
|
266
|
+
|
|
267
|
+
rules_source = CallableDerivedSource(
|
|
268
|
+
name="symbol-rules",
|
|
269
|
+
priority=100,
|
|
270
|
+
supports=lambda key: key.namespace == "exchange" and key.name == "rules",
|
|
271
|
+
dependencies=lambda _key: (EXCHANGE_INFO,),
|
|
272
|
+
deriver=lambda key, snapshot, _context: extract_rules(
|
|
273
|
+
snapshot.value(EXCHANGE_INFO, dict),
|
|
274
|
+
key.subject,
|
|
275
|
+
),
|
|
276
|
+
)
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
Dependencies may themselves be cached, batched, fetched, or derived. Direct and indirect cycles are rejected.
|
|
280
|
+
|
|
281
|
+
## Global and per-source capacity
|
|
282
|
+
|
|
283
|
+
`max_concurrency` is a builder-wide limit. Concurrent calls to `build()` and multiple active sessions share the same capacity.
|
|
284
|
+
|
|
285
|
+
```python
|
|
286
|
+
builder = SnapshotBuilder(
|
|
287
|
+
sources,
|
|
288
|
+
max_concurrency=12,
|
|
289
|
+
source_concurrency={
|
|
290
|
+
"remote-rest": 4,
|
|
291
|
+
"database": 6,
|
|
292
|
+
},
|
|
293
|
+
)
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
Callable adapters can also declare their own limit:
|
|
297
|
+
|
|
298
|
+
```python
|
|
299
|
+
rest_source = CallableSource(
|
|
300
|
+
name="remote-rest",
|
|
301
|
+
priority=10,
|
|
302
|
+
supports=supports_rest,
|
|
303
|
+
fetcher=fetch_rest,
|
|
304
|
+
max_concurrency=4,
|
|
305
|
+
)
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
An explicit `source_concurrency` entry overrides the limit declared by the source. Batch calls consume one slot regardless of batch size. Derivation consumes a slot only while the derivation function itself runs; dependency acquisition uses its own source slots.
|
|
309
|
+
|
|
310
|
+
## Source-specific resilience and circuit scopes
|
|
311
|
+
|
|
312
|
+
```python
|
|
313
|
+
from coalestra import (
|
|
314
|
+
CircuitBreakerPolicy,
|
|
315
|
+
CircuitScope,
|
|
316
|
+
RetryPolicy,
|
|
317
|
+
SourceResiliencePolicy,
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
stream_policy = SourceResiliencePolicy(
|
|
321
|
+
retry=RetryPolicy(max_attempts=1),
|
|
322
|
+
circuit=CircuitBreakerPolicy(
|
|
323
|
+
scope=CircuitScope.SUBJECT,
|
|
324
|
+
failure_threshold=2,
|
|
325
|
+
recovery_timeout_seconds=5.0,
|
|
326
|
+
),
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
stream_source = CallableSource(
|
|
330
|
+
name="market-stream",
|
|
331
|
+
priority=100,
|
|
332
|
+
supports=supports_market,
|
|
333
|
+
fetcher=read_stream_state,
|
|
334
|
+
resilience_policy=stream_policy,
|
|
335
|
+
)
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
Available scopes:
|
|
339
|
+
|
|
340
|
+
- `SOURCE`: one circuit for the complete source;
|
|
341
|
+
- `NAMESPACE`: one circuit per source and resource namespace;
|
|
342
|
+
- `SUBJECT`: one circuit per source and subject;
|
|
343
|
+
- `RESOURCE`: one circuit per complete `ResourceKey`.
|
|
344
|
+
|
|
345
|
+
A stale payload is treated as an unsuccessful circuit outcome for its configured scope. With `SUBJECT`, stale data for one symbol does not disable the source for other symbols.
|
|
346
|
+
|
|
347
|
+
Policies can also be supplied centrally through `source_resilience` or a `ResiliencePolicyResolver`.
|
|
348
|
+
|
|
349
|
+
## Direct event publication
|
|
350
|
+
|
|
351
|
+
A long-lived builder exposes a `ResourcePublisher` backed by the same cache used by snapshot acquisition.
|
|
352
|
+
|
|
353
|
+
```python
|
|
354
|
+
await builder.publisher.publish(
|
|
355
|
+
PRICE,
|
|
356
|
+
{"price": "65001.25"},
|
|
357
|
+
source="market-stream",
|
|
358
|
+
observed_at=event_timestamp,
|
|
359
|
+
metadata={"sequence": sequence},
|
|
360
|
+
)
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
Publication is monotonic by default:
|
|
364
|
+
|
|
365
|
+
- an older `observed_at` is ignored;
|
|
366
|
+
- an equal timestamp is treated as a duplicate;
|
|
367
|
+
- `force=True` permits explicit reconciliation or repair;
|
|
368
|
+
- `replace_equal=True` permits replacement at the same timestamp.
|
|
369
|
+
|
|
370
|
+
Several updates can be published together:
|
|
371
|
+
|
|
372
|
+
```python
|
|
373
|
+
from coalestra import ResourceUpdate
|
|
374
|
+
|
|
375
|
+
await builder.publisher.publish_many(
|
|
376
|
+
[
|
|
377
|
+
ResourceUpdate(PRICE_BTC, btc, source="stream", observed_at=btc_time),
|
|
378
|
+
ResourceUpdate(PRICE_ETH, eth, source="stream", observed_at=eth_time),
|
|
379
|
+
]
|
|
380
|
+
)
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
Uncertain state can be invalidated:
|
|
384
|
+
|
|
385
|
+
```python
|
|
386
|
+
await builder.publisher.invalidate(POSITION_BTC, reason="stream-gap")
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
A session intentionally keeps values already pinned before a publication. New builds and new sessions observe the published value.
|
|
390
|
+
|
|
391
|
+
## Synchronous applications
|
|
392
|
+
|
|
393
|
+
`SyncSnapshotBuilder` owns one persistent event-loop thread. Keep it alive for the application lifetime.
|
|
394
|
+
|
|
395
|
+
```python
|
|
396
|
+
from coalestra import SyncSnapshotBuilder
|
|
397
|
+
|
|
398
|
+
with SyncSnapshotBuilder(builder) as sync_builder:
|
|
399
|
+
sync_builder.publisher.publish(
|
|
400
|
+
PRICE,
|
|
401
|
+
{"price": "65001.25"},
|
|
402
|
+
source="market-stream",
|
|
403
|
+
)
|
|
404
|
+
|
|
405
|
+
# Suitable for callbacks that should not block their producer thread.
|
|
406
|
+
future = sync_builder.publisher.submit_publish(
|
|
407
|
+
POSITION,
|
|
408
|
+
position,
|
|
409
|
+
source="user-stream",
|
|
410
|
+
observed_at=event_timestamp,
|
|
411
|
+
)
|
|
412
|
+
|
|
413
|
+
snapshot = sync_builder.build([PRICE, POSITION])
|
|
414
|
+
future.result()
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
Synchronous fetchers and derivation functions run in worker threads by default. `run_sync_in_thread=False` is available only for guaranteed non-blocking local reads. Transport-level timeouts remain necessary because an already-running Python thread cannot be forcibly terminated. Closing the synchronous facade closes its underlying builder by default.
|
|
418
|
+
|
|
419
|
+
## Architectural boundary
|
|
420
|
+
|
|
421
|
+
Coalestra owns read acquisition, cache publication, freshness, coalescing, fallback, derivation, and read concurrency. The consuming application owns business decisions, authorization, risk, writes, transactions, and domain validation.
|
|
422
|
+
|
|
423
|
+
## Project layout
|
|
424
|
+
|
|
425
|
+
```text
|
|
426
|
+
src/coalestra/
|
|
427
|
+
├── adapters/ # Callable single, batch, and derived sources
|
|
428
|
+
├── cache/ # Cache implementations and event publisher
|
|
429
|
+
├── concurrency/ # Builder-wide and per-source capacity control
|
|
430
|
+
├── core/ # Models, protocols, and errors
|
|
431
|
+
├── observability/ # Event and metrics sinks
|
|
432
|
+
├── orchestration/ # Builder, session, policy, and single-flight
|
|
433
|
+
├── resilience/ # Retry, circuit policies, and circuit breaker
|
|
434
|
+
└── sync.py # Persistent synchronous facades
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
## Quality pipeline
|
|
438
|
+
|
|
439
|
+
```bash
|
|
440
|
+
make quality
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
This runs formatting, linting, strict mypy, verifies the 500-test minimum, executes the full 618-test suite, and builds the package.
|
|
444
|
+
|
|
445
|
+
## Documentation
|
|
446
|
+
|
|
447
|
+
- [Architecture](docs/architecture.md)
|
|
448
|
+
- [Public API](docs/public-api.md)
|
|
449
|
+
- [Migration from 0.4](docs/migration-0.5.md)
|
|
450
|
+
- [Revisão final para integração](docs/integration-readiness.pt-BR.md)
|
|
451
|
+
- [Integração com o Alphora](docs/alphora-integration.pt-BR.md)
|
|
452
|
+
- [Changelog](CHANGELOG.md)
|
|
453
|
+
|
|
454
|
+
## License
|
|
455
|
+
|
|
456
|
+
MIT
|