refresh-engine
Keep anything in sync with its source by refreshing only what changed.
refresh-engine is an asyncio-first, domain-neutral Python library for keeping derived data current: search indexes, caches, embeddings, configuration, registries, or anything else built from a source. You describe how to list and read resources and how to apply a change; the engine detects what changed, plans the work in dependency order, runs it safely, and remembers the outcome.
Features
- Precise change detection. Deterministic fingerprints detect added, modified, and deleted resources. Reliable cheap indicators, such as ETags or version tokens, skip reading unchanged resources entirely.
- Four refresh modes. Incremental, full, dependency-aware, and targeted refreshes, with targets chosen by ID or by composable selectors.
- Dependency ordering. Dependencies are refreshed before their dependents, changes propagate to everything that depends on them, and cycles are isolated.
- Safe by default. Bounded concurrency, retries with jittered exponential backoff, timeouts, and fail-fast or best-effort policies. A partial discovery never deletes anything, and failed work is retried by the next refresh.
- Durable state. State advances only for successful resources, in one atomic commit per refresh. Use the in-memory store or plug in your own database.
- Scheduling and overlap control. A drift-free interval scheduler, a thread-safe bridge for external schedulers, and policies that coalesce, queue, skip, or supersede overlapping requests.
- Observability. Typed lifecycle events with correlation IDs, structured logging through structlog with secret redaction, and metrics.
- Lightweight and typed. Fully type-annotated, with two runtime dependencies: structlog and tenacity.
Installation
refresh-engine requires Python 3.12 or newer.
pip install refresh-engine
Quick start
Implement a source that lists and reads resources, and an operation that applies each change. The engine does the rest:
import asyncio
from refresh_engine import (
DiscoveryResult,
PlanAction,
RefreshEngine,
RefreshRequest,
Resource,
ResourceSnapshot,
)
CATALOG = {"apples": 120, "pears": 80}
SEARCH_INDEX: dict[str, int] = {}
class InventorySource:
async def discover(self) -> DiscoveryResult:
return DiscoveryResult(Resource(item) for item in sorted(CATALOG))
async def snapshot(self, resource: Resource, /) -> ResourceSnapshot:
return ResourceSnapshot(
resource.resource_id, content=CATALOG[resource.resource_id]
)
async def update_index(
resource: Resource,
snapshot: ResourceSnapshot | None,
action: PlanAction,
request: RefreshRequest,
/,
) -> None:
if action is PlanAction.DELETE:
SEARCH_INDEX.pop(resource.resource_id, None)
elif snapshot is not None:
SEARCH_INDEX[resource.resource_id] = snapshot.content
async def main() -> None:
async with RefreshEngine(InventorySource(), update_index) as engine:
first = await engine.refresh()
print(f"{first.status}: refreshed {first.refreshed_count}", SEARCH_INDEX)
CATALOG["pears"] = 75
del CATALOG["apples"]
second = await engine.refresh()
print(
f"{second.status}: modified {second.modified_count},"
f" deleted {second.deleted_count}",
SEARCH_INDEX,
)
asyncio.run(main())
success: refreshed 2 {'apples': 120, 'pears': 80}
success: modified 1, deleted 1 {'pears': 75}
The second refresh touches only the modified and the deleted resource. Continue with the getting started guide to add durable state, dependencies, scheduling, and logging.
Performance
The engine's overhead is small next to real I/O: with in-memory sources, a full refresh of 100,000 resources takes about 3 seconds, and an incremental refresh that finds nothing changed takes about half a second. See the benchmarks for the method and details.
Documentation
The documentation covers:
- Getting started and concepts
- Guides for every feature, including a production checklist
- Runnable examples, including SQLite and PostgreSQL state stores
- The API reference
- The architecture and its guarantees
Agent Skills
Coding agents such as Claude Code, Codex, Cursor, and GitHub Copilot can use the refresh-engine Agent Skill to integrate the library correctly:
npx skills add https://github.com/smuniharish/refresh-engine/tree/master/refresh-engine-skills/skills/refresh-engine
Contributing
Contributions are welcome. Read the contributing guide, and report vulnerabilities as described in the security policy. Notable changes are listed in the changelog.
License
refresh-engine is licensed under the Apache License 2.0.
Metadata
Release files for refresh-engine 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| refresh_engine-0.2.0.tar.gz | 111.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| refresh_engine-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 172.4 kB
Release files / refresh_engine-0.2.0.tar.gz
| Download URL | refresh_engine-0.2.0.tar.gz |
|---|---|
| Size | 111.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
2f86a2413b2dc0baeed0d98400db2dff38413a0ebc37818c1623fd47595a2447
|
|
BLAKE2b-256 checksum How to use checksums |
f8df4723ff76c57a41c1bac810b07d2811d6ff80e03507927528431599add943
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.15 {"installer":{"name":"uv","version":"0.12.15","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / refresh_engine-0.2.0-py3-none-any.whl
| Download URL | refresh_engine-0.2.0-py3-none-any.whl |
|---|---|
| Size | 60.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
33ac7a438dcacda27b95849276db2c99241c749454b117c1a47fcc665d7ba2ef
|
|
BLAKE2b-256 checksum How to use checksums |
2ce10471192ab8c44c041c2b47832005dcdf6eb0fa0a2e629e682bb64ae883b4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.15 {"installer":{"name":"uv","version":"0.12.15","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|