A python lib implementing a cached property decorator with context-aware isolation
Find a file
Quentin Boudinel 2d5d145974
All checks were successful
Publish to PyPI / test (push) Successful in 29s
Publish to PyPI / publish-testpypi (push) Has been skipped
Publish to PyPI / publish-pypi (push) Successful in 25s
ci: use Python 3.13 Alpine image in publish workflow
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 14:31:46 +01:00
.forgejo/workflows ci: use Python 3.13 Alpine image in publish workflow 2026-01-28 14:31:46 +01:00
src/contextually_cached_property fix: lower minimum Python version to 3.13 2026-01-28 14:27:41 +01:00
tests fix: lower minimum Python version to 3.13 2026-01-28 14:27:41 +01:00
.gitignore Initial commit 2026-01-26 15:26:00 +01:00
.pre-commit-config.yaml Initial commit 2026-01-26 15:26:00 +01:00
LICENSE Initial commit 2026-01-26 15:26:00 +01:00
poetry.lock chore: update poetry.lock 2026-01-28 14:31:26 +01:00
poetry.toml Initial commit 2026-01-26 15:26:00 +01:00
pyproject.toml fix: lower minimum Python version to 3.13 2026-01-28 14:27:41 +01:00
README.md ci: add automated PyPI publishing with git tag versioning 2026-01-28 12:10:56 +01:00

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

pip install contextually-cached-property

Requires Python 3.14+.

Usage

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:

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:

del instance.cached_property_name

Development

This project uses Poetry for dependency management.

# 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:

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

Production release (PyPI)

Create a release tag to publish to PyPI:

git tag v1.0.0
git push origin v1.0.0

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

License

See LICENSE for details.