gpdatacached 0.2.0__tar.gz → 0.3.0__tar.gz
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.
- {gpdatacached-0.2.0 → gpdatacached-0.3.0}/.gitignore +3 -0
- {gpdatacached-0.2.0 → gpdatacached-0.3.0}/CHANGELOG.md +88 -0
- {gpdatacached-0.2.0 → gpdatacached-0.3.0}/PKG-INFO +6 -6
- {gpdatacached-0.2.0 → gpdatacached-0.3.0}/README.md +3 -3
- {gpdatacached-0.2.0 → gpdatacached-0.3.0}/README.zh-CN.md +3 -3
- {gpdatacached-0.2.0 → gpdatacached-0.3.0}/docs/en/api.md +64 -17
- {gpdatacached-0.2.0 → gpdatacached-0.3.0}/docs/en/extensibility.md +3 -2
- {gpdatacached-0.2.0 → gpdatacached-0.3.0}/docs/en/lifecycle.md +2 -2
- {gpdatacached-0.2.0 → gpdatacached-0.3.0}/docs/en/overview.md +2 -2
- {gpdatacached-0.2.0 → gpdatacached-0.3.0}/docs/en/pandas-support.md +3 -3
- {gpdatacached-0.2.0 → gpdatacached-0.3.0}/docs/en/performance-guide.md +2 -2
- {gpdatacached-0.2.0 → gpdatacached-0.3.0}/docs/en/pydantic-support.md +1 -1
- {gpdatacached-0.2.0 → gpdatacached-0.3.0}/docs/zh/api.md +39 -10
- {gpdatacached-0.2.0 → gpdatacached-0.3.0}/docs/zh/extensibility.md +3 -2
- {gpdatacached-0.2.0 → gpdatacached-0.3.0}/docs/zh/lifecycle.md +3 -3
- {gpdatacached-0.2.0 → gpdatacached-0.3.0}/docs/zh/overview.md +2 -2
- {gpdatacached-0.2.0 → gpdatacached-0.3.0}/docs/zh/pandas-support.md +5 -5
- {gpdatacached-0.2.0 → gpdatacached-0.3.0}/docs/zh/performance-guide.md +2 -2
- {gpdatacached-0.2.0 → gpdatacached-0.3.0}/docs/zh/pydantic-support.md +1 -1
- {gpdatacached-0.2.0 → gpdatacached-0.3.0}/pyproject.toml +1 -1
- {gpdatacached-0.2.0 → gpdatacached-0.3.0}/src/gpdatacached/__init__.py +15 -2
- gpdatacached-0.3.0/src/gpdatacached/builtins/decimal_object.py +39 -0
- {gpdatacached-0.2.0 → gpdatacached-0.3.0}/src/gpdatacached/builtins/registry.py +11 -0
- {gpdatacached-0.2.0 → gpdatacached-0.3.0}/src/gpdatacached/data_cache.py +114 -28
- {gpdatacached-0.2.0 → gpdatacached-0.3.0}/src/gpdatacached/domain.py +16 -0
- {gpdatacached-0.2.0 → gpdatacached-0.3.0}/src/gpdatacached/extensions/pandas_support.py +1 -1
- {gpdatacached-0.2.0 → gpdatacached-0.3.0}/src/gpdatacached/keys.py +30 -1
- {gpdatacached-0.2.0 → gpdatacached-0.3.0}/LICENSE +0 -0
- {gpdatacached-0.2.0 → gpdatacached-0.3.0}/docs/en/index.md +0 -0
- {gpdatacached-0.2.0 → gpdatacached-0.3.0}/docs/en/locking.md +0 -0
- {gpdatacached-0.2.0 → gpdatacached-0.3.0}/docs/zh/index.md +0 -0
- {gpdatacached-0.2.0 → gpdatacached-0.3.0}/docs/zh/locking.md +0 -0
- {gpdatacached-0.2.0 → gpdatacached-0.3.0}/src/gpdatacached/builtins/__init__.py +0 -0
- {gpdatacached-0.2.0 → gpdatacached-0.3.0}/src/gpdatacached/builtins/bool_object.py +0 -0
- {gpdatacached-0.2.0 → gpdatacached-0.3.0}/src/gpdatacached/builtins/datetime_object.py +0 -0
- {gpdatacached-0.2.0 → gpdatacached-0.3.0}/src/gpdatacached/builtins/dict_object.py +0 -0
- {gpdatacached-0.2.0 → gpdatacached-0.3.0}/src/gpdatacached/builtins/float_object.py +0 -0
- {gpdatacached-0.2.0 → gpdatacached-0.3.0}/src/gpdatacached/builtins/int_object.py +0 -0
- {gpdatacached-0.2.0 → gpdatacached-0.3.0}/src/gpdatacached/builtins/list_object.py +0 -0
- {gpdatacached-0.2.0 → gpdatacached-0.3.0}/src/gpdatacached/builtins/none_object.py +0 -0
- {gpdatacached-0.2.0 → gpdatacached-0.3.0}/src/gpdatacached/builtins/set_object.py +0 -0
- {gpdatacached-0.2.0 → gpdatacached-0.3.0}/src/gpdatacached/builtins/str_object.py +0 -0
- {gpdatacached-0.2.0 → gpdatacached-0.3.0}/src/gpdatacached/builtins/tuple_object.py +0 -0
- {gpdatacached-0.2.0 → gpdatacached-0.3.0}/src/gpdatacached/codec.py +0 -0
- {gpdatacached-0.2.0 → gpdatacached-0.3.0}/src/gpdatacached/config.py +0 -0
- {gpdatacached-0.2.0 → gpdatacached-0.3.0}/src/gpdatacached/container.py +0 -0
- {gpdatacached-0.2.0 → gpdatacached-0.3.0}/src/gpdatacached/exceptions.py +0 -0
- {gpdatacached-0.2.0 → gpdatacached-0.3.0}/src/gpdatacached/extensions/__init__.py +0 -0
- {gpdatacached-0.2.0 → gpdatacached-0.3.0}/src/gpdatacached/extensions/pydantic_support.py +0 -0
- {gpdatacached-0.2.0 → gpdatacached-0.3.0}/src/gpdatacached/gc.py +0 -0
- {gpdatacached-0.2.0 → gpdatacached-0.3.0}/src/gpdatacached/lifecycle.py +0 -0
- {gpdatacached-0.2.0 → gpdatacached-0.3.0}/src/gpdatacached/locking.py +0 -0
- {gpdatacached-0.2.0 → gpdatacached-0.3.0}/src/gpdatacached/meta.py +0 -0
- {gpdatacached-0.2.0 → gpdatacached-0.3.0}/src/gpdatacached/namespace.py +0 -0
- {gpdatacached-0.2.0 → gpdatacached-0.3.0}/src/gpdatacached/object.py +0 -0
- {gpdatacached-0.2.0 → gpdatacached-0.3.0}/src/gpdatacached/refset.py +0 -0
- {gpdatacached-0.2.0 → gpdatacached-0.3.0}/src/gpdatacached/scalar.py +0 -0
- {gpdatacached-0.2.0 → gpdatacached-0.3.0}/src/gpdatacached/types.py +0 -0
|
@@ -8,6 +8,94 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
8
8
|
GitHub Releases (with download artifacts) are published at
|
|
9
9
|
<https://github.com/LinnetCodes/gpdatacached/releases>.
|
|
10
10
|
|
|
11
|
+
## [0.3.0] — 2026-10-16
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- **Dotted domain names via the `@` filename escape (`GPDataCache`)** —
|
|
16
|
+
domain names may now contain `.` end-to-end through the config-file path.
|
|
17
|
+
gpconfig addresses configs by dot-separated paths, so a `.` in a config
|
|
18
|
+
filename is consumed as a path separator and the file becomes
|
|
19
|
+
unaddressable; the new convention writes each `.` in a domain name as `@`
|
|
20
|
+
in the filename (chosen for its internet-address connotation — the natural
|
|
21
|
+
spelling when a domain mirrors an internet domain):
|
|
22
|
+
`domain_name@com.yaml` configures the domain `"domain_name.com"`, and
|
|
23
|
+
`cache.get_domain("domain_name.com")`, `name in cache`, and
|
|
24
|
+
`gpdc://domain_name.com/{ns}/{obj}[...]` URIs resolve it transparently.
|
|
25
|
+
The mapping is injective because `@` is not a legal character in GPDC
|
|
26
|
+
names. `GPDataCache.get_domain` now validates the requested name against
|
|
27
|
+
the GPDC domain-name rules (`ValueError` otherwise; URI resolution reports
|
|
28
|
+
the same case as `ObjectNotFoundByURIError`, and `has_uri` stays aligned).
|
|
29
|
+
`GPDCDomain`'s name resolution restores `@` → `.` for names derived from
|
|
30
|
+
a config-file stem (before the name-mismatch check), so gpconfig's
|
|
31
|
+
object factory path also constructs correctly-named domains:
|
|
32
|
+
`folder.get_object("domain_name@com")` yields the domain
|
|
33
|
+
`"domain_name.com"`, identical to `GPDataCache.get_domain(
|
|
34
|
+
"domain_name.com")`. An explicit `name=` argument containing `@` is
|
|
35
|
+
still rejected. The escape helpers (`DOMAIN_DOT_ESCAPE`,
|
|
36
|
+
`escape_domain_name`, `unescape_domain_name`) are exported from the
|
|
37
|
+
package root. **No gpconfig-side change is
|
|
38
|
+
needed or wanted** (owner ruling 2026-10-16): gpconfig keeps
|
|
39
|
+
`config.name` as the raw stem, preserving the
|
|
40
|
+
`config.name == addressable path component` invariant; a gpconfig-side
|
|
41
|
+
request (fail-fast validation of config filenames containing `.` at manager
|
|
42
|
+
init, plus a docs note) is recorded in
|
|
43
|
+
`dev_docs/issue-gpconfig-at-escape-in-config-names.md`); that request
|
|
44
|
+
has since landed in gpconfig 0.4.0.
|
|
45
|
+
- **Startup warning for unaddressable config filenames** — `GPDataCache`
|
|
46
|
+
construction logs one warning per config file whose name contains a real
|
|
47
|
+
`.` (listed by `list_configs()` but never loadable by gpconfig's
|
|
48
|
+
dot-notation), suggesting the `@` spelling.
|
|
49
|
+
|
|
50
|
+
### Fixed
|
|
51
|
+
|
|
52
|
+
- `GPDataCache.__contains__` and `get_domain` no longer disagree for
|
|
53
|
+
config files whose stem contains `.` (previously `in` returned `True`
|
|
54
|
+
while `get_domain` raised `ConfigNotFoundError`; such files are now
|
|
55
|
+
warned about at construction and looked up only through valid names).
|
|
56
|
+
|
|
57
|
+
## [0.2.1] — 2026-10-12
|
|
58
|
+
|
|
59
|
+
### Added
|
|
60
|
+
|
|
61
|
+
- **`Decimal` builtin scalar type** — `decimal.Decimal` is auto-registered on
|
|
62
|
+
`import gpdatacached` as `scalar:decimal`; `DecimalObject` and `DecimalCodec`
|
|
63
|
+
are exported from the package root. Encoded losslessly as
|
|
64
|
+
`decimal:{str(value)}`: coefficient/exponent (significance) are preserved
|
|
65
|
+
exactly, extreme exponents use scientific notation, and non-finite values
|
|
66
|
+
(`NaN`, `sNaN`, `±Infinity`) plus `-0` roundtrip as-is — the same policy as
|
|
67
|
+
`float`'s `nan`/`inf`. Works everywhere a registered scalar does: standalone
|
|
68
|
+
objects, container elements, dict keys, set members, tuple elements, and
|
|
69
|
+
pandas object-dtype columns/index labels. Malformed payloads raise
|
|
70
|
+
`ValueError` on decode. Note the documented cross-type-equality caveat:
|
|
71
|
+
natively-equal values of different numeric types (`1` vs `Decimal(1)`) are
|
|
72
|
+
distinct GPDC dict keys/set members, as `1` vs `1.0` already were.
|
|
73
|
+
|
|
74
|
+
### Changed
|
|
75
|
+
|
|
76
|
+
- Require `gpconfig>=0.4.0` (was `>=0.3.0`, a version never published).
|
|
77
|
+
gpconfig 0.4.0 ships fail-fast validation of config filenames containing
|
|
78
|
+
`.` plus the `@`-convention documentation; gpdatacached's own
|
|
79
|
+
construction-time warning remains as defense for it.
|
|
80
|
+
|
|
81
|
+
### Documentation
|
|
82
|
+
|
|
83
|
+
- Full docs-vs-code synchronization pass across `docs/` (English + zh-CN
|
|
84
|
+
mirrors) and both READMEs: README now lists `Decimal` among the supported
|
|
85
|
+
scalar types and its Quick Start lifecycle example is runnable as written
|
|
86
|
+
(`update_cache_mode_and_ttl` instead of mode-restricted `expire_in`);
|
|
87
|
+
`api.md` documents `remaining_ttl()` and the exact `ObjectGoneError`
|
|
88
|
+
surface; the extensibility codec diagram and the overview layer-dependency
|
|
89
|
+
edge were corrected to match the code (`GPDCContainerCodec` is a sibling
|
|
90
|
+
root of `GPDCCodec`, and the extensions depend on the type-system layer,
|
|
91
|
+
not the builtins); performance-guide RTT attributions were updated; the
|
|
92
|
+
zh-CN mirrors gained the translated §7 ownership heading (with a durable
|
|
93
|
+
explicit anchor) and several translation fixes. `keys.py`'s `make_ref_key`
|
|
94
|
+
docstring and the pandas `UnsupportedElementTypeError` guidance message
|
|
95
|
+
were corrected to match reality (hash, not set; `decimal` included). The
|
|
96
|
+
README status badge now reads Beta, aligned with the packaging
|
|
97
|
+
classifier `Development Status :: 4 - Beta`.
|
|
98
|
+
|
|
11
99
|
## [0.2.0] — 2026-07-26
|
|
12
100
|
|
|
13
101
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: gpdatacached
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Redis-based cross-process data sharing and caching library
|
|
5
5
|
Project-URL: Homepage, https://github.com/linnetcodes/gpdatacached
|
|
6
6
|
Project-URL: Documentation, https://linnetcodes.github.io/gpdatacached
|
|
@@ -20,7 +20,7 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
20
20
|
Classifier: Programming Language :: Python :: 3.13
|
|
21
21
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
22
22
|
Requires-Python: >=3.10
|
|
23
|
-
Requires-Dist: gpconfig>=0.
|
|
23
|
+
Requires-Dist: gpconfig>=0.4.0
|
|
24
24
|
Requires-Dist: pydantic>=2.0
|
|
25
25
|
Requires-Dist: redis>=5.0
|
|
26
26
|
Provides-Extra: dev
|
|
@@ -41,7 +41,7 @@ Description-Content-Type: text/markdown
|
|
|
41
41
|
|
|
42
42
|
[](LICENSE)
|
|
43
43
|
[](https://www.python.org/)
|
|
44
|
-
[](https://pypi.org/project/gpdatacached/)
|
|
45
45
|
|
|
46
46
|
A Redis-based **cross-process data sharing and caching** library for Python. Any process that can reach the same Redis instance can share live, mutable, typed data structures — scalars, lists, dicts, sets — with reference semantics, lifecycle control, and optional distributed locking.
|
|
47
47
|
|
|
@@ -53,7 +53,7 @@ A Redis-based **cross-process data sharing and caching** library for Python. Any
|
|
|
53
53
|
|
|
54
54
|
Sharing mutable state across processes is painful. `multiprocessing.Manager` is single-process-bottlenecked; raw Redis forces you to (de)serialize everything by hand and gives you no type safety, no reference tracking, no TTL cascades. `gpdatacached` gives you a dict-like API on top of Redis with:
|
|
55
55
|
|
|
56
|
-
- **Typed objects** — store `int`, `str`, `datetime`, `list`, `dict`, `set`, … and get the same type back.
|
|
56
|
+
- **Typed objects** — store `int`, `decimal.Decimal`, `str`, `datetime`, `list`, `dict`, `set`, … and get the same type back.
|
|
57
57
|
- **Live containers** — `ns["tags"].append("x")` mutates Redis directly; no read-modify-write races on the whole structure.
|
|
58
58
|
- **Reference sharing** — bind the same container under multiple names inside a namespace; the data is stored once.
|
|
59
59
|
- **Lifecycle control** — per-object `permanent` / `ttl` / `sliding-window` cache modes, with cascade propagation to owned subtrees.
|
|
@@ -105,7 +105,7 @@ assert ns["tags"].value == ["b", "c", "d"]
|
|
|
105
105
|
|
|
106
106
|
# Lifecycle control.
|
|
107
107
|
ns["temp"] = "x"
|
|
108
|
-
ns.get_object("temp").
|
|
108
|
+
ns.get_object("temp").update_cache_mode_and_ttl("ttl", 60) # permanent → ttl, expires in 60s
|
|
109
109
|
|
|
110
110
|
domain.close()
|
|
111
111
|
```
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
[](LICENSE)
|
|
6
6
|
[](https://www.python.org/)
|
|
7
|
-
[](https://pypi.org/project/gpdatacached/)
|
|
8
8
|
|
|
9
9
|
A Redis-based **cross-process data sharing and caching** library for Python. Any process that can reach the same Redis instance can share live, mutable, typed data structures — scalars, lists, dicts, sets — with reference semantics, lifecycle control, and optional distributed locking.
|
|
10
10
|
|
|
@@ -16,7 +16,7 @@ A Redis-based **cross-process data sharing and caching** library for Python. Any
|
|
|
16
16
|
|
|
17
17
|
Sharing mutable state across processes is painful. `multiprocessing.Manager` is single-process-bottlenecked; raw Redis forces you to (de)serialize everything by hand and gives you no type safety, no reference tracking, no TTL cascades. `gpdatacached` gives you a dict-like API on top of Redis with:
|
|
18
18
|
|
|
19
|
-
- **Typed objects** — store `int`, `str`, `datetime`, `list`, `dict`, `set`, … and get the same type back.
|
|
19
|
+
- **Typed objects** — store `int`, `decimal.Decimal`, `str`, `datetime`, `list`, `dict`, `set`, … and get the same type back.
|
|
20
20
|
- **Live containers** — `ns["tags"].append("x")` mutates Redis directly; no read-modify-write races on the whole structure.
|
|
21
21
|
- **Reference sharing** — bind the same container under multiple names inside a namespace; the data is stored once.
|
|
22
22
|
- **Lifecycle control** — per-object `permanent` / `ttl` / `sliding-window` cache modes, with cascade propagation to owned subtrees.
|
|
@@ -68,7 +68,7 @@ assert ns["tags"].value == ["b", "c", "d"]
|
|
|
68
68
|
|
|
69
69
|
# Lifecycle control.
|
|
70
70
|
ns["temp"] = "x"
|
|
71
|
-
ns.get_object("temp").
|
|
71
|
+
ns.get_object("temp").update_cache_mode_and_ttl("ttl", 60) # permanent → ttl, expires in 60s
|
|
72
72
|
|
|
73
73
|
domain.close()
|
|
74
74
|
```
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
[](LICENSE)
|
|
6
6
|
[](https://www.python.org/)
|
|
7
|
-
[](https://pypi.org/project/gpdatacached/)
|
|
8
8
|
|
|
9
9
|
一个基于 Redis 的**跨进程数据共享与缓存** Python 库。任何能够访问同一 Redis 实例的进程,都可以共享活跃的、可变的、带类型的数据结构 —— 标量、list、dict、set —— 并具备引用语义、生命周期控制以及可选的分布式锁。
|
|
10
10
|
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
跨进程共享可变状态一直很麻烦:`multiprocessing.Manager` 会形成单进程瓶颈;直接使用 Redis 又需要你手动序列化所有数据,且没有类型安全、没有引用追踪、没有 TTL 级联。`gpdatacached` 在 Redis 之上提供了一套类 dict 的 API,特性包括:
|
|
18
18
|
|
|
19
|
-
- **带类型的对象** —— 存储 `int`、`str`、`datetime`、`list`、`dict`、`set` …,读取时得到相同类型。
|
|
19
|
+
- **带类型的对象** —— 存储 `int`、`decimal.Decimal`、`str`、`datetime`、`list`、`dict`、`set` …,读取时得到相同类型。
|
|
20
20
|
- **活跃容器** —— `ns["tags"].append("x")` 直接修改 Redis,避免对整个结构的“读-改-写”竞态。
|
|
21
21
|
- **引用共享** —— 在同一 namespace 内,同一个容器可绑定到多个名称;数据仅存储一份。
|
|
22
22
|
- **生命周期控制** —— 每个对象可设置 `permanent` / `ttl` / `sliding`(滑动窗口)缓存模式,并将策略级联传播到所拥有的子树。
|
|
@@ -68,7 +68,7 @@ assert ns["tags"].value == ["b", "c", "d"]
|
|
|
68
68
|
|
|
69
69
|
# 生命周期控制。
|
|
70
70
|
ns["temp"] = "x"
|
|
71
|
-
ns.get_object("temp").
|
|
71
|
+
ns.get_object("temp").update_cache_mode_and_ttl("ttl", 60) # permanent → ttl,60 秒后过期
|
|
72
72
|
|
|
73
73
|
domain.close()
|
|
74
74
|
```
|
|
@@ -110,6 +110,7 @@ folder = mgr.get_config("gpdc") # -> GPConfigFolder; sample layouts in te
|
|
|
110
110
|
with GPDataCache(folder) as cache:
|
|
111
111
|
"core_domain" in cache # True iff a config exists (no Redis)
|
|
112
112
|
domain = cache.get_domain("core_domain") # lazy construct + cache
|
|
113
|
+
site = cache.get_domain("domain_name.com") # dotted name -> domain_name@com.yaml
|
|
113
114
|
|
|
114
115
|
# 3. Resolve objects by GPDC URI — including anonymous nested objects.
|
|
115
116
|
ns = domain.get_namespace("core_app")
|
|
@@ -202,7 +203,7 @@ Configuration for a `GPDCDomain`. All fields are validated; lock fields must be
|
|
|
202
203
|
| `gc_lock_ttl_seconds` | `int` | `30` | TTL for locks acquired by GC (must be `≥ 1`). |
|
|
203
204
|
| `gc_lock_wait_seconds` | `int` | `5` | GC per-object try-lock timeout (must be `≥ 0`). |
|
|
204
205
|
|
|
205
|
-
👉 These four fields
|
|
206
|
+
👉 These four fields must be kept consistent across every process that opens the same domain (mismatch raises `GPDCLockConfigInconsistentError` at domain construction). See [Multi-Process Locking](./locking.md) for what each controls, the cost locks impose, and the GC contract.
|
|
206
207
|
|
|
207
208
|
---
|
|
208
209
|
|
|
@@ -214,7 +215,7 @@ class GPDCNamespace(name, domain, *, config=None) # MutableMapping
|
|
|
214
215
|
|
|
215
216
|
A logical isolation space inside a `GPDCDomain`. Implements the full `MutableMapping` protocol backed by Redis.
|
|
216
217
|
|
|
217
|
-
**Naming rule:** names may use letters, digits, `.` and `_`; names wrapped in `__xxx__` are reserved. Anonymous objects (internal names starting with `~`) are hidden from the public mapping API. Common characters that are **rejected** because they fall outside the allow-list (and, for `:`, collide with the Redis key separator): hyphen `-`, spaces, and `:` — use `_` or `.` instead.
|
|
218
|
+
**Naming rule:** names may use letters, digits, `.` and `_`; names wrapped in `__xxx__` are reserved. Anonymous objects (internal names starting with `~`) are hidden from the public mapping API. Common characters that are **rejected** because they fall outside the allow-list (and, for `:`, collide with the Redis key separator): hyphen `-`, spaces, and `:` — use `_` or `.` instead. This rule applies to **object and namespace names**, and to **domain names** (a `.` in a domain name is legal everywhere in GPDC — but when the domain is driven by a config file through `GPDataCache`, the `.` must be spelled `@` in the filename; see the [`GPDataCache` `@` escape](#gpdatacache)).
|
|
218
219
|
|
|
219
220
|
**Properties**
|
|
220
221
|
|
|
@@ -296,15 +297,34 @@ recommended entry point when your domains are driven by config files rather
|
|
|
296
297
|
than constructed ad-hoc in code.
|
|
297
298
|
|
|
298
299
|
**Construction.** Built from a `GPConfigFolder`. On construction it stores the
|
|
299
|
-
folder reference only — **no domain is created
|
|
300
|
-
|
|
301
|
-
[`get_domain`](#gpdatacache) access, and cached in
|
|
302
|
-
is a snapshot: config files edited after
|
|
303
|
-
rebuild the `GPDataCache` to pick up changes.
|
|
300
|
+
folder reference only — **no domain is created** (it does scan the folder once
|
|
301
|
+
to warn about unaddressable config filenames; see below). Domains are
|
|
302
|
+
constructed lazily on first [`get_domain`](#gpdatacache) access, and cached in
|
|
303
|
+
an internal dict. The cache is a snapshot: config files edited after
|
|
304
|
+
construction are **not** re-read; rebuild the `GPDataCache` to pick up changes.
|
|
304
305
|
|
|
305
306
|
`GPDataCache` does **not** create domains lacking a config file — for ad-hoc
|
|
306
307
|
domains use [`GPDCDomain`](#gpdcdomain) directly.
|
|
307
308
|
|
|
309
|
+
> **Dots in domain names — the `@` escape.** gpconfig addresses configs by
|
|
310
|
+
> dot-separated paths, so a `.` in a config filename is consumed as a path
|
|
311
|
+
> separator and the file becomes unaddressable. To use a dotted domain name —
|
|
312
|
+
> the natural shape when a domain mirrors an internet domain, e.g.
|
|
313
|
+
> `domain_name.com` — write each `.` as `@` in the config filename:
|
|
314
|
+
> `domain_name@com.yaml` configures the domain `"domain_name.com"`, and
|
|
315
|
+
> `cache.get_domain("domain_name.com")` /
|
|
316
|
+
> `cache.get_object_by_uri("gpdc://domain_name.com/ns/obj")` resolve it
|
|
317
|
+
> transparently. The mapping is unambiguous because `@` is not a legal
|
|
318
|
+
> character in GPDC names. `GPDCDomain` itself performs the `@` → `.`
|
|
319
|
+
> restore for names derived from a config-file stem, so gpconfig's object
|
|
320
|
+
> factory path also works: `folder.get_object("domain_name@com")`
|
|
321
|
+
> constructs a domain named `domain_name.com`, identical to
|
|
322
|
+
> `cache.get_domain("domain_name.com")`. An explicit `name=` argument
|
|
323
|
+
> containing `@` is still rejected — the escape exists for filenames;
|
|
324
|
+
> in code, write the `.` directly. Config files whose names contain a real `.` are
|
|
325
|
+
> listed by the folder but can never be loaded; `GPDataCache` logs a warning
|
|
326
|
+
> about them once at construction.
|
|
327
|
+
|
|
308
328
|
> **Registration side effect.** `import gpdatacached` registers `GPDCDomain`
|
|
309
329
|
> and `GPDCDomainConfig` with gpconfig's object factory (idempotent), which is
|
|
310
330
|
> what makes `folder.get_object(name)` able to construct a `GPDCDomain`. No
|
|
@@ -312,7 +332,7 @@ domains use [`GPDCDomain`](#gpdcdomain) directly.
|
|
|
312
332
|
|
|
313
333
|
**Dunder protocols**
|
|
314
334
|
|
|
315
|
-
- `name in cache` → `True` iff a config
|
|
335
|
+
- `name in cache` → `True` iff a config for `name` exists in the folder (dotted names look up their `@`-escaped filename). This is a pure config-snapshot check: it does **not** touch Redis and does not construct (or require) a `GPDCDomain` instance — the answer depends only on whether the config file is present, not on whether `get_domain(name)` has been called. Names that are not legal GPDC domain names (e.g. containing `@`) return `False`. The domain instance is created lazily on first `get_domain(name)` access.
|
|
316
336
|
- `with cache as c` / `__exit__` → closes every cached domain on exit; `close()` is idempotent.
|
|
317
337
|
|
|
318
338
|
> **`close()` behaviour — the membership exception.** After `cache.close()`, the resource-touching methods (`get_domain`, `get_object_by_uri`, `has_uri`) raise `RuntimeError("GPDataCache is closed")`. The `name in cache` membership check is the **deliberate exception**: because it only reads the config snapshot and touches no resources, it keeps working after `close()`. This is intentional — "does a config for this domain exist?" is meaningful regardless of whether the cache is open.
|
|
@@ -321,7 +341,7 @@ domains use [`GPDCDomain`](#gpdcdomain) directly.
|
|
|
321
341
|
|
|
322
342
|
| Method | Description |
|
|
323
343
|
|---|---|
|
|
324
|
-
| `get_domain(name)` | Return the cached `GPDCDomain` for `name`, constructing it lazily on first access.
|
|
344
|
+
| `get_domain(name)` | Return the cached `GPDCDomain` for `name`, constructing it lazily on first access. Dotted names resolve to their `@`-escaped config file (see above). Raises `ValueError` if `name` is not a legal domain name; propagates gpconfig's `ConfigNotFoundError` if no config exists for `name`. Raises `RuntimeError` after `close()`. |
|
|
325
345
|
| [`get_object_by_uri(uri)`](#get_object_by_uri) | Resolve a GPDC URI to a `GPDCDataObject`. Raises `RuntimeError` after `close()`. |
|
|
326
346
|
| `has_uri(uri)` | Return `True` iff `get_object_by_uri(uri)` would succeed. Raises `RuntimeError` after `close()`. |
|
|
327
347
|
| `close()` | Close every cached domain. Idempotent. |
|
|
@@ -370,9 +390,10 @@ For example, given `ns["cfg"] = {"port": 8080}`, `gpdc://d/ns/cfg/port` raises
|
|
|
370
390
|
|
|
371
391
|
**Errors.** Every failure mode raises [`ObjectNotFoundByURIError`](#objectnotfoundbyurierror)
|
|
372
392
|
(carries `.uri` and `.reason`): malformed URI (missing `gpdc://`, fewer than
|
|
373
|
-
three segments, empty segment),
|
|
374
|
-
|
|
375
|
-
|
|
393
|
+
three segments, empty segment), an invalid domain/namespace/object name (each
|
|
394
|
+
locating segment is validated up front), unknown domain, missing namespace,
|
|
395
|
+
missing root object, a path segment that fails
|
|
396
|
+
(`KeyError`/`IndexError`/`TypeError`), and a scalar terminal.
|
|
376
397
|
|
|
377
398
|
**Example.** Given `ns["cfg"] = {"server": {"host": "x"}, "tags": ["a","b"]}`:
|
|
378
399
|
|
|
@@ -395,7 +416,7 @@ class ObjectNotFoundByURIError(GPDCError)
|
|
|
395
416
|
Raised by [`GPDataCache.get_object_by_uri`](#get_object_by_uri) when a GPDC URI
|
|
396
417
|
cannot be resolved to a `GPDCDataObject`. Covers every failure mode: malformed
|
|
397
418
|
URI syntax, unknown domain (no config), missing namespace, missing root object,
|
|
398
|
-
a path segment that fails, and a scalar terminal. Carries `.uri` (the URI
|
|
419
|
+
an invalid domain/namespace/object name, a path segment that fails, and a scalar terminal. Carries `.uri` (the URI
|
|
399
420
|
string) and `.reason` (a short explanation).
|
|
400
421
|
|
|
401
422
|
`has_uri` swallows only this exception; infrastructure errors (e.g. Redis
|
|
@@ -431,12 +452,14 @@ Abstract base class for every GPDC data object. Subclasses are split into [`GPDC
|
|
|
431
452
|
|---|---|---|
|
|
432
453
|
| `type` | `str` | GPDC type string, e.g. `"scalar:int"`, `"collection:list"`. |
|
|
433
454
|
| `meta` | `ObjectMeta \| None` | Full metadata record; `None` if the object has vanished. |
|
|
434
|
-
| `cache_mode` | `str` | Effective cache mode
|
|
455
|
+
| `cache_mode` | `str` | Effective cache mode. |
|
|
435
456
|
| `ttl` | `int` | Declared TTL in seconds (`0` = permanent). |
|
|
436
457
|
| `created_at` | `datetime \| None` | Creation timestamp. |
|
|
437
458
|
| `updated_at` | `datetime \| None` | Last update timestamp. |
|
|
438
459
|
| `value` | `object` | The Python value (abstract; subclasses implement). |
|
|
439
460
|
|
|
461
|
+
> The meta-backed properties (`cache_mode`, `ttl`, `created_at`, `updated_at`) each raise `ObjectGoneError` if the object's metadata no longer exists.
|
|
462
|
+
|
|
440
463
|
**Lifecycle methods**
|
|
441
464
|
|
|
442
465
|
> This is the API summary. For the full model — policy resolution, sliding refresh, ownership, anonymous/reference lifecycle, and pitfalls — see [Lifecycle Management](./lifecycle.md).
|
|
@@ -449,6 +472,7 @@ Abstract base class for every GPDC data object. Subclasses are split into [`GPDC
|
|
|
449
472
|
| `persist()` | Switch to `cache_mode="permanent"` with `ttl=0`. Requires ownership. |
|
|
450
473
|
| `update_cache_mode_and_ttl(cache_mode, ttl)` | Replace the full cache policy. Requires ownership. |
|
|
451
474
|
| `refresh_ttl()` | Re-apply this object's own TTL to its keys and cascade-refresh its owned subtree (used by the sliding-window path). No-op for permanent objects. |
|
|
475
|
+
| `remaining_ttl()` | Remaining TTL in seconds on the canonical key, as reported by the Redis `TTL` command (`-1` = no expiry, `-2` = key missing). |
|
|
452
476
|
| `take_ownership()` | Promote **the referenced object** to own itself (primary use case: a named reference to an anonymous nested object). On a reference to a named (already self-owned) object it is a correct no-op. The call itself must be made via a named reference handle. See [Lifecycle §7](./lifecycle.md#7-the-ownership-model). |
|
|
453
477
|
|
|
454
478
|
**Locking methods** (opt-in, never called automatically by mutations)
|
|
@@ -480,7 +504,7 @@ Base class for every scalar object. A scalar stores a single encoded value in it
|
|
|
480
504
|
- `value` getter decodes the stored value via the registered codec.
|
|
481
505
|
- `value` setter validates the Python type against the registered codec, updates metadata, and refreshes TTL when `cache_mode="sliding"`.
|
|
482
506
|
|
|
483
|
-
Built-in subclasses: [`IntObject`](#intobject), [`FloatObject`](#floatobject), [`StrObject`](#strobject), [`BoolObject`](#boolobject), [`DateTimeObject`](#datetimeobject), [`DateObject`](#dateobject), [`TimeObject`](#timeobject), [`NoneObject`](#noneobject), [`TupleObject`](#tupleobject).
|
|
507
|
+
Built-in subclasses: [`IntObject`](#intobject), [`FloatObject`](#floatobject), [`StrObject`](#strobject), [`BoolObject`](#boolobject), [`DateTimeObject`](#datetimeobject), [`DateObject`](#dateobject), [`TimeObject`](#timeobject), [`DecimalObject`](#decimalobject), [`NoneObject`](#noneobject), [`TupleObject`](#tupleobject).
|
|
484
508
|
|
|
485
509
|
---
|
|
486
510
|
|
|
@@ -526,6 +550,7 @@ All scalar classes inherit from [`GPDCScalarObject`](#gpdcscalarobject). Reading
|
|
|
526
550
|
| [`DateTimeObject`](#datetimeobject) | `scalar:datetime` | `datetime.datetime` | `datetime:{isoformat}` |
|
|
527
551
|
| [`DateObject`](#dateobject) | `scalar:date` | `datetime.date` (excludes `datetime`) | `date:{isoformat}` |
|
|
528
552
|
| [`TimeObject`](#timeobject) | `scalar:time` | `datetime.time` | `time:{isoformat}` |
|
|
553
|
+
| [`DecimalObject`](#decimalobject) | `scalar:decimal` | `decimal.Decimal` | `decimal:{str(d)}` |
|
|
529
554
|
| [`NoneObject`](#noneobject) | `scalar:none` | `NoneType` | `none:None` |
|
|
530
555
|
| [`TupleObject`](#tupleobject) | `scalar:tuple` | `tuple` (scalar elements only) | `tuple:{json}` |
|
|
531
556
|
|
|
@@ -557,6 +582,14 @@ Wraps `datetime.date`. Note: a `datetime.datetime` is rejected by `DateCodec` ev
|
|
|
557
582
|
|
|
558
583
|
Wraps `datetime.time`.
|
|
559
584
|
|
|
585
|
+
### `DecimalObject`
|
|
586
|
+
|
|
587
|
+
Wraps `decimal.Decimal` (added in 0.2.1). Encoded as `decimal:{str(value)}`: the payload is `str(Decimal)`, which preserves the exact coefficient and exponent — `Decimal("1.10")` roundtrips as `1.10`, not `1.1`, and extreme exponents use scientific notation (`1E-27`). A malformed payload raises `ValueError` on decode.
|
|
588
|
+
|
|
589
|
+
- **Non-finite values** (`NaN`, `sNaN`, `±Infinity`) and `-0` are stored and restored as-is, matching the `float` codec's treatment of `nan`/`inf`. Comparison semantics follow the standard library: `NaN != NaN`, and comparing an `sNaN` with `==` raises `decimal.InvalidOperation`.
|
|
590
|
+
- **Exactness**: `str()` is context-independent — no rounding is applied on store or load. Construct from strings (`Decimal("0.1")`), not floats, if you mean the decimal literal; `Decimal(0.1)` faithfully stores the full binary expansion.
|
|
591
|
+
- **Cross-type equality**: GPDC matches dict keys and set members by their encoded wire form, so natively-equal values of different numeric types become distinct entries once stored — `d[1] = "a"` followed by `d[Decimal(1)] = "b"` yields two entries (the same already holds for `1` vs `1.0`). A native literal like `{1: "a", Decimal(1): "b"}` collapses to one key before GPDC ever sees it.
|
|
592
|
+
|
|
560
593
|
### `NoneObject`
|
|
561
594
|
|
|
562
595
|
Wraps `None`. Used to represent the presence of a `None` value distinctly from a missing key.
|
|
@@ -654,7 +687,7 @@ Abstract base for scalar codecs. Provides the shared helper:
|
|
|
654
687
|
|---|---|
|
|
655
688
|
| `_parse_encoded(encoded) -> tuple[str, str]` | Split a `name:payload` encoded value into `(type_name, raw)`. Raises `ValueError` on empty or colon-less input. |
|
|
656
689
|
|
|
657
|
-
Built-in subclasses: `BoolCodec`, `DateTimeCodec`, `DateCodec`, `DictCodec` *(see note)*, `FloatCodec`, `IntCodec`, `ListCodec` *(see note)*, `NoneCodec`, `SetCodec` *(see note)*, `StrCodec`, `TimeCodec`, `TupleCodec`.
|
|
690
|
+
Built-in subclasses: `BoolCodec`, `DateTimeCodec`, `DateCodec`, `DecimalCodec`, `DictCodec` *(see note)*, `FloatCodec`, `IntCodec`, `ListCodec` *(see note)*, `NoneCodec`, `SetCodec` *(see note)*, `StrCodec`, `TimeCodec`, `TupleCodec`.
|
|
658
691
|
|
|
659
692
|
> Scalar codec classes for containers (`ListCodec`, `DictCodec`, `SetCodec`) are re-exported only for completeness — they actually inherit from [`GPDCContainerCodec`](#gpdccontainercodec).
|
|
660
693
|
|
|
@@ -749,6 +782,20 @@ def validate_object_name(name: str) -> None
|
|
|
749
782
|
|
|
750
783
|
Validate that `name` contains only letters, digits, `.` or `_` and is not a reserved `__xxx__` name. Raises `ValueError` otherwise. (Equivalent validators exist internally for domain and namespace names.)
|
|
751
784
|
|
|
785
|
+
### Domain-name `@` escape helpers
|
|
786
|
+
|
|
787
|
+
```python
|
|
788
|
+
DOMAIN_DOT_ESCAPE: str # "@"
|
|
789
|
+
|
|
790
|
+
def escape_domain_name(name: str) -> str
|
|
791
|
+
# "domain_name.com" -> "domain_name@com" ('.' -> '@')
|
|
792
|
+
|
|
793
|
+
def unescape_domain_name(stem: str) -> str
|
|
794
|
+
# "domain_name@com" -> "domain_name.com" ('@' -> '.')
|
|
795
|
+
```
|
|
796
|
+
|
|
797
|
+
Helpers for the [`GPDataCache` `@` escape convention](#gpdatacache): gpconfig addresses configs by dot-separated paths, so a `.` in a domain config filename is unaddressable — each `.` in a domain name is written as `@` in the filename instead. The mapping is injective because `@` is not a legal character in GPDC names. `unescape_domain_name` is safe to apply to any stem-derived name for the same reason. You rarely need these directly — `GPDataCache` (lookup escaping) and `GPDCDomain` (load-time restore) apply them automatically.
|
|
798
|
+
|
|
752
799
|
---
|
|
753
800
|
|
|
754
801
|
## Exceptions
|
|
@@ -761,7 +808,7 @@ All public exceptions descend from the public `GPDCError(Exception)` root. Use `
|
|
|
761
808
|
class GPDCError(Exception)
|
|
762
809
|
```
|
|
763
810
|
|
|
764
|
-
The root of the gpdatacached exception hierarchy. Every exception raised by this library is a subclass of `GPDCError`, so `except GPDCError` is the broad catch-all. Prefer catching the specific subclass when you need to distinguish failure modes.
|
|
811
|
+
The root of the gpdatacached exception hierarchy. Every gpdatacached-specific exception raised by this library is a subclass of `GPDCError`, so `except GPDCError` is the broad catch-all. (Plain stdlib errors — e.g. `TypeError` for unsupported types or an invalid argument order, `ValueError` for invalid payloads — are raised as-is.) Prefer catching the specific subclass when you need to distinguish failure modes.
|
|
765
812
|
|
|
766
813
|
### `GPDCLockError`
|
|
767
814
|
|
|
@@ -38,7 +38,7 @@ Two value families exist, with different contracts:
|
|
|
38
38
|
|
|
39
39
|
---
|
|
40
40
|
|
|
41
|
-
## 2. The Registration
|
|
41
|
+
## 2. The Registration Entry
|
|
42
42
|
|
|
43
43
|
Registering a type always means pairing four things via one call:
|
|
44
44
|
|
|
@@ -94,9 +94,10 @@ graph TD
|
|
|
94
94
|
C1["GPDCScalarCodec"]
|
|
95
95
|
C2["GPDCContainerCodec (ABC)"]
|
|
96
96
|
C0 -->|encode_value / decode_value| C1
|
|
97
|
-
C0 -->|create_raw / read_raw / delete_raw| C2
|
|
98
97
|
```
|
|
99
98
|
|
|
99
|
+
Two **independent** roots: `GPDCCodec` (scalars) and `GPDCContainerCodec` (containers) are siblings — a container codec never implements `encode_value`/`decode_value`.
|
|
100
|
+
|
|
100
101
|
### `GPDCCodec` — the root ABC
|
|
101
102
|
|
|
102
103
|
```python
|
|
@@ -194,7 +194,7 @@ graph TD
|
|
|
194
194
|
|
|
195
195
|
---
|
|
196
196
|
|
|
197
|
-
## 7. The Ownership Model
|
|
197
|
+
## 7. The Ownership Model { #7-the-ownership-model }
|
|
198
198
|
|
|
199
199
|
Every object has an `owner` field in its metadata. Ownership determines who controls the object's lifecycle and is central to how anonymous objects work.
|
|
200
200
|
|
|
@@ -362,7 +362,7 @@ For reference, here is the complete set of user-facing lifecycle methods on `GPD
|
|
|
362
362
|
| `take_ownership()` | Promote **the referenced object** to own itself (primary use case: a named reference to an anonymous nested object; a correct no-op on a reference to an already self-owned named object). |
|
|
363
363
|
| `ttl` (property) | The declared TTL in seconds. |
|
|
364
364
|
| `cache_mode` (property) | The current cache mode. |
|
|
365
|
-
| `remaining_ttl()` | The actual remaining TTL reported by Redis (
|
|
365
|
+
| `remaining_ttl()` | The actual remaining TTL reported by Redis (0; -1 = no expiry; -2 = key already gone). |
|
|
366
366
|
| `created_at` / `updated_at` (properties) | Timestamps from metadata. |
|
|
367
367
|
|
|
368
368
|
Plus the domain-level `domain.run_gc(namespace=None)` for manual GC.
|
|
@@ -71,7 +71,7 @@ graph TD
|
|
|
71
71
|
end
|
|
72
72
|
subgraph L4["Layer 4 — Built-in types"]
|
|
73
73
|
BREG["builtins.registry<br/>(auto-registered on import)"]
|
|
74
|
-
SCALARS["scalar types<br/>int·float·str·bool·datetime·date·time·none·tuple"]
|
|
74
|
+
SCALARS["scalar types<br/>int·float·decimal·str·bool·datetime·date·time·none·tuple"]
|
|
75
75
|
CONTAINERS["container types<br/>list·dict·set"]
|
|
76
76
|
end
|
|
77
77
|
subgraph L3["Layer 3 — Type system"]
|
|
@@ -92,7 +92,7 @@ graph TD
|
|
|
92
92
|
EXC["exceptions"]
|
|
93
93
|
end
|
|
94
94
|
REDIS[("Redis")]
|
|
95
|
-
L5 -->
|
|
95
|
+
L5 --> L3
|
|
96
96
|
L4 --> L3
|
|
97
97
|
L3 --> L2
|
|
98
98
|
L3 --> L1
|
|
@@ -38,7 +38,7 @@ Two corollaries:
|
|
|
38
38
|
|---|---|
|
|
39
39
|
| **Row indexes** | `RangeIndex` (default int index, stored compactly) **and** any labelled index including `pd.MultiIndex` (stored one `collection:list` child per level). `.index_names` always returns a tuple. |
|
|
40
40
|
| **DataFrame columns** | Single-level (scalar names) **or** `pd.MultiIndex` (tuple names). The row and column axes are independent and compose (a DataFrame may have both a row and a column MultiIndex). |
|
|
41
|
-
| **Value dtypes** | `int64`/`Int64`, `float64`/`Float64`, `bool`/`boolean
|
|
41
|
+
| **Value dtypes** | Integer / float / boolean dtypes of any width (e.g. `int64`/`Int64`, `float64`/`Float64`, `bool`/`boolean`), `str`/`string`, `object` (any scalar-encodable element — including `decimal.Decimal`, builtin since 0.2.1), `datetime64` (naive & tz-aware, any unit), `category`. |
|
|
42
42
|
| **Mutation** | Row `extend` (Series & DataFrame); DataFrame column `__setitem__` / `__delitem__` (structural add/replace/drop). |
|
|
43
43
|
|
|
44
44
|
Notes:
|
|
@@ -185,7 +185,7 @@ GPDC type `collection:pandas_dataframe`. Backed by a Redis hash with column valu
|
|
|
185
185
|
|
|
186
186
|
| Method | Description |
|
|
187
187
|
|---|---|
|
|
188
|
-
| `extend(other)` | Append rows in place. Accepts a `pd.DataFrame`, a `dict` of lists, or a list of `dict` rows. For a genuine MultiIndex-row DataFrame (`L ≥ 2`), only a `DataFrame` with a matching MultiIndex is accepted (dict/list inputs synthesise positional labels a MultiIndex cannot supply → `TypeError`; use `.value`). Mutates Redis; returns `None`. |
|
|
188
|
+
| `extend(other)` | Append rows in place. Accepts a `pd.DataFrame`, a `dict` of lists, or a list of `dict` rows. For a genuine MultiIndex-row DataFrame (`L ≥ 2`), only a `DataFrame` with a matching MultiIndex is accepted (dict/list inputs synthesise positional labels a MultiIndex cannot supply → `TypeError`; use `.value`). Category columns reject values outside the stored categories (`ValueError`, raised before any Redis write). Mutates Redis; returns `None`. |
|
|
189
189
|
| `deepcopy()` | Return a fully independent `pd.DataFrame` (`.value.copy(deep=True)`). |
|
|
190
190
|
|
|
191
191
|
**Column-axis access (single-level columns)**
|
|
@@ -248,4 +248,4 @@ Raised when a DataFrame cannot be stored element-wise by `DataFrameObject`. The
|
|
|
248
248
|
class UnsupportedElementTypeError(TypeError)
|
|
249
249
|
```
|
|
250
250
|
|
|
251
|
-
Raised when a Series/DataFrame element is not encodable by a `GPDCScalarCodec` subclass — i.e. it is a container type (`list`/`dict`/`set`/container-registered pydantic model) or an unsupported type. Series/DataFrame elements must be scalar types encodable by a `GPDCScalarCodec` subclass. The message enumerates the allowed scalar types.
|
|
251
|
+
Raised when a Series/DataFrame element is not encodable by a `GPDCScalarCodec` subclass — i.e. it is a container type (`list`/`dict`/`set`/container-registered pydantic model) or an unsupported type. Series/DataFrame elements must be scalar types encodable by a `GPDCScalarCodec` subclass. The message enumerates the allowed scalar types. Allowed builtin scalars (0.2.1+): int/float/bool/str/datetime/date/time/decimal/tuple/None, plus any scalar-registered custom type; object-dtype columns and index labels may hold any of them — including `decimal.Decimal`.
|
|
@@ -36,7 +36,7 @@ A quick reference. "RTT" = Redis round-trip. N = number of elements; counts are
|
|
|
36
36
|
| `ns[k]` (scalar read) | O(1), ~3 RTT | `_resolve_binding` (`TYPE` + `HGETALL`) **plus** the scalar `.value` getter re-loads meta (`HGETALL`). |
|
|
37
37
|
| `ns[k] = [a, b, c]` (flat, scalar-element container) | O(N) local encode, **O(1)** data-write RTT | One `encode_session` + one bulk `create_raw` (single `RPUSH`/`HSET`/`SADD` carrying all elements) + `HGET` ×2 (namespace-default policy) + metadata `HSET` + TTL applies on the obj/raw/ref keys (≈ 8 RTT total, independent of N for scalar elements). |
|
|
38
38
|
| `ns[k] = [[...], ...]` (nested container) | O(**nested-container count**) RTT + O(N) local | Each nested container becomes one anonymous object (meta `HSET` + its own `create_raw` + `HINCRBY` ref + TTL applies ≈ 5–6 RTT each). Depth does **not** multiply — it is the total node count that scales. |
|
|
39
|
-
| `container.append(x)` / `container[k] = v` (scalar element, non-sliding) | O(1), ~5–6 RTT | `HGETALL` (policy) + data write (`RPUSH`/`HSET`) + `
|
|
39
|
+
| `container.append(x)` / `container[k] = v` (scalar element, non-sliding) | O(1), ~5–6 RTT | `HGETALL` ×2 (liveness/policy + data-owner resolve) + data write (`RPUSH`/`HSET`) + `LLEN`/`HLEN` + metadata `HSET`. **Add the sliding-subtree walk if `cache_mode="sliding"`** (§3); **add ~5–6 RTT if the element is itself a container** (anonymous-object creation). |
|
|
40
40
|
| `iter(ns)` / `len(ns)` | O(N), batched SCAN | A few RTT per ~100-key SCAN page (`len` delegates to `iter`). Don't put in a hot loop. |
|
|
41
41
|
| `k in ns` | O(1), 1–4 RTT | Direct `TYPE` + `HGETALL` on the named key (1–2 RTT for owner bindings); references add a `GET` + another `TYPE` (up to 4 RTT for aliases). **Not** a SCAN. Does not scale with namespace size. |
|
|
42
42
|
| `del ns[k]` (container owner) | O(owned subtree size) | `_teardown_owner_container` releases each nested ref (`HDEL`) and cascade-deletes orphaned anonymous children; ~1+ RTT per node. Raises `CanonicalObjectReferencedError` if live alias referrers exist — delete those first. |
|
|
@@ -48,7 +48,7 @@ A quick reference. "RTT" = Redis round-trip. N = number of elements; counts are
|
|
|
48
48
|
| `.value` (full container read) | flat: 1 RTT + O(N) local; nested: +O(nested count) RTT | One bulk read (`LRANGE`/`HGETALL`/`SMEMBERS`); each `ref:` element adds one `HGETALL` + decode for its anonymous object. |
|
|
49
49
|
| `name in cache` (`GPDataCache`) | **local, 0 RTT** | Pure config-snapshot check via `folder.list_configs()`. Does not touch Redis and does not construct a domain. The cheapest membership test in the library. |
|
|
50
50
|
| `cache.get_domain(name)` | 1st call: domain construction (domain-meta read/verify); subsequent: **0 RTT** | First access constructs and caches the `GPDCDomain` via gpconfig's factory; later calls return the cached instance. Does not re-read config files. |
|
|
51
|
-
| `cache.get_object_by_uri(uri)` | root: ~4–5 RTT; **+1 RTT per path segment** | `get_namespace` (`SISMEMBER` + namespace-meta `HGETALL`, ~
|
|
51
|
+
| `cache.get_object_by_uri(uri)` | root: ~4–5 RTT; **+1–2 RTT per path segment** | `get_namespace` (`SISMEMBER` + namespace-meta `HGETALL` + idempotent `SADD`, ~3 RTT) + root resolve (`TYPE` + `HGETALL`, ~2–3 RTT). Each extra path segment is one `__getitem__` read (ListObject: `LLEN` + `LINDEX` — 2 RTT; DictObject: `HGET` — 1 RTT). `cache.has_uri(uri)` has the same cost (it calls `get_object_by_uri` under a try/except). The domain is only constructed on the first access; later URIs in the same domain reuse the cached domain. |
|
|
52
52
|
|
|
53
53
|
---
|
|
54
54
|
|
|
@@ -134,7 +134,7 @@ Stores the entire model as a single JSON-encoded Redis string. Reads return a fr
|
|
|
134
134
|
|
|
135
135
|
The whole object is then stored as `{type_prefix}:{json}`, where `type_prefix` is the part of the GPDC type after `scalar:`.
|
|
136
136
|
|
|
137
|
-
**Field type restriction.** Every field must be encodable by a `GPDCScalarCodec` subclass (int / float / bool / str / datetime / date / time / tuple / None / any scalar-registered custom type). Container field types (`list`, `dict`, `set`, container-registered pydantic models) raise `TypeError` at encode time — use container mode instead.
|
|
137
|
+
**Field type restriction.** Every field must be encodable by a `GPDCScalarCodec` subclass (int / float / bool / str / datetime / date / time / decimal / tuple / None / any scalar-registered custom type). Container field types (`list`, `dict`, `set`, container-registered pydantic models) raise `TypeError` at encode time — use container mode instead.
|
|
138
138
|
|
|
139
139
|
### `PydanticModelScalarCodec`
|
|
140
140
|
|