Metadata-Version: 2.4
Name: pytracked
Version: 0.1.2
Summary: Reactive memoization with dependency-driven invalidation
Author: Andreas Bexell
License: BSD-3-Clause
Project-URL: Documentation, https://git.cs.lth.se/an8662be/pastadd
Project-URL: Repository, https://git.cs.lth.se/an8662be/pastadd
Requires-Python: >=3.13
Description-Content-Type: text/markdown
License-File: LICENSE
Dynamic: license-file

# pytracked

Reactive memoization with dependency-driven invalidation for Python.

Like `functools.cache`, but with automatic invalidation: when an input changes,
all computed values that depend on it are flushed and lazily recomputed on next access.

## Features

- **`tracked`** — memoized attribute with automatic dependency recording
- **`token`** — mutable input; writing a new value flushes all dependents transitively
- **`circular`** — fixed-point iteration for mutually recursive computations
- **`flush`** — manually invalidate cached values
- **`observe`** — trace evaluation order and profile cache efficiency

## Quick start

```python
from pytracked import tracked, token

class Spreadsheet:
    a = token(1)
    b = token(2)
    total = tracked(lambda self: self.a + self.b)
    label = tracked(lambda self: f"Total: {self.total}")

s = Spreadsheet()
s.total    # → 3 (computed, cached)
s.total    # → 3 (cache hit)
s.a = 10   # flushes total and label transitively
s.total    # → 12 (recomputed)
s.label    # → "Total: 12" (recomputed)
```

## Fixed-point computation

```python
from pytracked import tracked, circular

class Grammar:
    # Compute nullable set iteratively until stable
    nullable = circular(lambda self: self._compute_nullable(), bottom=frozenset)

    def _compute_nullable(self):
        result = set()
        for rule in self.rules:
            if all(sym in self.nullable for sym in rule.rhs):
                result.add(rule.lhs)
        return frozenset(result)
```

## How it works

During evaluation of a `tracked` attribute, any access to another `tracked` or
`token` attribute is recorded as a dependency edge. When a `token` is written,
the dependency graph is walked transitively to flush all stale cached values.
Re-evaluation happens lazily on next access.

This implements the algorithm from:
> Söderberg & Hedin, "Incremental Evaluation of Reference Attribute Grammars
> using Dynamic Dependency Tracking", LU-CS-TR:2012-249.
