A correctness-first incremental query engine for Python.
Project description
pyinc
pip install pyinc
pyinc is a pull-based incremental query kernel for Python. It memoizes the
results of decorated functions, captures their dependencies as they execute,
and on the next request re-runs only the work whose declared inputs actually
changed. The design space is the one occupied by Salsa, Jane Street
Incremental, and Bazel/Skyframe, adapted to the constraints of Python:
mutable defaults, hidden ambient I/O, and pervasive object identity.
The library is pure-Python and stdlib-only, with zero runtime dependencies.
What pyinc is for
Programs in Python that cache derived results usually invalidate by hand: a watcher that clears a dict, a hash compared at the start of a function, a "stale?" boolean updated at known points. Every shortcut of that shape has a failure mode in which a real input changed but the cache did not notice, and the program silently uses a stale value.
pyinc exists so that class of problem can be solved without the caller
having to reason about invalidation. The user declares base inputs, declares
resources for external state (files, environment variables, directories),
and writes queries as ordinary Python functions decorated with @query. The
runtime captures the dependency graph as queries execute, stores frozen
snapshots of every value crossing a cached boundary, and re-validates
dependencies top-down on each request. Anything whose recorded dependencies
are unchanged is reused. Anything whose recomputation produces a
semantically equal result is backdated, so downstream consumers stay valid
without re-running.
Quick example
from pyinc import Database, FileResource, query
_FILES = FileResource()
@query
def read_config(db, path):
return _FILES.read(db, path) # tracked file read
@query
def parse_names(db, path):
text = read_config(db, path)
return [line.strip() for line in text.splitlines() if line.strip()]
db = Database(mode="strict")
result = db.get(parse_names, "/tmp/names.txt") # computes from scratch
result = db.get(parse_names, "/tmp/names.txt") # reuses memo — file unchanged
# Edit the file: only affected queries re-execute.
# Comment-only edits can be backdated (early cutoff) with a cutoff= function.
# Raw open() inside a query raises UntrackedReadError.
# In strict mode, returned values are frozen — mutation raises TypeError.
See examples/correctness_demo.py for a walkthrough of backdating, mutation
protection, untracked-read enforcement, and provenance inspection. The
examples/ directory also contains focused scripts for the push-observer
and artifact-store APIs, the mutable-graph boundary, and several shipped
integrations.
What pyinc guarantees
pyinc guarantees from-scratch consistency: the result of incremental
evaluation matches a fresh evaluation on the same declared inputs and
resources. The guarantee holds when, and only when, three conditions hold:
- Value boundary ownership — every value crossing a cached boundary is
snapshot-safe: an immutable scalar, a tuple, a container that
freezecan deep-convert (list→tuple,dict→FrozenDict,set→frozenset, dataclass →FrozenRecord), or a value handled by a registeredValueAdapter. Mutable graphs with shared identity or cycles are supported viaFrozenGraph/FrozenRefand round-trip through the boundary without losing aliasing. - Tracked ambient reads — every read of external state inside a query
goes through a
Resource(or is explicitly declared viadb.report_untracked_read(reason)). The runtime interceptsbuiltins.open,io.open,os.getenv,os.environ,os.listdir,os.scandir, andPath.iterdirwhile a query is executing, and raisesUntrackedReadErroron uses that escape this protocol. - Deterministic queries — given the same tracked dependencies, a query returns a semantically equal value. Mutable closure or global captures in a query definition are rejected at decoration time so memo reuse cannot silently depend on hidden Python object mutation.
The full contract — the soundness envelope, the three execution modes, the explicit out-of-scope cases, and the documented escape hatches — is in docs/kernel-contract.md.
Kernel surface
@queryfor derived values,Inputfor explicit base leaves, optionaleq=/cutoff=policies for custom equivalence and backdating, andValueAdapterfor custom snapshot-safe boundary types.FileResource,FileStatResource,EnvResource, andDirectoryResourcefor tracked external reads.- Pull-based recomputation with revisions, dependency capture, red-green verification, and backdating (early cutoff) on semantic equality.
strict,checked, andfastexecution modes with explicit boundary semantics.- Bounded query memoization via
Database(max_query_nodes=...)(LRU at top-level request boundaries; inputs and resources stay resident). - Atomic batch invalidation via
Database.set_many(...)(single revision bump for any number of input updates). Database.dependency_graph()for a machine-readable export of all nodes and edges;Database.inspect(...)andDatabase.explain(...)for per-node provenance, observational and human-readable respectively;Database.statistics()andDatabase.query_profile()for aggregate counters and per-query timing.Database.observe(callback, query, *args, **kwargs)for push observers on query nodes. Events are delivered asQueryChangeEventafter the outermost request scope completes (so callbacks may safely re-enter the database) and fire only onexecuteddecisions;reusedandbackdateddecisions do not fire because the stored value did not move.- Mutable object graphs across cached boundaries via
FrozenGraphandFrozenRefsnapshot variants.freezememoizes mutable containers by id;thawreconstructs identity faithfully via two-pass allocate-then-fill so a list-with-itself round-trips to an actual self-referential list. Pure trees pay no overhead and retain the flat snapshot shape. - Content-addressed artifact storage via the
ArtifactStoreProtocol, withInMemoryArtifactStoreandFileSystemArtifactStoreimplementations. PassingDatabase(store=...)writes the serialized snapshot bytes for every value crossing the membrane, keyed by itsfingerprint_snapshotdigest.serialize_snapshotanddeserialize_snapshotexpose the byte form to external callers and round-trip the full snapshot grammar. Databaseis thread-safe for concurrent use across instances and on a single shared instance. The ambient-read guard is installed once globally and dispatches per-context, so threads inside queries on different databases do not interfere with each other's enforcement.
Integrations
pyinc.integrations ships narrow, stdlib-only integrations that compose at
the query layer. The kernel tracks cross-integration calls as ordinary
dependency edges; no extra wiring is required.
python_source— workspace-local module discovery, top-level imports and definitions, simple assignment tracking for export surfaces, and conservative import resolution withworkspace/stdlib/installed/missing/ambiguousoutcomes (stdlib and installed classification via composition withinstalled_packages; installed imports'resolved_pathpopulated viadeep_module_resolution).toml_config— single-file TOML inspection: section and key extraction, dependency and optional-dependency discovery, and tool-config discovery.requirements_txt— requirements parsing: normalized requirement specs, file references, index directives, editable installs, URL requirements, anddeep_requirements_analysisfor recursive-r/--requirementfile following with cycle detection.installed_packages— installed package discovery viaimportlib.metadata-compatible.dist-infodirectories, stdlib module identification viasys.stdlib_module_names, and import-name resolution (stdlib/installed/unknown).deep_module_resolution—sys.pathwalking,.pthfile processing with backdating on whitespace and comment-only edits, PEP 420 namespace package collection, and dotted-name → file resolution. Exposesresolve_module_pathanddeep_module_resolution_analysis.json_config— single-file JSON inspection: section and key extraction with type detection, nested-object traversal, and parse-error diagnostics.dependency_check— cross-integration dependency validation: composesinstalled_packagesandpython_sourceto detect undeclared imports and missing or version-mismatched packages.env_file—.envfile parsing: key/value extraction with quoted and unquoted values,exportprefix handling, and interpolation-reference detection.xml_config— XML inspection viaxml.etree.ElementTree: element and attribute extraction, dot-path traversal, and namespace-aware tag normalization.csv_data— CSV/TSV structural analysis via stdlibcsv: header detection, column discovery, delimiter sniffing, row counting, and inconsistent-column diagnostics.requirement_evaluation— PEP 440 version-specifier satisfaction and PEP 508 environment-marker evaluation; composes withrequirements_txtandinstalled_packagesto surface the effective applicable and satisfied requirement set for the current Python environment.symbol_resolution— workspace-wide symbol tables (module-level and class-level), cross-module re-export following with cycle detection, type-annotation text extraction viaast.unparse(no type evaluation), and a workspace-wide reverse-reference index for a given qualified name.notebook— Jupyter.ipynbanalysis via stdlibjson: per-cell type, source, and heading extraction; per-cell AST imports and definitions for code cells; and cutoff-based backdating that ignoresoutputsandexecution_count, so re-running cells does not invalidate downstream consumers.
pyinc.integrations re-exports only the stable dataclass and result types
and the high-level entrypoints. Low-level payload queries, decode helpers,
and resource helpers remain experimental in their defining submodules.
Verification
- The kernel contract is summarized in docs/kernel-contract.md.
- The repository includes dedicated test modules for value semantics, runtime behavior, provenance and explanation formatting, property-based from-scratch consistency checks, and each shipped integration.
- The integration suites exercise
strict,checked, andfastmodes and compare incremental results against fresh recomputation over edit sequences.
The integration boundary is summarized in docs/integration-contract.md.
Diagnostics and escape hatches
Database.inspect(...)is observational: it returns the last recorded provenance tree for a query key without forcing a fresh revalidation pass.Database.inspect_fresh(...)runs verification first and then returns the tree. Seeexamples/inspect_fresh_demo.py.- Query identity includes the function-definition payload. Captured
ambient values contribute to the query fingerprint; mutable closure or
global captures are rejected. Run
pyinc.explain_query_captures(fn)before the firstdb.get(...)to see how each capture will be classified. Seeexamples/capture_diagnostics.py. Database.report_untracked_read(reason)is an explicit impurity escape hatch. It marks the current query as always re-executing and disables backdating for that node, which is the right trade-off when a dependency is real but not resource-trackable. Seeexamples/untracked_escape_hatch.py.- The package ships inline typing metadata via
py.typed.
Consumer tooling
LSP wiring and push-based filesystem watchers are deliberately out of scope
for the kernel package. They live in a separate consumer layer,
pyinc_tools, that builds only on the stable pyinc.integrations public
surface:
pyinc-tools analyze <root>runs one-shot or threaded--watchworkspace analysis via a polling watcher.pyinc-tools lspruns a stdio LSP server with document and workspace symbols, diagnostics, hover, goto-definition, and find-references, all backed bypyinc.integrations.symbol_resolution. The server starts a threaded filesystem watcher by default so external edits (e.g.git pull, formatter scripts) publish fresh diagnostics even when the editor does not emitworkspace/didChangeWatchedFiles.
See docs/pyinc-tools-guide.md for install, editor wiring, the overlay model, and the supported-vs.-not-yet feature table.
Development
git clone https://github.com/Brumbelow/pyinc.git && cd pyinc
python3 -m venv .venv
. .venv/bin/activate
python3 -m pip install -e '.[dev]'
pytest -q
python3 -m mypy src tests
python3 -m ruff check src tests
The kernel contract is summarized in docs/kernel-contract.md. Integration API boundaries are summarized in docs/integration-contract.md. A guide to authoring new integrations is at docs/integration-authoring.md.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pyinc-2.0.0.tar.gz.
File metadata
- Download URL: pyinc-2.0.0.tar.gz
- Upload date:
- Size: 186.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7e6e98446207189a6b79857291f1abd76291e9983be6df0df9466a196c642160
|
|
| MD5 |
1d93387f92e418063bb84fba276e9374
|
|
| BLAKE2b-256 |
774d0cab51db0a6da59e1b645af26ea5b2d7465136b095f5309ac7cd67f43df6
|
Provenance
The following attestation bundles were made for pyinc-2.0.0.tar.gz:
Publisher:
release.yml on Brumbelow/pyinc
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pyinc-2.0.0.tar.gz -
Subject digest:
7e6e98446207189a6b79857291f1abd76291e9983be6df0df9466a196c642160 - Sigstore transparency entry: 1383549638
- Sigstore integration time:
-
Permalink:
Brumbelow/pyinc@9d1312a8ffa3b12d2dca252011646a08ccbfc6cf -
Branch / Tag:
refs/tags/v2.0.0 - Owner: https://github.com/Brumbelow
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@9d1312a8ffa3b12d2dca252011646a08ccbfc6cf -
Trigger Event:
push
-
Statement type:
File details
Details for the file pyinc-2.0.0-py3-none-any.whl.
File metadata
- Download URL: pyinc-2.0.0-py3-none-any.whl
- Upload date:
- Size: 101.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7705bf13fc3c347db5bbb4a5b7b850af6f81d0a347dc258c74d1d86b48431128
|
|
| MD5 |
034d21cbf3a0e3e91ab8b5d78e3537ea
|
|
| BLAKE2b-256 |
950129577b08efd63f61b31f93ed3ddb41a3afa28e46947af9b077aa0461a21b
|
Provenance
The following attestation bundles were made for pyinc-2.0.0-py3-none-any.whl:
Publisher:
release.yml on Brumbelow/pyinc
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pyinc-2.0.0-py3-none-any.whl -
Subject digest:
7705bf13fc3c347db5bbb4a5b7b850af6f81d0a347dc258c74d1d86b48431128 - Sigstore transparency entry: 1383549651
- Sigstore integration time:
-
Permalink:
Brumbelow/pyinc@9d1312a8ffa3b12d2dca252011646a08ccbfc6cf -
Branch / Tag:
refs/tags/v2.0.0 - Owner: https://github.com/Brumbelow
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@9d1312a8ffa3b12d2dca252011646a08ccbfc6cf -
Trigger Event:
push
-
Statement type: