query-cascade 0.2.3__tar.gz → 0.2.4__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 (38) hide show
  1. query_cascade-0.2.4/PKG-INFO +148 -0
  2. query_cascade-0.2.4/README.md +135 -0
  3. {query_cascade-0.2.3 → query_cascade-0.2.4}/pyproject.toml +1 -1
  4. query_cascade-0.2.4/src/query_cascade.egg-info/PKG-INFO +148 -0
  5. query_cascade-0.2.3/PKG-INFO +0 -633
  6. query_cascade-0.2.3/README.md +0 -620
  7. query_cascade-0.2.3/src/query_cascade.egg-info/PKG-INFO +0 -633
  8. {query_cascade-0.2.3 → query_cascade-0.2.4}/setup.cfg +0 -0
  9. {query_cascade-0.2.3 → query_cascade-0.2.4}/src/cascade/__init__.py +0 -0
  10. {query_cascade-0.2.3 → query_cascade-0.2.4}/src/cascade/_errors.py +0 -0
  11. {query_cascade-0.2.3 → query_cascade-0.2.4}/src/cascade/_evaluator.py +0 -0
  12. {query_cascade-0.2.3 → query_cascade-0.2.4}/src/cascade/_graph_export.py +0 -0
  13. {query_cascade-0.2.3 → query_cascade-0.2.4}/src/cascade/_persistence.py +0 -0
  14. {query_cascade-0.2.3 → query_cascade-0.2.4}/src/cascade/_runtime.py +0 -0
  15. {query_cascade-0.2.3 → query_cascade-0.2.4}/src/cascade/_scheduler.py +0 -0
  16. {query_cascade-0.2.3 → query_cascade-0.2.4}/src/cascade/_serde.py +0 -0
  17. {query_cascade-0.2.3 → query_cascade-0.2.4}/src/cascade/_state.py +0 -0
  18. {query_cascade-0.2.3 → query_cascade-0.2.4}/src/cascade/_store.py +0 -0
  19. {query_cascade-0.2.3 → query_cascade-0.2.4}/src/cascade/_synthetic_graph.py +0 -0
  20. {query_cascade-0.2.3 → query_cascade-0.2.4}/src/cascade/engine.py +0 -0
  21. {query_cascade-0.2.3 → query_cascade-0.2.4}/src/query_cascade.egg-info/SOURCES.txt +0 -0
  22. {query_cascade-0.2.3 → query_cascade-0.2.4}/src/query_cascade.egg-info/dependency_links.txt +0 -0
  23. {query_cascade-0.2.3 → query_cascade-0.2.4}/src/query_cascade.egg-info/requires.txt +0 -0
  24. {query_cascade-0.2.3 → query_cascade-0.2.4}/src/query_cascade.egg-info/top_level.txt +0 -0
  25. {query_cascade-0.2.3 → query_cascade-0.2.4}/tests/test_dependency_parallelism.py +0 -0
  26. {query_cascade-0.2.3 → query_cascade-0.2.4}/tests/test_engine.py +0 -0
  27. {query_cascade-0.2.3 → query_cascade-0.2.4}/tests/test_engine_compute_many.py +0 -0
  28. {query_cascade-0.2.3 → query_cascade-0.2.4}/tests/test_engine_concurrency.py +0 -0
  29. {query_cascade-0.2.3 → query_cascade-0.2.4}/tests/test_engine_persistence_and_tracing.py +0 -0
  30. {query_cascade-0.2.3 → query_cascade-0.2.4}/tests/test_evaluator_and_synthetic_coverage.py +0 -0
  31. {query_cascade-0.2.3 → query_cascade-0.2.4}/tests/test_examples.py +0 -0
  32. {query_cascade-0.2.3 → query_cascade-0.2.4}/tests/test_internal_invariants.py +0 -0
  33. {query_cascade-0.2.3 → query_cascade-0.2.4}/tests/test_performance.py +0 -0
  34. {query_cascade-0.2.3 → query_cascade-0.2.4}/tests/test_repro_touch_memo_keyerror.py +0 -0
  35. {query_cascade-0.2.3 → query_cascade-0.2.4}/tests/test_scale_behavior.py +0 -0
  36. {query_cascade-0.2.3 → query_cascade-0.2.4}/tests/test_serde.py +0 -0
  37. {query_cascade-0.2.3 → query_cascade-0.2.4}/tests/test_stateful_engine_invariants.py +0 -0
  38. {query_cascade-0.2.3 → query_cascade-0.2.4}/tests/test_subgraph_stats_export.py +0 -0
@@ -0,0 +1,148 @@
1
+ Metadata-Version: 2.4
2
+ Name: query-cascade
3
+ Version: 0.2.4
4
+ Summary: Minimal demand-driven query framework for incremental computation.
5
+ Requires-Python: >=3.12
6
+ Description-Content-Type: text/markdown
7
+ Provides-Extra: dev
8
+ Requires-Dist: hypothesis; extra == "dev"
9
+ Requires-Dist: mutmut; extra == "dev"
10
+ Requires-Dist: pytest; extra == "dev"
11
+ Requires-Dist: pytest-cov; extra == "dev"
12
+ Requires-Dist: ruff; extra == "dev"
13
+
14
+ # Cascade Query
15
+
16
+ Cascade Query is a Python library for incremental dependency tracking. It caches function results and re-executes them only when their specific inputs or upstream dependencies change.
17
+
18
+ [![PyPI version](https://img.shields.io/pypi/v/query-cascade?color=3775A9&label=PyPI)](https://pypi.org/project/query-cascade/)
19
+ [![Python versions](https://img.shields.io/pypi/pyversions/query-cascade?color=5A9)](https://pypi.org/project/query-cascade/)
20
+
21
+ ---
22
+
23
+ ## Core Principles
24
+
25
+ 1. **Automatic Caching:** Results are stored. If dependencies are unchanged, the function body does not execute.
26
+ 2. **Dependency Tracking:** Cascade records every `@engine.input` or `@engine.query` accessed during execution.
27
+ 3. **Targeted Updates:** When an input changes, Cascade identifies and invalidates only the affected downstream functions.
28
+ 4. **Early Bail-out:** If a function's output remains identical after its dependencies change, re-computation stops for that branch.
29
+
30
+ ---
31
+
32
+ ## Quickstart
33
+
34
+ ```python
35
+ import time
36
+ from cascade import Engine
37
+
38
+ engine = Engine()
39
+
40
+ @engine.input
41
+ def user_id():
42
+ return "user_1"
43
+
44
+ @engine.query
45
+ def fetch_data():
46
+ time.sleep(2)
47
+ return {"id": user_id(), "data": "value"}
48
+
49
+ @engine.query
50
+ def get_result():
51
+ data = fetch_data()
52
+ return f"Result for {data['id']}"
53
+
54
+ # First run: Executes for 2 seconds.
55
+ print(get_result())
56
+
57
+ # Second run: Returns immediately from cache.
58
+ print(get_result())
59
+
60
+ # Update input:
61
+ user_id.set("user_2")
62
+
63
+ # Third run: Executes for 2 seconds to refresh.
64
+ print(get_result())
65
+ ```
66
+
67
+ ---
68
+
69
+ ## Engine API
70
+
71
+ ### Core Methods
72
+ * **`Engine(max_entries=10000, stats=False)`**: Initializes the engine. `max_entries` sets the limit for the Least Recently Used (LRU) cache.
73
+ * **`@engine.input`**: Decorator for mutable data roots.
74
+ * `input.set(value)`: Updates the value and increments the global revision.
75
+ * `input.set(*args, value=value)`: Updates a keyed input.
76
+ * **`@engine.query`**: Decorator for cached computations.
77
+ * **`engine.snapshot()`**: Returns a `Snapshot` object pinning the current global revision. Use `query(snapshot=s)` to read data as it existed at that revision.
78
+ * **`engine.save(path)` / `engine.load(path)`**: Persists all inputs and cached results to a SQLite database.
79
+
80
+ ### Parallel & Background Execution
81
+ * **`engine.compute_many(calls, workers=None)`**: Executes a list of queries in parallel using a thread pool.
82
+ * **`engine.submit(query, *args, executor=None)`**: Schedules a query for background execution. Returns a `concurrent.futures.Future`.
83
+ * **`QueryCancelled`**: Exception raised if a background query's dependencies change before it completes.
84
+
85
+ ### Graph Utilities
86
+ * **`engine.inspect_graph()`**: Returns a dictionary of all nodes and edges in the dependency graph.
87
+ * **`engine.subgraph(roots, direction="deps")`**: Filters the graph to the dependency chain of the specified root nodes.
88
+ * **`engine.prune(roots)`**: Removes cached query results that are not reachable from the specified roots.
89
+
90
+ ---
91
+
92
+ ## Advanced Features
93
+
94
+ ### Side-Effect Accumulators
95
+ Queries must be pure functions. Use `Accumulator` to record side-effects (like logs or warnings) that must be replayed when a result is served from the cache.
96
+
97
+ ```python
98
+ warnings = engine.accumulator("warnings")
99
+
100
+ @engine.query
101
+ def validate_data():
102
+ data = fetch_data()
103
+ if not data:
104
+ warnings.push("No data found")
105
+ return data
106
+
107
+ # On cache hit, 'warnings' are re-populated into the effects dictionary.
108
+ effects = {}
109
+ validate_data(effects=effects)
110
+ print(effects["warnings"])
111
+ ```
112
+
113
+ ### Performance Metrics
114
+ Set `stats=True` in the `Engine` constructor to track execution timing.
115
+ * **`engine.stats_summary()`**: Returns wall-clock time spent in function bodies and cache eviction counts.
116
+ * **`engine.reset_stats()`**: Clears accumulated timing data.
117
+
118
+ ---
119
+
120
+ ## Visualization
121
+ Cascade provides renderers for the dependency graph.
122
+
123
+ ```python
124
+ from cascade import export_dot, export_mermaid
125
+
126
+ graph = engine.inspect_graph()
127
+ # Generate Graphviz DOT format
128
+ dot_text = export_dot(graph)
129
+ # Generate Mermaid flowchart format
130
+ mermaid_text = export_mermaid(graph)
131
+ ```
132
+
133
+ ---
134
+
135
+ ## Limitations
136
+
137
+ 1. **Cycle Detection:** Cascade detects and rejects recursive function calls (cycles) with a `CycleError`.
138
+ 2. **Thread Safety:** While Cascade supports parallel query execution, the `Engine` object itself should be modified (`.set()`, `@engine.query`) from a single thread or with external synchronization.
139
+ 3. **Persistence Security:** `engine.load()` uses JSON to resolve types via `importlib`. Only load databases from trusted sources.
140
+ 4. **Python Version:** Optimization for parallel CPU-bound work requires **CPython 3.14+ free-threaded** builds with `PYTHON_GIL=0`.
141
+
142
+ ---
143
+
144
+ ## Installation
145
+
146
+ ```bash
147
+ pip install query-cascade
148
+ ```
@@ -0,0 +1,135 @@
1
+ # Cascade Query
2
+
3
+ Cascade Query is a Python library for incremental dependency tracking. It caches function results and re-executes them only when their specific inputs or upstream dependencies change.
4
+
5
+ [![PyPI version](https://img.shields.io/pypi/v/query-cascade?color=3775A9&label=PyPI)](https://pypi.org/project/query-cascade/)
6
+ [![Python versions](https://img.shields.io/pypi/pyversions/query-cascade?color=5A9)](https://pypi.org/project/query-cascade/)
7
+
8
+ ---
9
+
10
+ ## Core Principles
11
+
12
+ 1. **Automatic Caching:** Results are stored. If dependencies are unchanged, the function body does not execute.
13
+ 2. **Dependency Tracking:** Cascade records every `@engine.input` or `@engine.query` accessed during execution.
14
+ 3. **Targeted Updates:** When an input changes, Cascade identifies and invalidates only the affected downstream functions.
15
+ 4. **Early Bail-out:** If a function's output remains identical after its dependencies change, re-computation stops for that branch.
16
+
17
+ ---
18
+
19
+ ## Quickstart
20
+
21
+ ```python
22
+ import time
23
+ from cascade import Engine
24
+
25
+ engine = Engine()
26
+
27
+ @engine.input
28
+ def user_id():
29
+ return "user_1"
30
+
31
+ @engine.query
32
+ def fetch_data():
33
+ time.sleep(2)
34
+ return {"id": user_id(), "data": "value"}
35
+
36
+ @engine.query
37
+ def get_result():
38
+ data = fetch_data()
39
+ return f"Result for {data['id']}"
40
+
41
+ # First run: Executes for 2 seconds.
42
+ print(get_result())
43
+
44
+ # Second run: Returns immediately from cache.
45
+ print(get_result())
46
+
47
+ # Update input:
48
+ user_id.set("user_2")
49
+
50
+ # Third run: Executes for 2 seconds to refresh.
51
+ print(get_result())
52
+ ```
53
+
54
+ ---
55
+
56
+ ## Engine API
57
+
58
+ ### Core Methods
59
+ * **`Engine(max_entries=10000, stats=False)`**: Initializes the engine. `max_entries` sets the limit for the Least Recently Used (LRU) cache.
60
+ * **`@engine.input`**: Decorator for mutable data roots.
61
+ * `input.set(value)`: Updates the value and increments the global revision.
62
+ * `input.set(*args, value=value)`: Updates a keyed input.
63
+ * **`@engine.query`**: Decorator for cached computations.
64
+ * **`engine.snapshot()`**: Returns a `Snapshot` object pinning the current global revision. Use `query(snapshot=s)` to read data as it existed at that revision.
65
+ * **`engine.save(path)` / `engine.load(path)`**: Persists all inputs and cached results to a SQLite database.
66
+
67
+ ### Parallel & Background Execution
68
+ * **`engine.compute_many(calls, workers=None)`**: Executes a list of queries in parallel using a thread pool.
69
+ * **`engine.submit(query, *args, executor=None)`**: Schedules a query for background execution. Returns a `concurrent.futures.Future`.
70
+ * **`QueryCancelled`**: Exception raised if a background query's dependencies change before it completes.
71
+
72
+ ### Graph Utilities
73
+ * **`engine.inspect_graph()`**: Returns a dictionary of all nodes and edges in the dependency graph.
74
+ * **`engine.subgraph(roots, direction="deps")`**: Filters the graph to the dependency chain of the specified root nodes.
75
+ * **`engine.prune(roots)`**: Removes cached query results that are not reachable from the specified roots.
76
+
77
+ ---
78
+
79
+ ## Advanced Features
80
+
81
+ ### Side-Effect Accumulators
82
+ Queries must be pure functions. Use `Accumulator` to record side-effects (like logs or warnings) that must be replayed when a result is served from the cache.
83
+
84
+ ```python
85
+ warnings = engine.accumulator("warnings")
86
+
87
+ @engine.query
88
+ def validate_data():
89
+ data = fetch_data()
90
+ if not data:
91
+ warnings.push("No data found")
92
+ return data
93
+
94
+ # On cache hit, 'warnings' are re-populated into the effects dictionary.
95
+ effects = {}
96
+ validate_data(effects=effects)
97
+ print(effects["warnings"])
98
+ ```
99
+
100
+ ### Performance Metrics
101
+ Set `stats=True` in the `Engine` constructor to track execution timing.
102
+ * **`engine.stats_summary()`**: Returns wall-clock time spent in function bodies and cache eviction counts.
103
+ * **`engine.reset_stats()`**: Clears accumulated timing data.
104
+
105
+ ---
106
+
107
+ ## Visualization
108
+ Cascade provides renderers for the dependency graph.
109
+
110
+ ```python
111
+ from cascade import export_dot, export_mermaid
112
+
113
+ graph = engine.inspect_graph()
114
+ # Generate Graphviz DOT format
115
+ dot_text = export_dot(graph)
116
+ # Generate Mermaid flowchart format
117
+ mermaid_text = export_mermaid(graph)
118
+ ```
119
+
120
+ ---
121
+
122
+ ## Limitations
123
+
124
+ 1. **Cycle Detection:** Cascade detects and rejects recursive function calls (cycles) with a `CycleError`.
125
+ 2. **Thread Safety:** While Cascade supports parallel query execution, the `Engine` object itself should be modified (`.set()`, `@engine.query`) from a single thread or with external synchronization.
126
+ 3. **Persistence Security:** `engine.load()` uses JSON to resolve types via `importlib`. Only load databases from trusted sources.
127
+ 4. **Python Version:** Optimization for parallel CPU-bound work requires **CPython 3.14+ free-threaded** builds with `PYTHON_GIL=0`.
128
+
129
+ ---
130
+
131
+ ## Installation
132
+
133
+ ```bash
134
+ pip install query-cascade
135
+ ```
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "query-cascade"
7
- version = "0.2.3"
7
+ version = "0.2.4"
8
8
  description = "Minimal demand-driven query framework for incremental computation."
9
9
  requires-python = ">=3.12"
10
10
  readme = "README.md"
@@ -0,0 +1,148 @@
1
+ Metadata-Version: 2.4
2
+ Name: query-cascade
3
+ Version: 0.2.4
4
+ Summary: Minimal demand-driven query framework for incremental computation.
5
+ Requires-Python: >=3.12
6
+ Description-Content-Type: text/markdown
7
+ Provides-Extra: dev
8
+ Requires-Dist: hypothesis; extra == "dev"
9
+ Requires-Dist: mutmut; extra == "dev"
10
+ Requires-Dist: pytest; extra == "dev"
11
+ Requires-Dist: pytest-cov; extra == "dev"
12
+ Requires-Dist: ruff; extra == "dev"
13
+
14
+ # Cascade Query
15
+
16
+ Cascade Query is a Python library for incremental dependency tracking. It caches function results and re-executes them only when their specific inputs or upstream dependencies change.
17
+
18
+ [![PyPI version](https://img.shields.io/pypi/v/query-cascade?color=3775A9&label=PyPI)](https://pypi.org/project/query-cascade/)
19
+ [![Python versions](https://img.shields.io/pypi/pyversions/query-cascade?color=5A9)](https://pypi.org/project/query-cascade/)
20
+
21
+ ---
22
+
23
+ ## Core Principles
24
+
25
+ 1. **Automatic Caching:** Results are stored. If dependencies are unchanged, the function body does not execute.
26
+ 2. **Dependency Tracking:** Cascade records every `@engine.input` or `@engine.query` accessed during execution.
27
+ 3. **Targeted Updates:** When an input changes, Cascade identifies and invalidates only the affected downstream functions.
28
+ 4. **Early Bail-out:** If a function's output remains identical after its dependencies change, re-computation stops for that branch.
29
+
30
+ ---
31
+
32
+ ## Quickstart
33
+
34
+ ```python
35
+ import time
36
+ from cascade import Engine
37
+
38
+ engine = Engine()
39
+
40
+ @engine.input
41
+ def user_id():
42
+ return "user_1"
43
+
44
+ @engine.query
45
+ def fetch_data():
46
+ time.sleep(2)
47
+ return {"id": user_id(), "data": "value"}
48
+
49
+ @engine.query
50
+ def get_result():
51
+ data = fetch_data()
52
+ return f"Result for {data['id']}"
53
+
54
+ # First run: Executes for 2 seconds.
55
+ print(get_result())
56
+
57
+ # Second run: Returns immediately from cache.
58
+ print(get_result())
59
+
60
+ # Update input:
61
+ user_id.set("user_2")
62
+
63
+ # Third run: Executes for 2 seconds to refresh.
64
+ print(get_result())
65
+ ```
66
+
67
+ ---
68
+
69
+ ## Engine API
70
+
71
+ ### Core Methods
72
+ * **`Engine(max_entries=10000, stats=False)`**: Initializes the engine. `max_entries` sets the limit for the Least Recently Used (LRU) cache.
73
+ * **`@engine.input`**: Decorator for mutable data roots.
74
+ * `input.set(value)`: Updates the value and increments the global revision.
75
+ * `input.set(*args, value=value)`: Updates a keyed input.
76
+ * **`@engine.query`**: Decorator for cached computations.
77
+ * **`engine.snapshot()`**: Returns a `Snapshot` object pinning the current global revision. Use `query(snapshot=s)` to read data as it existed at that revision.
78
+ * **`engine.save(path)` / `engine.load(path)`**: Persists all inputs and cached results to a SQLite database.
79
+
80
+ ### Parallel & Background Execution
81
+ * **`engine.compute_many(calls, workers=None)`**: Executes a list of queries in parallel using a thread pool.
82
+ * **`engine.submit(query, *args, executor=None)`**: Schedules a query for background execution. Returns a `concurrent.futures.Future`.
83
+ * **`QueryCancelled`**: Exception raised if a background query's dependencies change before it completes.
84
+
85
+ ### Graph Utilities
86
+ * **`engine.inspect_graph()`**: Returns a dictionary of all nodes and edges in the dependency graph.
87
+ * **`engine.subgraph(roots, direction="deps")`**: Filters the graph to the dependency chain of the specified root nodes.
88
+ * **`engine.prune(roots)`**: Removes cached query results that are not reachable from the specified roots.
89
+
90
+ ---
91
+
92
+ ## Advanced Features
93
+
94
+ ### Side-Effect Accumulators
95
+ Queries must be pure functions. Use `Accumulator` to record side-effects (like logs or warnings) that must be replayed when a result is served from the cache.
96
+
97
+ ```python
98
+ warnings = engine.accumulator("warnings")
99
+
100
+ @engine.query
101
+ def validate_data():
102
+ data = fetch_data()
103
+ if not data:
104
+ warnings.push("No data found")
105
+ return data
106
+
107
+ # On cache hit, 'warnings' are re-populated into the effects dictionary.
108
+ effects = {}
109
+ validate_data(effects=effects)
110
+ print(effects["warnings"])
111
+ ```
112
+
113
+ ### Performance Metrics
114
+ Set `stats=True` in the `Engine` constructor to track execution timing.
115
+ * **`engine.stats_summary()`**: Returns wall-clock time spent in function bodies and cache eviction counts.
116
+ * **`engine.reset_stats()`**: Clears accumulated timing data.
117
+
118
+ ---
119
+
120
+ ## Visualization
121
+ Cascade provides renderers for the dependency graph.
122
+
123
+ ```python
124
+ from cascade import export_dot, export_mermaid
125
+
126
+ graph = engine.inspect_graph()
127
+ # Generate Graphviz DOT format
128
+ dot_text = export_dot(graph)
129
+ # Generate Mermaid flowchart format
130
+ mermaid_text = export_mermaid(graph)
131
+ ```
132
+
133
+ ---
134
+
135
+ ## Limitations
136
+
137
+ 1. **Cycle Detection:** Cascade detects and rejects recursive function calls (cycles) with a `CycleError`.
138
+ 2. **Thread Safety:** While Cascade supports parallel query execution, the `Engine` object itself should be modified (`.set()`, `@engine.query`) from a single thread or with external synchronization.
139
+ 3. **Persistence Security:** `engine.load()` uses JSON to resolve types via `importlib`. Only load databases from trusted sources.
140
+ 4. **Python Version:** Optimization for parallel CPU-bound work requires **CPython 3.14+ free-threaded** builds with `PYTHON_GIL=0`.
141
+
142
+ ---
143
+
144
+ ## Installation
145
+
146
+ ```bash
147
+ pip install query-cascade
148
+ ```