Metadata-Version: 2.4
Name: lexigram-cache
Version: 0.1.1
Summary: Multi-backend caching system for Lexigram Framework - Redis, Memcached, and in-memory caching
Project-URL: Homepage, https://github.com/lexigram-dev/lexigram
Project-URL: Repository, https://github.com/lexigram-dev/lexigram
Project-URL: Documentation, https://docs.lexigram.dev/cache
Project-URL: Issues, https://github.com/lexigram-dev/lexigram/issues
Project-URL: Changelog, https://github.com/lexigram-dev/lexigram/blob/main/CHANGELOG.md
Author-email: Lexigram Framework Team <team@lexigram.dev>
Maintainer-email: Lexigram Framework Team <team@lexigram.dev>
License: MIT
License-File: LICENSE
Keywords: async,cache,caching,distributed-cache,framework,memcached,redis,ttl
Classifier: Development Status :: 4 - Beta
Classifier: Framework :: AsyncIO
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Database
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
Classifier: Typing :: Typed
Requires-Python: >=3.11
Requires-Dist: lexigram-contracts>=0.1.0
Requires-Dist: lexigram>=0.1.1
Requires-Dist: orjson>=3.9.0
Requires-Dist: typing-extensions>=4.0.0
Provides-Extra: all
Requires-Dist: faiss-cpu>=1.7.0; extra == 'all'
Requires-Dist: lexigram-testing>=0.1.1; extra == 'all'
Requires-Dist: numpy>=1.24.0; extra == 'all'
Requires-Dist: pymemcache>=4.0.0; extra == 'all'
Requires-Dist: pytest-asyncio>=0.21.0; extra == 'all'
Requires-Dist: pytest-cov>=4.0.0; extra == 'all'
Requires-Dist: pytest-mock>=3.10.0; extra == 'all'
Requires-Dist: pytest>=8.0.0; extra == 'all'
Requires-Dist: redis>=5.0.0; extra == 'all'
Provides-Extra: dev
Requires-Dist: black>=23.0.0; extra == 'dev'
Requires-Dist: mypy>=1.0.0; extra == 'dev'
Requires-Dist: ruff>=0.1.0; extra == 'dev'
Requires-Dist: types-redis>=4.6.0; extra == 'dev'
Provides-Extra: memcached
Requires-Dist: pymemcache>=4.0.0; extra == 'memcached'
Provides-Extra: redis
Requires-Dist: redis>=5.0.0; extra == 'redis'
Provides-Extra: semantic
Requires-Dist: faiss-cpu>=1.7.0; extra == 'semantic'
Requires-Dist: numpy>=1.24.0; extra == 'semantic'
Provides-Extra: test
Requires-Dist: lexigram-testing>=0.1.1; extra == 'test'
Requires-Dist: pytest-asyncio>=0.21.0; extra == 'test'
Requires-Dist: pytest-cov>=4.0.0; extra == 'test'
Requires-Dist: pytest-mock>=3.10.0; extra == 'test'
Requires-Dist: pytest>=8.0.0; extra == 'test'
Description-Content-Type: text/markdown

# lexigram-cache

Multi-backend caching system for Lexigram Framework — Redis, Memcached, and in-memory caching.

---

## Overview

Multi-backend async caching for the Lexigram Framework. Supports Redis, in-memory,
and Memcached backends with stampede protection, circuit breaker, `Result`-aware
caching, and domain model round-trip serialization.

Configure backends via `CacheModule.configure()` and inject `CacheBackendProtocol`
into any service. The `@cacheable` decorator provides cache-aside logic with
automatic key generation and Result support.

## Install

```bash
uv add lexigram-cache
# Optional extras
uv add "lexigram-cache[redis,memcached,semantic]"
```

## Quick Start

```python
from lexigram import Application
from lexigram.di.module import Module, module
from lexigram.cache import CacheModule, CacheConfig
from lexigram.cache.config import CacheBackendConfig
from lexigram.cache.types import BackendType
from lexigram.contracts.infra.cache import CacheBackendProtocol

@module(imports=[
    CacheModule.configure(
        CacheConfig(
            backends=[
                CacheBackendConfig(
                    name="default",
                    type=BackendType.REDIS,
                    default=True,
                    redis_url="redis://localhost:6379/0",
                )
            ]
        )
    )
])
class AppModule(Module):
    pass

async def main():
    async with Application.boot(modules=[AppModule]) as app:
        cache = await app.container.resolve(CacheBackendProtocol)
        await cache.set("greeting", "hello", ttl=60)
        value = await cache.get("greeting")
        print(value)  # "hello"

if __name__ == "__main__":
    import asyncio
    asyncio.run(main())
```

## Configuration

> **Zero-config usage:** Call `CacheModule.configure()` with no arguments to use defaults (in-memory backend).

### Option 1 — YAML file

```yaml
# application.yaml
cache:
  enabled: true
  backends:
    - name: redis
      type: redis
      default: true
      redis_url: "redis://localhost:6379/0"
      default_ttl: 300
  service:
    enable_protection: true
```

### Option 2 — Profiles + Environment Variables *(recommended)*

```bash
export LEX_CACHE__ENABLED=true
export LEX_CACHE__BACKENDS__0__TYPE=redis
export LEX_CACHE__BACKENDS__0__REDIS_URL=redis://localhost:6379/0
```

### Option 3 — Python

```python
from lexigram.cache import CacheModule, CacheConfig
from lexigram.cache.config import CacheBackendConfig
from lexigram.cache.types import BackendType

CacheModule.configure(
    CacheConfig(
        backends=[
            CacheBackendConfig(
                name="redis",
                type=BackendType.REDIS,
                default=True,
                redis_url="redis://localhost:6379/0",
            )
        ]
    )
)
```

### Config reference

| Field | Default | Env var | Description |
|-------|---------|---------|-------------|
| `enabled` | `True` | `LEX_CACHE__ENABLED` | Enable the cache module |
| `backends[].name` | — | `LEX_CACHE__BACKENDS__0__NAME` | Unique backend name |
| `backends[].type` | — | `LEX_CACHE__BACKENDS__0__TYPE` | Backend type: `redis`, `memory`, `memcached` |
| `backends[].redis_url` | — | `LEX_CACHE__BACKENDS__0__REDIS_URL` | Redis connection URL |
| `backends[].default_ttl` | `null` | `LEX_CACHE__BACKENDS__0__DEFAULT_TTL` | Default TTL in seconds |
| `service.enable_protection` | `True` | `LEX_CACHE__SERVICE__ENABLE_PROTECTION` | Stampede protection |
| `service.circuit_breaker_enabled` | `False` | `LEX_CACHE__SERVICE__CIRCUIT_BREAKER_ENABLED` | Circuit breaker on backend failures |

## Module Factory Methods

| Method | Description |
|--------|-------------|
| `CacheModule.configure(...)` | Configure with explicit CacheConfig |
| `CacheModule.stub()` | In-memory backend for unit testing |

## Key Features

- **Multi-backend** — Redis, in-memory, and Memcached with a unified protocol
- **Stampede protection** — Distributed lock on cold reads prevents thundering herd
- **Circuit breaker** — Opens on repeated backend failures, falls through to origin
- **`@cacheable` decorator** — Cache-aside with automatic key generation and Result support
- **Domain model serialization** — Type-tagged JSON envelope preserves type identity round-trip
- **Production security** — Blocks insecure Redis passwords when LEX_ENV=production

## Testing

```python
async with Application.boot(modules=[CacheModule.stub()]) as app:
    cache = await app.container.resolve(CacheBackendProtocol)
    await cache.set("key", "value", ttl=60)
    assert await cache.get("key") == "value"
```

## Key Source Files

| File | What it contains |
|------|-----------------|
| `src/lexigram/cache/module.py` | CacheModule with configure() and stub() |
| `src/lexigram/cache/config.py` | CacheConfig, CacheBackendConfig, CacheServiceConfig |
| `src/lexigram/cache/di/provider.py` | CacheProvider boot and registration |
| `src/lexigram/cache/decorators.py` | @cacheable decorator |
| `src/lexigram/cache/backends/` | Redis, in-memory, and Memcached implementations |
