structtype 0.13.0__tar.gz → 0.15.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.
- {structtype-0.13.0 → structtype-0.15.0}/AGENTS.md +18 -15
- {structtype-0.13.0 → structtype-0.15.0}/MANIFEST.in +0 -1
- {structtype-0.13.0 → structtype-0.15.0}/Makefile +23 -11
- {structtype-0.13.0 → structtype-0.15.0}/PKG-INFO +1 -1
- {structtype-0.13.0 → structtype-0.15.0}/src/structtype/__init__.pyi +21 -12
- {structtype-0.13.0 → structtype-0.15.0}/src/structtype/_adapter.py +20 -0
- {structtype-0.13.0 → structtype-0.15.0}/src/structtype/_config.py +2 -1
- {structtype-0.13.0 → structtype-0.15.0}/src/structtype/_core.c +1831 -787
- {structtype-0.13.0 → structtype-0.15.0}/src/structtype/_inspect.py +80 -4
- {structtype-0.13.0 → structtype-0.15.0}/src/structtype/_json_schema.py +55 -9
- {structtype-0.13.0 → structtype-0.15.0}/src/structtype/_version.py +2 -2
- {structtype-0.13.0 → structtype-0.15.0}/src/structtype/atof.h +5 -0
- {structtype-0.13.0 → structtype-0.15.0}/src/structtype.egg-info/PKG-INFO +1 -1
- {structtype-0.13.0 → structtype-0.15.0}/src/structtype.egg-info/SOURCES.txt +4 -2
- structtype-0.15.0/src/structtype.egg-info/scm_file_list.json +97 -0
- {structtype-0.13.0 → structtype-0.15.0}/tests/test_adapter.py +31 -0
- {structtype-0.13.0 → structtype-0.15.0}/tests/test_annotations.py +36 -13
- {structtype-0.13.0 → structtype-0.15.0}/tests/test_check.py +80 -4
- {structtype-0.13.0 → structtype-0.15.0}/tests/test_constraints.py +1 -1
- {structtype-0.13.0 → structtype-0.15.0}/tests/test_csv.py +45 -0
- {structtype-0.13.0 → structtype-0.15.0}/tests/test_free_threading.py +9 -72
- {structtype-0.13.0 → structtype-0.15.0}/tests/test_inspect.py +14 -0
- structtype-0.15.0/tests/test_ipnetwork.py +196 -0
- {structtype-0.13.0 → structtype-0.15.0}/tests/test_json.py +286 -0
- structtype-0.15.0/tests/test_protocols.py +181 -0
- structtype-0.15.0/tests/test_purepath.py +155 -0
- {structtype-0.13.0 → structtype-0.15.0}/tests/test_roundtrip.py +16 -0
- {structtype-0.13.0 → structtype-0.15.0}/tests/test_schema.py +102 -0
- {structtype-0.13.0 → structtype-0.15.0}/tests/test_struct.py +93 -19
- {structtype-0.13.0 → structtype-0.15.0}/tests/test_struct_meta.py +170 -7
- {structtype-0.13.0 → structtype-0.15.0}/tests/typecheck/basic.py +15 -0
- {structtype-0.13.0 → structtype-0.15.0}/tests/typecheck/fields.py +21 -0
- {structtype-0.13.0 → structtype-0.15.0}/uv.lock +401 -349
- structtype-0.13.0/.opencode/opencode.json +0 -6
- structtype-0.13.0/.opencode/plugins/graphify.js +0 -30
- {structtype-0.13.0 → structtype-0.15.0}/LICENSE +0 -0
- {structtype-0.13.0 → structtype-0.15.0}/README.md +0 -0
- {structtype-0.13.0 → structtype-0.15.0}/pyproject.toml +0 -0
- {structtype-0.13.0 → structtype-0.15.0}/setup.cfg +0 -0
- {structtype-0.13.0 → structtype-0.15.0}/setup.py +0 -0
- {structtype-0.13.0 → structtype-0.15.0}/src/structtype/__init__.py +0 -0
- {structtype-0.13.0 → structtype-0.15.0}/src/structtype/_utils.py +0 -0
- {structtype-0.13.0 → structtype-0.15.0}/src/structtype/atof_consts.h +0 -0
- {structtype-0.13.0 → structtype-0.15.0}/src/structtype/common.h +0 -0
- {structtype-0.13.0 → structtype-0.15.0}/src/structtype/itoa.h +0 -0
- {structtype-0.13.0 → structtype-0.15.0}/src/structtype/py.typed +0 -0
- {structtype-0.13.0 → structtype-0.15.0}/src/structtype/ryu.h +0 -0
- {structtype-0.13.0 → structtype-0.15.0}/src/structtype.egg-info/dependency_links.txt +0 -0
- {structtype-0.13.0 → structtype-0.15.0}/src/structtype.egg-info/top_level.txt +0 -0
- {structtype-0.13.0 → structtype-0.15.0}/tests/__init__.py +0 -0
- {structtype-0.13.0 → structtype-0.15.0}/tests/conftest.py +0 -0
- {structtype-0.13.0 → structtype-0.15.0}/tests/test_JSONTestSuite.py +0 -0
- {structtype-0.13.0 → structtype-0.15.0}/tests/test_attrs.py +0 -0
- {structtype-0.13.0 → structtype-0.15.0}/tests/test_cpylint.py +0 -0
- {structtype-0.13.0 → structtype-0.15.0}/tests/test_frozendict.py +0 -0
- {structtype-0.13.0 → structtype-0.15.0}/tests/test_memory.py +0 -0
- {structtype-0.13.0 → structtype-0.15.0}/tests/test_msgspec.py +0 -0
- {structtype-0.13.0 → structtype-0.15.0}/tests/test_pydantic.py +0 -0
- {structtype-0.13.0 → structtype-0.15.0}/tests/test_threading.py +0 -0
- {structtype-0.13.0 → structtype-0.15.0}/tests/test_typecheck.py +0 -0
- {structtype-0.13.0 → structtype-0.15.0}/tests/test_utils.py +0 -0
- {structtype-0.13.0 → structtype-0.15.0}/tests/typecheck/api.py +0 -0
- {structtype-0.13.0 → structtype-0.15.0}/tests/typecheck/types.py +0 -0
- {structtype-0.13.0 → structtype-0.15.0}/tests/utils.py +0 -0
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
## Project
|
|
4
4
|
|
|
5
5
|
Fast struct validation + JSON serialization for Python.
|
|
6
|
-
Core is a monolithic C extension (`src/structtype/_core.c`, ~
|
|
6
|
+
Core is a monolithic C extension (`src/structtype/_core.c`, ~22K lines).
|
|
7
7
|
No runtime deps.
|
|
8
8
|
|
|
9
9
|
## Setup
|
|
@@ -25,28 +25,41 @@ Use `make` targets where available. Targeted tests can be run directly with
|
|
|
25
25
|
| Coverage (Python + C) | `make test-cov-c` |
|
|
26
26
|
| Coverage (Python + C, all Pythons, merged) | `make test-cov-c-all` |
|
|
27
27
|
| Tests in all supported Pythons | `make test-all` |
|
|
28
|
+
| Doctests | `make test-doc` |
|
|
29
|
+
| Build sdist + wheel | `make build` |
|
|
28
30
|
| Build docs | `make docs` |
|
|
29
31
|
| Format | `make format` |
|
|
30
32
|
| Lint | `make ruff-check` |
|
|
31
33
|
| Type check | `make type-check` |
|
|
32
34
|
| Static checks | `make check` |
|
|
33
35
|
|
|
36
|
+
Benchmark targets: `make bench`, `make bench-validators`, `make bench-codecs`,
|
|
37
|
+
`make bench-field-types`, `make bench-csv-1m`, `make bench-strings`
|
|
38
|
+
(run sequentially; CPU-bound; never in parallel).
|
|
39
|
+
|
|
40
|
+
Benchmarks run on free-threaded CPython (3.15t) via `BENCH_PYTHON` (override
|
|
41
|
+
with `make bench BENCH_PYTHON=3.15`); see `docs/benchmarks.rst`.
|
|
42
|
+
|
|
34
43
|
## Conventions
|
|
35
44
|
|
|
36
45
|
- **88-char lines**, formatted with `ruff format`
|
|
37
|
-
- `ruff check` with
|
|
46
|
+
- `ruff check` with Ruff's default rule set (the project config sets only
|
|
47
|
+
`target-version` and isort `combine-as-imports`; line length is enforced by
|
|
48
|
+
`ruff format`, not `E501`)
|
|
38
49
|
- Private modules/functions prefixed with `_`
|
|
39
50
|
- C code uses `ms_`/`MS_` prefix
|
|
40
51
|
- Type stubs (`.pyi`) alongside public modules
|
|
41
52
|
- Sentinel values: `NODEFAULT`, `UNSET`, `_NoDefault`, `UnsetType`
|
|
42
53
|
- never do git commit
|
|
43
54
|
- check for performance regressions, speed is a goal
|
|
55
|
+
- no parallel benchmarks
|
|
56
|
+
- keep C code simple, fast, readable and compatible to freethreading
|
|
44
57
|
|
|
45
58
|
## Key API
|
|
46
59
|
|
|
47
60
|
- `structtype.Struct` — base class with config options (frozen, tag, rename, etc.)
|
|
48
61
|
- `structtype.Field` — field metadata (alias, title, description, examples, deprecated, json_schema_extra)
|
|
49
|
-
- `structtype.Constraint` — base constraint (callable `
|
|
62
|
+
- `structtype.Constraint` — base constraint (callable `_fn`, positional-only in the constructor); subclasses: `NumericConstraint`, `StrConstraint`, `BytesConstraint`, `CollectionConstraint`, `TimezoneConstraint`
|
|
50
63
|
- `structtype.Serializer` — load/dump codecs for supported custom and native types
|
|
51
64
|
- `structtype.fields(type_or_instance)` — get FieldInfo tuple for a struct type/instance
|
|
52
65
|
- `structtype._inspect.type_info()` / `multi_type_info()` — type introspection
|
|
@@ -59,6 +72,8 @@ Use `make` targets where available. Targeted tests can be run directly with
|
|
|
59
72
|
- `obj.struct_check_types()` — validate field values against types + constraints (pure type-check, no conversion)
|
|
60
73
|
- `cls.struct_validate_json(buf, *, strict=True)` — deserialize from JSON
|
|
61
74
|
- `cls.struct_validate(obj, *, strict=True, from_attributes=False)` — convert built-in types to struct
|
|
75
|
+
- `obj.struct_dump_csv()` — encode one row as `list[str]` for `csv.writer.writerow()`; flat scalar fields only, positional
|
|
76
|
+
- `cls.struct_validate_csv(row, *, null_values=("",))` — decode one CSV row (sequence of cell strings); always lax
|
|
62
77
|
|
|
63
78
|
### Dict & Iteration Protocol
|
|
64
79
|
|
|
@@ -75,15 +90,3 @@ Struct instances support the mapping protocol:
|
|
|
75
90
|
- Validation matches keys by the **alias** name only, except `struct_validate(obj, from_attributes=True)` on a **non-dict object**, which matches by both the python field name and the alias. Dict/JSON input (even with `from_attributes=True`) and all dump/serialization use only the alias name.
|
|
76
91
|
- For `Annotated[MyType, Serializer(load=...)]`, an existing `MyType` instance bypasses `load`; other Python values and JSON representations are passed to `load`. Serializers remain rejected on `Any`, `bool`, `int`, `float`, `str`, `list`, `dict`, `tuple`, `TypedDict`, `NamedTuple`, `frozendict`, and `Literal`. `Serializer` and `Constraint` must attach to a concrete type inside `Annotated` — `Annotated[Union[A, B], ...]` and `Annotated[Optional[T], ...]` are rejected; use `Annotated[T, ...] | None` for optional fields.
|
|
77
92
|
|
|
78
|
-
## graphify
|
|
79
|
-
|
|
80
|
-
This project has a knowledge graph at graphify-out/ with god nodes, community structure, and cross-file relationships.
|
|
81
|
-
|
|
82
|
-
When the user types `/graphify`, use the installed graphify skill or instructions before doing anything else.
|
|
83
|
-
|
|
84
|
-
Rules:
|
|
85
|
-
- For codebase questions, first run `graphify query "<question>"` when graphify-out/graph.json exists. Use `graphify path "<A>" "<B>"` for relationships and `graphify explain "<concept>"` for focused concepts. These return a scoped subgraph, usually much smaller than GRAPH_REPORT.md or raw grep output.
|
|
86
|
-
- Dirty graphify-out/ files are expected after hooks or incremental updates; dirty graph files are not a reason to skip graphify. Only skip graphify if the task is about stale or incorrect graph output, or the user explicitly says not to use it.
|
|
87
|
-
- If graphify-out/wiki/index.md exists, use it for broad navigation instead of raw source browsing.
|
|
88
|
-
- Read graphify-out/GRAPH_REPORT.md only for broad architecture review or when query/path/explain do not surface enough context.
|
|
89
|
-
- After modifying code, run `graphify update .` to keep the graph current (AST-only, no API cost).
|
|
@@ -6,7 +6,8 @@ export UV_MANAGED_PYTHON ?= 1
|
|
|
6
6
|
##@ Build
|
|
7
7
|
.PHONY: build
|
|
8
8
|
build: ## Build
|
|
9
|
-
uv build
|
|
9
|
+
uv build --sdist
|
|
10
|
+
uv build --wheel
|
|
10
11
|
|
|
11
12
|
.PHONY: docs
|
|
12
13
|
docs: ## build docs
|
|
@@ -107,9 +108,7 @@ test-debug: ## Build core with Py_DEBUG + ASan/UBSan + debug allocator and run a
|
|
|
107
108
|
$(DEBUG_VENV)/bin/python -m pytest
|
|
108
109
|
|
|
109
110
|
.PHONY: check
|
|
110
|
-
check: ## Run all checks
|
|
111
|
-
-uvx ty check ${SOURCE_DIR}
|
|
112
|
-
uvx ruff check ${SOURCE_DIR}
|
|
111
|
+
check: ruff-check type-check ## Run all checks
|
|
113
112
|
|
|
114
113
|
.PHONY: ruff-check
|
|
115
114
|
ruff-check: ## Lint using ruff
|
|
@@ -117,8 +116,7 @@ ruff-check: ## Lint using ruff
|
|
|
117
116
|
|
|
118
117
|
.PHONY: type-check
|
|
119
118
|
type-check: ## Type check with
|
|
120
|
-
|
|
121
|
-
uvx pyrefly check ${SOURCE_DIR}
|
|
119
|
+
uvx ty check ${SOURCE_DIR}
|
|
122
120
|
|
|
123
121
|
.PHONY: typecheck-tests
|
|
124
122
|
typecheck-tests: ## Type check tests/typecheck fixtures (skipped when tools unavailable)
|
|
@@ -131,21 +129,34 @@ format: ## Format files using ruff format
|
|
|
131
129
|
uvx ruff format ${SOURCE_DIR}
|
|
132
130
|
|
|
133
131
|
##@ Benchmark
|
|
132
|
+
# Published comparisons use free-threaded CPython (see docs/benchmarks.rst).
|
|
133
|
+
# Benchmark scripts are PEP 723 scripts, so this builds an isolated per-script
|
|
134
|
+
# environment and does not touch the project `.venv`.
|
|
135
|
+
BENCH_PYTHON ?= 3.15t
|
|
136
|
+
|
|
134
137
|
.PHONY: bench
|
|
135
138
|
bench: ## run benchmarks
|
|
136
|
-
uv run -p
|
|
139
|
+
uv run -p $(BENCH_PYTHON) benchmarks/bench_libs.py
|
|
137
140
|
|
|
138
141
|
.PHONY: bench-validators
|
|
139
142
|
bench-validators: ## run Serializer/Validator benchmarks
|
|
140
|
-
uv run -p
|
|
143
|
+
uv run -p $(BENCH_PYTHON) benchmarks/bench_validators.py
|
|
141
144
|
|
|
142
145
|
.PHONY: bench-codecs
|
|
143
146
|
bench-codecs: ## run Constraint + Serializer heavy benchmarks
|
|
144
|
-
uv run -p
|
|
147
|
+
uv run -p $(BENCH_PYTHON) benchmarks/bench_codecs.py
|
|
148
|
+
|
|
149
|
+
.PHONY: bench-field-types
|
|
150
|
+
bench-field-types: ## run per-field-type benchmarks
|
|
151
|
+
uv run -p $(BENCH_PYTHON) benchmarks/bench_field_types.py
|
|
145
152
|
|
|
146
153
|
.PHONY: bench-csv-1m
|
|
147
154
|
bench-csv-1m: ## run 1M-row on-disk CSV benchmark
|
|
148
|
-
uv run -p
|
|
155
|
+
uv run -p $(BENCH_PYTHON) benchmarks/bench_csv_1m.py
|
|
156
|
+
|
|
157
|
+
.PHONY: bench-strings
|
|
158
|
+
bench-strings: ## run long-string JSON codec benchmark
|
|
159
|
+
uv run -p $(BENCH_PYTHON) benchmarks/bench_strings.py
|
|
149
160
|
|
|
150
161
|
##@ Utility
|
|
151
162
|
.PHONY: clean
|
|
@@ -161,6 +172,7 @@ clean: ## Delete all temporary files
|
|
|
161
172
|
rm -rf htmlcov-c
|
|
162
173
|
rm -f .coverage
|
|
163
174
|
rm -f coverage-c.info coverage-c.info.*
|
|
175
|
+
rm -rf .venv*
|
|
164
176
|
|
|
165
177
|
.PHONY: install
|
|
166
178
|
install: install-uv ## Install virtual environment
|
|
@@ -187,4 +199,4 @@ update-python: ## Reinstall managed Python versions to latest release
|
|
|
187
199
|
|
|
188
200
|
.PHONY: help
|
|
189
201
|
help: ## Display this help
|
|
190
|
-
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make <target>\033[36m\033[0m\n"} /^[a-zA-
|
|
202
|
+
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make <target>\033[36m\033[0m\n"} /^[a-zA-Z0-9_-]+:.*?##/ { printf " \033[36m%-18s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
# ruff: noqa: PYI041, PYI015
|
|
2
|
-
import enum
|
|
3
2
|
from collections.abc import Callable, Iterable, Iterator, Mapping, Sequence
|
|
4
3
|
from inspect import Signature
|
|
5
4
|
from typing import (
|
|
@@ -16,17 +15,15 @@ from typing import (
|
|
|
16
15
|
from typing_extensions import Buffer, Self, dataclass_transform
|
|
17
16
|
|
|
18
17
|
@final
|
|
19
|
-
class UnsetType
|
|
20
|
-
UNSET = "UNSET"
|
|
18
|
+
class UnsetType:
|
|
21
19
|
def __bool__(self) -> Literal[False]: ...
|
|
22
20
|
|
|
23
|
-
UNSET: Final
|
|
21
|
+
UNSET: Final[UnsetType]
|
|
24
22
|
|
|
25
23
|
@final
|
|
26
|
-
class
|
|
27
|
-
NODEFAULT = "NODEFAULT"
|
|
24
|
+
class _NoDefaultType: ...
|
|
28
25
|
|
|
29
|
-
NODEFAULT: Final
|
|
26
|
+
NODEFAULT: Final[_NoDefaultType]
|
|
30
27
|
|
|
31
28
|
class StructConfig(TypedDict, total=False):
|
|
32
29
|
frozen: bool
|
|
@@ -59,8 +56,6 @@ class StructMeta(type):
|
|
|
59
56
|
def __signature__(self) -> Signature: ...
|
|
60
57
|
@property
|
|
61
58
|
def __struct_config__(self) -> StructConfig: ...
|
|
62
|
-
@property
|
|
63
|
-
def struct_config(self) -> StructConfig: ...
|
|
64
59
|
|
|
65
60
|
@dataclass_transform(field_specifiers=("Field",)) # type: ignore
|
|
66
61
|
class Struct(metaclass=StructMeta):
|
|
@@ -70,6 +65,7 @@ class Struct(metaclass=StructMeta):
|
|
|
70
65
|
__struct_alias_fields__: ClassVar[tuple[str, ...]]
|
|
71
66
|
__struct_defaults__: ClassVar[tuple[Any, ...]]
|
|
72
67
|
__match_args__: ClassVar[tuple[str, ...]] = ...
|
|
68
|
+
__slots__: ClassVar[tuple[str, ...]] = ...
|
|
73
69
|
# A default __init__ so that Structs with unknown field types
|
|
74
70
|
# won't error on every call to `__init__`
|
|
75
71
|
def __init__(self, *args: Any, **kwargs: Any) -> None: ...
|
|
@@ -78,6 +74,14 @@ class Struct(metaclass=StructMeta):
|
|
|
78
74
|
def __reduce__(self) -> tuple: ...
|
|
79
75
|
def __replace__(self, **changes: Any) -> Self: ...
|
|
80
76
|
def __iter__(self) -> Iterator[tuple[str, Any]]: ...
|
|
77
|
+
# Generated only when `struct_config = StructConfig(order=True)`. They are
|
|
78
|
+
# declared unconditionally because the class-body config dict is invisible
|
|
79
|
+
# to `@dataclass_transform`. A different struct type is never orderable
|
|
80
|
+
# against this one, hence `Self`.
|
|
81
|
+
def __lt__(self, other: Self) -> bool: ...
|
|
82
|
+
def __le__(self, other: Self) -> bool: ...
|
|
83
|
+
def __gt__(self, other: Self) -> bool: ...
|
|
84
|
+
def __ge__(self, other: Self) -> bool: ...
|
|
81
85
|
def struct_dump_json(
|
|
82
86
|
self,
|
|
83
87
|
*,
|
|
@@ -122,7 +126,7 @@ _NonNegativeInt: TypeAlias = int
|
|
|
122
126
|
|
|
123
127
|
@final
|
|
124
128
|
class Factory:
|
|
125
|
-
def __new__(cls, factory: Callable[[], Any]) -> Any: ...
|
|
129
|
+
def __new__(cls, factory: Callable[[], Any], /) -> Any: ...
|
|
126
130
|
factory: Final[Callable[[], Any]]
|
|
127
131
|
|
|
128
132
|
@final
|
|
@@ -157,7 +161,12 @@ class Serializer:
|
|
|
157
161
|
dump: Final[Callable[[Any], Any] | None]
|
|
158
162
|
|
|
159
163
|
class Constraint:
|
|
160
|
-
|
|
164
|
+
# `_fn` is positional-only and stored privately: it is not part of the
|
|
165
|
+
# public interface. The callable follows the `Constraint.__call__`
|
|
166
|
+
# protocol — it receives the value and signals failure by raising, so its
|
|
167
|
+
# return type is `None`.
|
|
168
|
+
def __init__(self, fn: Callable[[Any], None] | None = None, /) -> None: ...
|
|
169
|
+
_fn: Final[Callable[[Any], None] | None]
|
|
161
170
|
def __call__(self, value: Any) -> None: ...
|
|
162
171
|
|
|
163
172
|
@final
|
|
@@ -243,7 +252,7 @@ class CollectionConstraint(Constraint):
|
|
|
243
252
|
|
|
244
253
|
@final
|
|
245
254
|
class TimezoneConstraint(Constraint):
|
|
246
|
-
def __init__(self,
|
|
255
|
+
def __init__(self, tz: bool) -> None: ...
|
|
247
256
|
tz: Final[bool]
|
|
248
257
|
|
|
249
258
|
class FieldInfo(Struct):
|
|
@@ -62,6 +62,22 @@ def _has_constraint_on_union(ann):
|
|
|
62
62
|
return any(_has_constraint_on_union(arg) for arg in get_args(ann))
|
|
63
63
|
|
|
64
64
|
|
|
65
|
+
def _has_constraint(ann):
|
|
66
|
+
"""True if the annotation carries a ``Constraint`` anywhere."""
|
|
67
|
+
metadata = getattr(ann, "__metadata__", None)
|
|
68
|
+
if metadata is not None:
|
|
69
|
+
for meta in metadata:
|
|
70
|
+
if isinstance(meta, _Constraint):
|
|
71
|
+
return True
|
|
72
|
+
supertype = getattr(ann, "__supertype__", None) # NewType
|
|
73
|
+
if supertype is not None and _has_constraint(supertype):
|
|
74
|
+
return True
|
|
75
|
+
value = getattr(ann, "__value__", None) # PEP 695 type alias
|
|
76
|
+
if value is not None and _has_constraint(value):
|
|
77
|
+
return True
|
|
78
|
+
return any(_has_constraint(arg) for arg in get_args(ann))
|
|
79
|
+
|
|
80
|
+
|
|
65
81
|
class StructAdapter:
|
|
66
82
|
"""Adapter for validating and serializing types without subclassing ``Struct``.
|
|
67
83
|
|
|
@@ -101,6 +117,10 @@ class StructAdapter:
|
|
|
101
117
|
"for optional fields"
|
|
102
118
|
)
|
|
103
119
|
self._type = type
|
|
120
|
+
# Validate constraint/type compatibility eagerly, matching `Struct`
|
|
121
|
+
# class creation, without disturbing the lazy decoder cache below.
|
|
122
|
+
if _has_constraint(type):
|
|
123
|
+
_JSONDecoder(type, strict=True)
|
|
104
124
|
self._decoder_loose = None
|
|
105
125
|
self._decoder_strict = None
|
|
106
126
|
|
|
@@ -7,7 +7,8 @@ class StructConfig(TypedDict, total=False):
|
|
|
7
7
|
|
|
8
8
|
Used as a class-body ``struct_config`` attribute. Keys not present inherit
|
|
9
9
|
from the base class; ``__struct_config__`` returns the fully-resolved dict.
|
|
10
|
-
``struct_config`` returns exactly what the user wrote (sparse dict)
|
|
10
|
+
``struct_config`` returns exactly what the user wrote (sparse dict), and is
|
|
11
|
+
always present — it defaults to ``{}`` when never declared.
|
|
11
12
|
"""
|
|
12
13
|
|
|
13
14
|
frozen: bool
|