structtype 0.1.0__tar.gz → 0.2.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.1.0 → structtype-0.2.0}/AGENTS.md +12 -10
- {structtype-0.1.0 → structtype-0.2.0}/Makefile +5 -2
- {structtype-0.1.0/src/structtype.egg-info → structtype-0.2.0}/PKG-INFO +1 -1
- {structtype-0.1.0 → structtype-0.2.0}/pyproject.toml +0 -16
- {structtype-0.1.0 → structtype-0.2.0}/src/structtype/__init__.py +1 -1
- {structtype-0.1.0 → structtype-0.2.0}/src/structtype/__init__.pyi +6 -22
- structtype-0.2.0/src/structtype/_adapter.py +112 -0
- {structtype-0.1.0 → structtype-0.2.0}/src/structtype/_core.c +94 -353
- {structtype-0.1.0 → structtype-0.2.0}/src/structtype/_inspect.py +65 -29
- {structtype-0.1.0 → structtype-0.2.0}/src/structtype/_version.py +2 -2
- {structtype-0.1.0 → structtype-0.2.0/src/structtype.egg-info}/PKG-INFO +1 -1
- {structtype-0.1.0 → structtype-0.2.0}/tests/unit/test_check.py +23 -23
- {structtype-0.1.0 → structtype-0.2.0}/tests/unit/test_json.py +0 -107
- {structtype-0.1.0 → structtype-0.2.0}/tests/unit/test_msgspec.py +1 -1
- {structtype-0.1.0 → structtype-0.2.0}/tests/unit/test_pydantic.py +1 -1
- {structtype-0.1.0 → structtype-0.2.0}/tests/unit/test_struct.py +0 -21
- structtype-0.1.0/src/structtype/_adapter.py +0 -34
- {structtype-0.1.0 → structtype-0.2.0}/.cibuildwheel.toml +0 -0
- {structtype-0.1.0 → structtype-0.2.0}/LICENSE +0 -0
- {structtype-0.1.0 → structtype-0.2.0}/MANIFEST.in +0 -0
- {structtype-0.1.0 → structtype-0.2.0}/README.md +0 -0
- {structtype-0.1.0 → structtype-0.2.0}/setup.cfg +0 -0
- {structtype-0.1.0 → structtype-0.2.0}/setup.py +0 -0
- {structtype-0.1.0 → structtype-0.2.0}/src/structtype/_json_schema.py +0 -0
- {structtype-0.1.0 → structtype-0.2.0}/src/structtype/_utils.py +0 -0
- {structtype-0.1.0 → structtype-0.2.0}/src/structtype/atof.h +0 -0
- {structtype-0.1.0 → structtype-0.2.0}/src/structtype/atof_consts.h +0 -0
- {structtype-0.1.0 → structtype-0.2.0}/src/structtype/common.h +0 -0
- {structtype-0.1.0 → structtype-0.2.0}/src/structtype/itoa.h +0 -0
- {structtype-0.1.0 → structtype-0.2.0}/src/structtype/py.typed +0 -0
- {structtype-0.1.0 → structtype-0.2.0}/src/structtype/ryu.h +0 -0
- {structtype-0.1.0 → structtype-0.2.0}/src/structtype.egg-info/SOURCES.txt +0 -0
- {structtype-0.1.0 → structtype-0.2.0}/src/structtype.egg-info/dependency_links.txt +0 -0
- {structtype-0.1.0 → structtype-0.2.0}/src/structtype.egg-info/top_level.txt +0 -0
- {structtype-0.1.0 → structtype-0.2.0}/tests/__init__.py +0 -0
- {structtype-0.1.0 → structtype-0.2.0}/tests/unit/__init__.py +0 -0
- {structtype-0.1.0 → structtype-0.2.0}/tests/unit/conftest.py +0 -0
- {structtype-0.1.0 → structtype-0.2.0}/tests/unit/test_JSONTestSuite.py +0 -0
- {structtype-0.1.0 → structtype-0.2.0}/tests/unit/test_adapter.py +0 -0
- {structtype-0.1.0 → structtype-0.2.0}/tests/unit/test_constraints.py +0 -0
- {structtype-0.1.0 → structtype-0.2.0}/tests/unit/test_cpylint.py +0 -0
- {structtype-0.1.0 → structtype-0.2.0}/tests/unit/test_free_threading.py +0 -0
- {structtype-0.1.0 → structtype-0.2.0}/tests/unit/test_inspect.py +0 -0
- {structtype-0.1.0 → structtype-0.2.0}/tests/unit/test_raw.py +0 -0
- {structtype-0.1.0 → structtype-0.2.0}/tests/unit/test_schema.py +0 -0
- {structtype-0.1.0 → structtype-0.2.0}/tests/unit/test_struct_meta.py +0 -0
- {structtype-0.1.0 → structtype-0.2.0}/tests/unit/test_utils.py +0 -0
- {structtype-0.1.0 → structtype-0.2.0}/tests/unit/utils.py +0 -0
|
@@ -41,22 +41,24 @@ All commands go through `make`.
|
|
|
41
41
|
- `structtype.Struct` — base class with config options (frozen, tag, rename, etc.)
|
|
42
42
|
- `structtype.Field` — field constraints (gt, ge, lt, le, min_length, etc.)
|
|
43
43
|
- `structtype.Raw` — lazy JSON passthrough
|
|
44
|
+
- `structtype.fields(type_or_instance)` — get FieldInfo tuple for a struct type/instance
|
|
44
45
|
- `structtype._inspect.type_info()` / `multi_type_info()` — type introspection
|
|
45
46
|
|
|
46
47
|
### Struct Methods
|
|
47
48
|
|
|
48
|
-
- `obj.struct_dump_json()` — serialize to JSON bytes
|
|
49
|
-
- `obj.struct_dump()` — convert to built-in Python types
|
|
50
|
-
- `obj.struct_to_dict()` — shallow field dict
|
|
51
|
-
- `obj.struct_to_tuple()` — shallow field tuple
|
|
49
|
+
- `obj.struct_dump_json(*, enc_hook=None, decimal_format=None, uuid_format=None, order=None)` — serialize to JSON bytes
|
|
50
|
+
- `obj.struct_dump()` — convert to built-in Python types (uses `encode_name` for keys)
|
|
52
51
|
- `obj.struct_force_setattr(name, value)` — set attr on frozen struct
|
|
53
|
-
- `obj.
|
|
54
|
-
- `cls.struct_validate_json(buf)` — deserialize from JSON
|
|
55
|
-
- `cls.
|
|
56
|
-
|
|
57
|
-
|
|
52
|
+
- `obj.struct_validate_self(*, strict=True, dec_hook=None)` — validate field values against types + constraints
|
|
53
|
+
- `cls.struct_validate_json(buf, *, strict=True, dec_hook=None)` — deserialize from JSON
|
|
54
|
+
- `cls.struct_validate(obj, *, strict=True, from_attributes=False, dec_hook=None)` — convert built-in types to struct
|
|
55
|
+
|
|
56
|
+
### Dict & Iteration Protocol
|
|
57
|
+
|
|
58
|
+
Struct instances support the mapping protocol:
|
|
59
|
+
- `dict(p)` — shallow dict of Python field names to values (iterates `(name, value)` pairs)
|
|
60
|
+
- `list(p)` / `iter(p)` — iterate `(name, value)` 2-tuples in declaration order
|
|
58
61
|
|
|
59
62
|
## Gotchas
|
|
60
63
|
|
|
61
|
-
- `pytest-randomly` shuffles test order
|
|
62
64
|
- `make test-lf` reinstalls the C extension before running (last-failed first)
|
|
@@ -22,6 +22,10 @@ test-cov: ## Run tests with coverage
|
|
|
22
22
|
test-lf: ## Run tests in current Python
|
|
23
23
|
uv run --reinstall pytest --lf
|
|
24
24
|
|
|
25
|
+
.PHONY: test-doc
|
|
26
|
+
test-doc: ## Run doctests
|
|
27
|
+
uv run pytest --doctest-modules --pyargs structtype
|
|
28
|
+
|
|
25
29
|
.PHONY: test-all
|
|
26
30
|
test-all: ## Run tests in all supporte Python versions
|
|
27
31
|
for py_v in $(PY_VERSIONS); do \
|
|
@@ -49,8 +53,7 @@ format: ## Format files using ruff format
|
|
|
49
53
|
##@ Benchmark
|
|
50
54
|
.PHONY: bench
|
|
51
55
|
bench: ## run benchmarks
|
|
52
|
-
uv run
|
|
53
|
-
uv run --group bench benchmarks/bench_libs.py
|
|
56
|
+
uv run benchmarks/bench_libs.py
|
|
54
57
|
|
|
55
58
|
##@ Utility
|
|
56
59
|
.PHONY: clean
|
|
@@ -46,17 +46,6 @@ dev = [
|
|
|
46
46
|
"pytest>=9",
|
|
47
47
|
"pytest-coverage>=0.0",
|
|
48
48
|
]
|
|
49
|
-
bench = [
|
|
50
|
-
"cattrs==25.3.0",
|
|
51
|
-
"mashumaro==3.17",
|
|
52
|
-
"msgspec",
|
|
53
|
-
"orjson==3.11.4",
|
|
54
|
-
"pydantic==2.12.4",
|
|
55
|
-
"pysimdjson==7.0.2",
|
|
56
|
-
"python-rapidjson==1.22",
|
|
57
|
-
"requests",
|
|
58
|
-
"ujson==5.11.0",
|
|
59
|
-
]
|
|
60
49
|
docs = [
|
|
61
50
|
"furo",
|
|
62
51
|
"ipython",
|
|
@@ -65,11 +54,6 @@ docs = [
|
|
|
65
54
|
"sphinx-copybutton",
|
|
66
55
|
"sphinx-design",
|
|
67
56
|
]
|
|
68
|
-
hooks = [
|
|
69
|
-
"codespell==2.4.1",
|
|
70
|
-
"prek>=0.2.13",
|
|
71
|
-
"ruff==0.15.16",
|
|
72
|
-
]
|
|
73
57
|
|
|
74
58
|
[build-system]
|
|
75
59
|
requires = [
|
|
@@ -11,7 +11,7 @@ from ._core import (
|
|
|
11
11
|
UnsetType,
|
|
12
12
|
ValidationError,
|
|
13
13
|
)
|
|
14
|
-
from ._adapter import StructAdapter
|
|
14
|
+
from ._adapter import StrAdapter, StructAdapter
|
|
15
15
|
from ._inspect import FieldInfo, fields
|
|
16
16
|
from ._json_schema import json_schema, json_schema_dump, json_schema_components
|
|
17
17
|
from ._version import __version__
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import enum
|
|
2
|
-
from collections.abc import Callable, Iterable, Mapping
|
|
2
|
+
from collections.abc import Callable, Iterable, Iterator, Mapping
|
|
3
3
|
from inspect import Signature
|
|
4
4
|
from typing import (
|
|
5
5
|
Any,
|
|
@@ -115,6 +115,7 @@ class Struct(metaclass=StructMeta):
|
|
|
115
115
|
) -> None: ...
|
|
116
116
|
def __rich_repr__(self) -> list[tuple[str, Any]]: ...
|
|
117
117
|
def __replace__(self, **changes: Any) -> Self: ...
|
|
118
|
+
def __iter__(self) -> Iterator[tuple[str, Any]]: ...
|
|
118
119
|
def struct_dump_json(
|
|
119
120
|
self,
|
|
120
121
|
*,
|
|
@@ -124,10 +125,8 @@ class Struct(metaclass=StructMeta):
|
|
|
124
125
|
order: Literal["deterministic", "sorted"] | None = None,
|
|
125
126
|
) -> bytes: ...
|
|
126
127
|
def struct_dump(self) -> dict[str, Any]: ...
|
|
127
|
-
def struct_to_dict(self) -> dict[str, Any]: ...
|
|
128
|
-
def struct_to_tuple(self) -> tuple[Any, ...]: ...
|
|
129
128
|
def struct_force_setattr(self, name: str, value: Any) -> None: ...
|
|
130
|
-
def
|
|
129
|
+
def struct_validate_self(
|
|
131
130
|
self,
|
|
132
131
|
*,
|
|
133
132
|
strict: bool = True,
|
|
@@ -150,24 +149,6 @@ class Struct(metaclass=StructMeta):
|
|
|
150
149
|
from_attributes: bool = False,
|
|
151
150
|
dec_hook: Callable[[type[Any], Any], Any] | None = None,
|
|
152
151
|
) -> _T: ...
|
|
153
|
-
@classmethod
|
|
154
|
-
def struct_validate_jsonln(
|
|
155
|
-
cls: type[_T],
|
|
156
|
-
buf: str | Buffer,
|
|
157
|
-
*,
|
|
158
|
-
strict: bool = True,
|
|
159
|
-
dec_hook: Callable[[type[Any], Any], Any] | None = None,
|
|
160
|
-
) -> list[_T]: ...
|
|
161
|
-
@classmethod
|
|
162
|
-
def struct_dump_jsonln(
|
|
163
|
-
cls,
|
|
164
|
-
items: Iterable[Any],
|
|
165
|
-
*,
|
|
166
|
-
enc_hook: Callable[[Any], Any] | None = None,
|
|
167
|
-
decimal_format: Literal["string", "number"] | Callable[[Any], Any] | None = None,
|
|
168
|
-
uuid_format: Literal["canonical", "hex"] | None = None,
|
|
169
|
-
order: Literal["deterministic", "sorted"] | None = None,
|
|
170
|
-
) -> bytes: ...
|
|
171
152
|
|
|
172
153
|
# Lie and say `Raw` is a subclass of `bytes`, so mypy will accept it in most
|
|
173
154
|
# places where an object that implements the buffer protocol is valid
|
|
@@ -354,6 +335,9 @@ class StructAdapter:
|
|
|
354
335
|
) -> Any: ...
|
|
355
336
|
def struct_dump(self, obj: Any) -> Any: ...
|
|
356
337
|
|
|
338
|
+
class StrAdapter:
|
|
339
|
+
def __new__(cls, type: type[Any]) -> type[str]: ...
|
|
340
|
+
|
|
357
341
|
class EncodeError(ValueError): ...
|
|
358
342
|
class DecodeError(ValueError): ...
|
|
359
343
|
class ValidationError(ValueError): ...
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from ._core import _convert, json_decode, json_encode
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class StructAdapter:
|
|
7
|
+
"""Adapter for validating and serializing types without subclassing ``Struct``.
|
|
8
|
+
|
|
9
|
+
Useful when you want to validate or serialize plain Python types
|
|
10
|
+
(e.g. ``list[int]``) without defining a full ``Struct`` subclass.
|
|
11
|
+
|
|
12
|
+
>>> from structtype import StructAdapter
|
|
13
|
+
>>> adapter = StructAdapter(list[int])
|
|
14
|
+
>>> adapter.struct_validate_json(b"[1, 2, 3]")
|
|
15
|
+
[1, 2, 3]
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
__slots__ = ("_type",)
|
|
19
|
+
|
|
20
|
+
def __init__(self, type: Any):
|
|
21
|
+
self._type = type
|
|
22
|
+
|
|
23
|
+
def struct_validate_json(self, buf, *, strict=True, dec_hook=None):
|
|
24
|
+
"""Validate JSON bytes and decode into the adapter's type.
|
|
25
|
+
|
|
26
|
+
Parameters
|
|
27
|
+
----------
|
|
28
|
+
buf : str or bytes
|
|
29
|
+
The JSON message to decode.
|
|
30
|
+
strict : bool, optional
|
|
31
|
+
If True (default), unmatched fields cause an error.
|
|
32
|
+
dec_hook : callable, optional
|
|
33
|
+
A callback for customizing decoding of specific types.
|
|
34
|
+
"""
|
|
35
|
+
return json_decode(buf, type=self._type, strict=strict, dec_hook=dec_hook)
|
|
36
|
+
|
|
37
|
+
def struct_dump_json(self, obj, *, enc_hook=None, decimal_format=None, uuid_format=None, order=None):
|
|
38
|
+
"""Encode a validated object to JSON bytes.
|
|
39
|
+
|
|
40
|
+
Parameters
|
|
41
|
+
----------
|
|
42
|
+
obj : Any
|
|
43
|
+
A value to encode. Must match the adapter's type.
|
|
44
|
+
enc_hook : callable, optional
|
|
45
|
+
A callback for customizing encoding of specific types.
|
|
46
|
+
decimal_format : str or callable, optional
|
|
47
|
+
Controls how ``Decimal`` values are encoded.
|
|
48
|
+
uuid_format : str, optional
|
|
49
|
+
Controls how ``UUID`` values are encoded.
|
|
50
|
+
order : str, optional
|
|
51
|
+
Determines key ordering in JSON objects.
|
|
52
|
+
"""
|
|
53
|
+
return json_encode(obj, enc_hook=enc_hook, decimal_format=decimal_format, uuid_format=uuid_format, order=order)
|
|
54
|
+
|
|
55
|
+
def struct_validate(
|
|
56
|
+
self, obj, *, strict=True, dec_hook=None, from_attributes=False
|
|
57
|
+
):
|
|
58
|
+
"""Validate a Python object against the adapter's type.
|
|
59
|
+
|
|
60
|
+
Parameters
|
|
61
|
+
----------
|
|
62
|
+
obj : Any
|
|
63
|
+
A Python object to validate and convert.
|
|
64
|
+
strict : bool, optional
|
|
65
|
+
If True (default), unmatched fields cause an error.
|
|
66
|
+
dec_hook : callable, optional
|
|
67
|
+
A callback for customizing decoding of specific types.
|
|
68
|
+
from_attributes : bool, optional
|
|
69
|
+
If True, accept objects with attributes instead of dict keys.
|
|
70
|
+
"""
|
|
71
|
+
return _convert(
|
|
72
|
+
obj,
|
|
73
|
+
self._type,
|
|
74
|
+
strict=strict,
|
|
75
|
+
dec_hook=dec_hook,
|
|
76
|
+
from_attributes=from_attributes,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
def struct_dump(self, obj):
|
|
80
|
+
"""Convert a validated object to built-in Python types (``dict``, ``list``, etc.)."""
|
|
81
|
+
if hasattr(obj, "struct_dump"):
|
|
82
|
+
return obj.struct_dump()
|
|
83
|
+
if hasattr(obj, "model_dump"):
|
|
84
|
+
return obj.model_dump()
|
|
85
|
+
return obj
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class StrAdapter:
|
|
89
|
+
"""Create a ``str`` subclass wrapper for validating a type during
|
|
90
|
+
structtype serialization.
|
|
91
|
+
|
|
92
|
+
Wraps a type that has a single-argument string constructor
|
|
93
|
+
(e.g. ``HttpUrl``, ``EmailStr``, ``IPv4Address``) into a ``str``
|
|
94
|
+
subclass. The wrapped value is stored as a string but validated by
|
|
95
|
+
calling ``typ(value)`` on construction. During structtype
|
|
96
|
+
validation and serialization, the wrapper is treated as a native
|
|
97
|
+
``str``.
|
|
98
|
+
|
|
99
|
+
>>> from structtype import StrAdapter, Struct
|
|
100
|
+
>>> from ipaddress import IPv4Address
|
|
101
|
+
>>>
|
|
102
|
+
>>> class Config(Struct):
|
|
103
|
+
... ip: StrAdapter(IPv4Address)
|
|
104
|
+
"""
|
|
105
|
+
|
|
106
|
+
__slots__ = ()
|
|
107
|
+
|
|
108
|
+
def __new__(cls, typ):
|
|
109
|
+
return type(f"_Wrapped_{typ.__name__}", (str,), {
|
|
110
|
+
"__slots__": (),
|
|
111
|
+
"__new__": lambda self, v: str.__new__(self, str(typ(v))),
|
|
112
|
+
})
|