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.
- query_cascade-0.2.4/PKG-INFO +148 -0
- query_cascade-0.2.4/README.md +135 -0
- {query_cascade-0.2.3 → query_cascade-0.2.4}/pyproject.toml +1 -1
- query_cascade-0.2.4/src/query_cascade.egg-info/PKG-INFO +148 -0
- query_cascade-0.2.3/PKG-INFO +0 -633
- query_cascade-0.2.3/README.md +0 -620
- query_cascade-0.2.3/src/query_cascade.egg-info/PKG-INFO +0 -633
- {query_cascade-0.2.3 → query_cascade-0.2.4}/setup.cfg +0 -0
- {query_cascade-0.2.3 → query_cascade-0.2.4}/src/cascade/__init__.py +0 -0
- {query_cascade-0.2.3 → query_cascade-0.2.4}/src/cascade/_errors.py +0 -0
- {query_cascade-0.2.3 → query_cascade-0.2.4}/src/cascade/_evaluator.py +0 -0
- {query_cascade-0.2.3 → query_cascade-0.2.4}/src/cascade/_graph_export.py +0 -0
- {query_cascade-0.2.3 → query_cascade-0.2.4}/src/cascade/_persistence.py +0 -0
- {query_cascade-0.2.3 → query_cascade-0.2.4}/src/cascade/_runtime.py +0 -0
- {query_cascade-0.2.3 → query_cascade-0.2.4}/src/cascade/_scheduler.py +0 -0
- {query_cascade-0.2.3 → query_cascade-0.2.4}/src/cascade/_serde.py +0 -0
- {query_cascade-0.2.3 → query_cascade-0.2.4}/src/cascade/_state.py +0 -0
- {query_cascade-0.2.3 → query_cascade-0.2.4}/src/cascade/_store.py +0 -0
- {query_cascade-0.2.3 → query_cascade-0.2.4}/src/cascade/_synthetic_graph.py +0 -0
- {query_cascade-0.2.3 → query_cascade-0.2.4}/src/cascade/engine.py +0 -0
- {query_cascade-0.2.3 → query_cascade-0.2.4}/src/query_cascade.egg-info/SOURCES.txt +0 -0
- {query_cascade-0.2.3 → query_cascade-0.2.4}/src/query_cascade.egg-info/dependency_links.txt +0 -0
- {query_cascade-0.2.3 → query_cascade-0.2.4}/src/query_cascade.egg-info/requires.txt +0 -0
- {query_cascade-0.2.3 → query_cascade-0.2.4}/src/query_cascade.egg-info/top_level.txt +0 -0
- {query_cascade-0.2.3 → query_cascade-0.2.4}/tests/test_dependency_parallelism.py +0 -0
- {query_cascade-0.2.3 → query_cascade-0.2.4}/tests/test_engine.py +0 -0
- {query_cascade-0.2.3 → query_cascade-0.2.4}/tests/test_engine_compute_many.py +0 -0
- {query_cascade-0.2.3 → query_cascade-0.2.4}/tests/test_engine_concurrency.py +0 -0
- {query_cascade-0.2.3 → query_cascade-0.2.4}/tests/test_engine_persistence_and_tracing.py +0 -0
- {query_cascade-0.2.3 → query_cascade-0.2.4}/tests/test_evaluator_and_synthetic_coverage.py +0 -0
- {query_cascade-0.2.3 → query_cascade-0.2.4}/tests/test_examples.py +0 -0
- {query_cascade-0.2.3 → query_cascade-0.2.4}/tests/test_internal_invariants.py +0 -0
- {query_cascade-0.2.3 → query_cascade-0.2.4}/tests/test_performance.py +0 -0
- {query_cascade-0.2.3 → query_cascade-0.2.4}/tests/test_repro_touch_memo_keyerror.py +0 -0
- {query_cascade-0.2.3 → query_cascade-0.2.4}/tests/test_scale_behavior.py +0 -0
- {query_cascade-0.2.3 → query_cascade-0.2.4}/tests/test_serde.py +0 -0
- {query_cascade-0.2.3 → query_cascade-0.2.4}/tests/test_stateful_engine_invariants.py +0 -0
- {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
|
+
[](https://pypi.org/project/query-cascade/)
|
|
19
|
+
[](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
|
+
[](https://pypi.org/project/query-cascade/)
|
|
6
|
+
[](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
|
+
```
|
|
@@ -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
|
+
[](https://pypi.org/project/query-cascade/)
|
|
19
|
+
[](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
|
+
```
|