Metadata-Version: 2.4
Name: contextually-cached-property
Version: 0.1.1
Summary: A cached property decorator with context-aware isolation
License-File: LICENSE
Author: Quentin Boudinel
Author-email: quentin.boudinel@digitalescapade.com
Requires-Python: >=3.13
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Description-Content-Type: text/markdown

# contextually-cached-property

A Python descriptor that caches property values per context variable, providing isolated caches for concurrent contexts like asyncio tasks.

## Why?

Python's built-in `functools.cached_property` uses a single cache shared across all contexts. This can cause issues in concurrent applications where different tasks or threads need independent cached values.

`contextually_cached_property` solves this by using `contextvars.ContextVar` to maintain separate caches per context, while also using weak references to allow proper garbage collection of instances.

## Installation

```bash
pip install contextually-cached-property
```

Requires Python 3.14+.

## Usage

```python
from contextually_cached_property import contextually_cached_property


class ExpensiveResource:
    @contextually_cached_property
    def connection(self) -> Connection:
        return create_connection()
```

The cached value is computed once per context per instance. Different asyncio tasks will each get their own cached value:

```python
import asyncio


class TaskLocalData:
    @contextually_cached_property
    def request_id(self) -> str:
        return generate_unique_id()


data = TaskLocalData()


async def task_a():
    print(data.request_id)  # e.g., "abc123"


async def task_b():
    print(data.request_id)  # e.g., "xyz789" (different from task_a)


async def main():
    await asyncio.gather(task_a(), task_b())
```

### Invalidating the cache

Delete the attribute to clear the cached value for the current context:

```python
del instance.cached_property_name
```

## Development

This project uses [Poetry](https://python-poetry.org/) for dependency management.

```bash
# Install dependencies
poetry install

# Run tests
pytest

# Run linting
ruff check

# Run type checking
mypy .
```

## Releasing

This project uses git tags for versioning and automated publishing via Forgejo Actions.

### Pre-release (TestPyPI)

Create a pre-release tag to publish to TestPyPI for testing:

```bash
git tag v1.0.0-rc1
git push origin v1.0.0-rc1
```

### Production release (PyPI)

Create a release tag to publish to PyPI:

```bash
git tag v1.0.0
git push origin v1.0.0
```

The workflow runs tests, linting, and type checking before publishing.

## License

See [LICENSE](LICENSE) for details.

