phzyx-cache 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,13 @@
1
+ """Phzyx versioned resource cache."""
2
+
3
+ from phzyx.cache.backends import MemoryCache, RedisCache
4
+ from phzyx.cache.resource import ResourceCache
5
+
6
+ __version__ = "0.1.0"
7
+
8
+ __all__ = [
9
+ "MemoryCache",
10
+ "RedisCache",
11
+ "ResourceCache",
12
+ "__version__",
13
+ ]
@@ -0,0 +1,76 @@
1
+ """Cache backends — memory and optional Redis."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import time
7
+ from dataclasses import dataclass, field
8
+ from typing import Any, Protocol, runtime_checkable
9
+
10
+
11
+ @runtime_checkable
12
+ class CacheBackend(Protocol):
13
+ def get(self, key: str) -> str | None: ...
14
+
15
+ def set(self, key: str, value: str, *, ttl: int | None = None) -> None: ...
16
+
17
+ def delete(self, key: str) -> None: ...
18
+
19
+
20
+ @dataclass
21
+ class MemoryCache:
22
+ """Process-local cache with optional TTL."""
23
+
24
+ _data: dict[str, tuple[str, float | None]] = field(default_factory=dict)
25
+
26
+ def get(self, key: str) -> str | None:
27
+ item = self._data.get(key)
28
+ if item is None:
29
+ return None
30
+ value, expires = item
31
+ if expires is not None and time.time() > expires:
32
+ self._data.pop(key, None)
33
+ return None
34
+ return value
35
+
36
+ def set(self, key: str, value: str, *, ttl: int | None = None) -> None:
37
+ exp = time.time() + ttl if ttl else None
38
+ self._data[key] = (value, exp)
39
+
40
+ def delete(self, key: str) -> None:
41
+ self._data.pop(key, None)
42
+
43
+
44
+ class RedisCache:
45
+ """Redis string cache (optional redis package)."""
46
+
47
+ def __init__(self, client: Any | None = None, *, url: str = "redis://localhost:6379/0") -> None:
48
+ if client is not None:
49
+ self._client = client
50
+ else:
51
+ try:
52
+ import redis # type: ignore[import-not-found]
53
+ except ImportError as exc: # pragma: no cover
54
+ raise ImportError("uv add 'phzyx-cache[redis]'") from exc
55
+ self._client = redis.Redis.from_url(url, decode_responses=True)
56
+
57
+ def get(self, key: str) -> str | None:
58
+ val = self._client.get(key)
59
+ return None if val is None else str(val)
60
+
61
+ def set(self, key: str, value: str, *, ttl: int | None = None) -> None:
62
+ if ttl:
63
+ self._client.setex(key, ttl, value)
64
+ else:
65
+ self._client.set(key, value)
66
+
67
+ def delete(self, key: str) -> None:
68
+ self._client.delete(key)
69
+
70
+
71
+ def dumps(obj: Any) -> str:
72
+ return json.dumps(obj, default=str, separators=(",", ":"))
73
+
74
+
75
+ def loads(raw: str) -> Any:
76
+ return json.loads(raw)
@@ -0,0 +1,53 @@
1
+ """Per-resource versioned list cache."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from phzyx.cache.backends import CacheBackend, dumps, loads
8
+
9
+
10
+ class ResourceCache:
11
+ """Cache lists under ``phzyx:res:{name}:v{version}:list:{suffix}``.
12
+
13
+ ``invalidate(resource)`` bumps the version so all prior list keys miss.
14
+ """
15
+
16
+ def __init__(
17
+ self, backend: CacheBackend, *, prefix: str = "phzyx", ttl: int | None = 60
18
+ ) -> None:
19
+ self.backend = backend
20
+ self.prefix = prefix
21
+ self.ttl = ttl
22
+
23
+ def _ver_key(self, resource: str) -> str:
24
+ return f"{self.prefix}:res:{resource}:version"
25
+
26
+ def version(self, resource: str) -> int:
27
+ raw = self.backend.get(self._ver_key(resource))
28
+ return int(raw or "1")
29
+
30
+ def invalidate(self, resource: str) -> int:
31
+ """Bump resource version; returns new version."""
32
+ v = self.version(resource) + 1
33
+ self.backend.set(self._ver_key(resource), str(v))
34
+ return v
35
+
36
+ def _list_key(self, resource: str, suffix: str) -> str:
37
+ return f"{self.prefix}:res:{resource}:v{self.version(resource)}:list:{suffix}"
38
+
39
+ def get_list(self, resource: str, suffix: str = "default") -> list[dict[str, Any]] | None:
40
+ raw = self.backend.get(self._list_key(resource, suffix))
41
+ if raw is None:
42
+ return None
43
+ data = loads(raw)
44
+ return data if isinstance(data, list) else None
45
+
46
+ def set_list(
47
+ self,
48
+ resource: str,
49
+ rows: list[dict[str, Any]],
50
+ *,
51
+ suffix: str = "default",
52
+ ) -> None:
53
+ self.backend.set(self._list_key(resource, suffix), dumps(rows), ttl=self.ttl)
@@ -0,0 +1,33 @@
1
+ Metadata-Version: 2.4
2
+ Name: phzyx-cache
3
+ Version: 0.1.0
4
+ Summary: Phzyx versioned resource cache (memory / Redis)
5
+ Author: Athul Nandaswaroop
6
+ License-Expression: MIT
7
+ Requires-Python: >=3.11
8
+ Provides-Extra: redis
9
+ Requires-Dist: redis>=5; extra == 'redis'
10
+ Description-Content-Type: text/markdown
11
+
12
+ # phzyx-cache
13
+
14
+ **Public battery.** `uv add 'phzyx-cache'` or `uv add 'phzyx-forge[cache]'`.
15
+
16
+
17
+ Per-resource **version keys** for list caching. Writes bump the version so stale lists miss.
18
+
19
+ ```python
20
+ from phzyx.cache import MemoryCache, ResourceCache
21
+
22
+ cache = ResourceCache(MemoryCache())
23
+ cache.set_list("users", "v1", [{"id": 1}]) # version auto-managed
24
+ cache.invalidate("users") # on write
25
+ ```
26
+
27
+ Redis:
28
+
29
+ ```bash
30
+ uv add 'phzyx-cache[redis]'
31
+ ```
32
+
33
+ **Limits:** only Phzyx write path invalidates; external DB writers can serve stale lists until TTL/version bump. Not a query cache for arbitrary SQL.
@@ -0,0 +1,6 @@
1
+ phzyx/cache/__init__.py,sha256=OoE_98mKZUZ6DuNHz9EpLlinqqkH9uzZm7g6K5yoXPY,258
2
+ phzyx/cache/backends.py,sha256=IJilcU0wwzOm0jEV8owAcmF0chNuF1Yrf0MEpJ7Hs18,2222
3
+ phzyx/cache/resource.py,sha256=U0Hr1c-de6iwb59UaVVDKpX2FcYhm01jF_YrbLqa12I,1675
4
+ phzyx_cache-0.1.0.dist-info/METADATA,sha256=u__oq8A5j5V38yQH-WU5-LhLTBBxuMdA1CfE9I1d-G8,888
5
+ phzyx_cache-0.1.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
6
+ phzyx_cache-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.31.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any