Skip to content

Commit 1a11a47

Browse files
committed
agents.md
1 parent 3eeee8c commit 1a11a47

1 file changed

Lines changed: 29 additions & 54 deletions

File tree

AGENTS.md

Lines changed: 29 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,21 @@ gcache is a fine-grained caching library with multi-layer support (local + Redis
88

99
```
1010
src/gcache/
11-
├── __init__.py # Public API exports
12-
├── base.py # Core implementation (~1000 lines)
13-
└── event_loop_thread.py # Sync/async bridge for threading
11+
├── __init__.py # Public API exports
12+
├── config.py # GCacheKey, GCacheKeyConfig, GCacheConfig, RedisConfig, Serializer
13+
├── exceptions.py # All exception classes
14+
├── gcache.py # GCache main class and @cached decorator
15+
└── _internal/ # Implementation details (not public API)
16+
├── constants.py # Named constants (cache sizes, TTLs, thresholds)
17+
├── event_loop_thread.py # EventLoopThread, EventLoopThreadPool
18+
├── local_cache.py # LocalCache (TTLCache-based)
19+
├── metrics.py # GCacheMetrics (Prometheus)
20+
├── redis_cache.py # RedisCache
21+
└── wrappers.py # CacheController, CacheChain
22+
1423
tests/
15-
├── conftest.py # Fixtures (redis_server, gcache, cache_config_provider)
16-
├── test_gcache.py # Main test suite
17-
└── test_*.py # Specialized tests
24+
├── conftest.py # Fixtures (redis_server, gcache, cache_config_provider)
25+
└── test_*.py # Test suites
1826
```
1927

2028
## Key Components
@@ -23,73 +31,40 @@ tests/
2331
- **CacheLayer**: Enum - `NOOP`, `LOCAL` (TTLCache), `REMOTE` (Redis)
2432
- **CacheChain**: Chains local → redis with read-through strategy
2533
- **EventLoopThreadPool**: Runs async code from sync cached functions (16 threads)
26-
- **GCacheKey**: URN-formatted cache keys with invalidation tracking
27-
28-
## Core Pattern: @cached Decorator
29-
30-
```python
31-
@gcache.cached(
32-
key_type="user_id", # Entity type
33-
id_arg="user_id", # Arg name for cache key
34-
use_case="GetUser", # Unique identifier
35-
arg_adapters={"request": lambda r: r.id}, # Complex arg → string
36-
ignore_args=["logger"], # Args not in cache key
37-
)
38-
async def get_user(user_id: str, request: Request, logger: Logger) -> User:
39-
...
40-
```
34+
- **GCacheKey**: Frozen dataclass for cache keys (key_type, id, use_case, args)
4135

4236
## Critical Patterns
4337

44-
1. **Context-based enable**: Cache is disabled by default
45-
```python
46-
with gcache.enable():
47-
result = cached_func() # Actually uses cache
48-
```
38+
1. **Context-based enable**: Cache is disabled by default - use `with gcache.enable():`
4939

50-
2. **Sync functions use thread pool**: Sync `@cached` functions run through `EventLoopThreadPool` to avoid blocking
40+
2. **Sync functions use thread pool**: Sync `@cached` functions run through `EventLoopThreadPool`
5141

52-
3. **No reentrant sync calls**: Sync cached function calling another sync cached function raises `ReentrantSyncFunctionDetected` - convert to async
42+
3. **Don't call sync cached functions from async**: Blocks event loop (logs warning)
5343

54-
4. **Thread-local Redis clients**: `RedisCache` stores client per-thread via `threading.local()`
44+
4. **No reentrant sync calls**: Raises `ReentrantSyncFunctionDetected` - convert to async
5545

56-
5. **Watermark invalidation**: Uses timestamps to invalidate without deleting keys
46+
5. **Thread-local Redis clients**: `RedisCache` stores client per-thread via `threading.local()`
5747

5848
## Code Conventions
5949

60-
- **Type hints required**: `mypy --disallow_untyped_defs`
61-
- **Line length**: 120 chars
62-
- **Linting**: ruff (E4, E7, E9, F, I, UP, ASYNC)
63-
- **Docstrings**: Numpy style
64-
- **Python**: 3.10+ (uses `|` union syntax)
50+
- Type hints required, line length 120, ruff + mypy
51+
- Python 3.10+ (uses `|` union syntax)
6552

6653
## Testing
6754

68-
- pytest + pytest-asyncio
69-
- `redislite` for in-memory Redis
70-
- Always test both sync and async paths
71-
- Key fixtures: `gcache`, `redis_server`, `cache_config_provider`
72-
73-
Run tests:
7455
```bash
75-
pytest tests/
56+
poetry run pytest tests/
7657
```
7758

78-
## When Modifying
79-
80-
1. **Update both sync/async paths** - changes typically affect both
81-
2. **Preserve context variables** - critical for `gcache.enable()` across threads
82-
3. **Test both redis_config and redis_client_factory paths**
83-
4. **Add Prometheus metrics** for new cache behaviors
84-
5. **Run pre-commit** - ruff format, mypy, poetry-check
85-
8659
## Common Gotchas
8760

8861
- GCache is singleton - second instantiation raises `GCacheAlreadyInstantiated`
89-
- "watermark" is a reserved use_case name
90-
- Local cache cannot be invalidated (TTL-only expiration)
91-
- Large payloads (>50KB) serialize in executor to avoid blocking
62+
- "watermark" is reserved use_case name
63+
- Local cache cannot be invalidated across instances (TTL-only)
64+
- `WATERMARK_TTL_SECONDS` (4 hours) must exceed your longest cache TTL for invalidation to work
65+
- uvloop is optional - falls back to asyncio on Windows/PyPy
9266

9367
## Dependencies
9468

95-
Core: pydantic, prometheus-client, cachetools, redis, uvloop
69+
Core: pydantic, prometheus-client, cachetools, redis
70+
Optional: uvloop

0 commit comments

Comments
 (0)