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.
Files changed (48) hide show
  1. {structtype-0.1.0 → structtype-0.2.0}/AGENTS.md +12 -10
  2. {structtype-0.1.0 → structtype-0.2.0}/Makefile +5 -2
  3. {structtype-0.1.0/src/structtype.egg-info → structtype-0.2.0}/PKG-INFO +1 -1
  4. {structtype-0.1.0 → structtype-0.2.0}/pyproject.toml +0 -16
  5. {structtype-0.1.0 → structtype-0.2.0}/src/structtype/__init__.py +1 -1
  6. {structtype-0.1.0 → structtype-0.2.0}/src/structtype/__init__.pyi +6 -22
  7. structtype-0.2.0/src/structtype/_adapter.py +112 -0
  8. {structtype-0.1.0 → structtype-0.2.0}/src/structtype/_core.c +94 -353
  9. {structtype-0.1.0 → structtype-0.2.0}/src/structtype/_inspect.py +65 -29
  10. {structtype-0.1.0 → structtype-0.2.0}/src/structtype/_version.py +2 -2
  11. {structtype-0.1.0 → structtype-0.2.0/src/structtype.egg-info}/PKG-INFO +1 -1
  12. {structtype-0.1.0 → structtype-0.2.0}/tests/unit/test_check.py +23 -23
  13. {structtype-0.1.0 → structtype-0.2.0}/tests/unit/test_json.py +0 -107
  14. {structtype-0.1.0 → structtype-0.2.0}/tests/unit/test_msgspec.py +1 -1
  15. {structtype-0.1.0 → structtype-0.2.0}/tests/unit/test_pydantic.py +1 -1
  16. {structtype-0.1.0 → structtype-0.2.0}/tests/unit/test_struct.py +0 -21
  17. structtype-0.1.0/src/structtype/_adapter.py +0 -34
  18. {structtype-0.1.0 → structtype-0.2.0}/.cibuildwheel.toml +0 -0
  19. {structtype-0.1.0 → structtype-0.2.0}/LICENSE +0 -0
  20. {structtype-0.1.0 → structtype-0.2.0}/MANIFEST.in +0 -0
  21. {structtype-0.1.0 → structtype-0.2.0}/README.md +0 -0
  22. {structtype-0.1.0 → structtype-0.2.0}/setup.cfg +0 -0
  23. {structtype-0.1.0 → structtype-0.2.0}/setup.py +0 -0
  24. {structtype-0.1.0 → structtype-0.2.0}/src/structtype/_json_schema.py +0 -0
  25. {structtype-0.1.0 → structtype-0.2.0}/src/structtype/_utils.py +0 -0
  26. {structtype-0.1.0 → structtype-0.2.0}/src/structtype/atof.h +0 -0
  27. {structtype-0.1.0 → structtype-0.2.0}/src/structtype/atof_consts.h +0 -0
  28. {structtype-0.1.0 → structtype-0.2.0}/src/structtype/common.h +0 -0
  29. {structtype-0.1.0 → structtype-0.2.0}/src/structtype/itoa.h +0 -0
  30. {structtype-0.1.0 → structtype-0.2.0}/src/structtype/py.typed +0 -0
  31. {structtype-0.1.0 → structtype-0.2.0}/src/structtype/ryu.h +0 -0
  32. {structtype-0.1.0 → structtype-0.2.0}/src/structtype.egg-info/SOURCES.txt +0 -0
  33. {structtype-0.1.0 → structtype-0.2.0}/src/structtype.egg-info/dependency_links.txt +0 -0
  34. {structtype-0.1.0 → structtype-0.2.0}/src/structtype.egg-info/top_level.txt +0 -0
  35. {structtype-0.1.0 → structtype-0.2.0}/tests/__init__.py +0 -0
  36. {structtype-0.1.0 → structtype-0.2.0}/tests/unit/__init__.py +0 -0
  37. {structtype-0.1.0 → structtype-0.2.0}/tests/unit/conftest.py +0 -0
  38. {structtype-0.1.0 → structtype-0.2.0}/tests/unit/test_JSONTestSuite.py +0 -0
  39. {structtype-0.1.0 → structtype-0.2.0}/tests/unit/test_adapter.py +0 -0
  40. {structtype-0.1.0 → structtype-0.2.0}/tests/unit/test_constraints.py +0 -0
  41. {structtype-0.1.0 → structtype-0.2.0}/tests/unit/test_cpylint.py +0 -0
  42. {structtype-0.1.0 → structtype-0.2.0}/tests/unit/test_free_threading.py +0 -0
  43. {structtype-0.1.0 → structtype-0.2.0}/tests/unit/test_inspect.py +0 -0
  44. {structtype-0.1.0 → structtype-0.2.0}/tests/unit/test_raw.py +0 -0
  45. {structtype-0.1.0 → structtype-0.2.0}/tests/unit/test_schema.py +0 -0
  46. {structtype-0.1.0 → structtype-0.2.0}/tests/unit/test_struct_meta.py +0 -0
  47. {structtype-0.1.0 → structtype-0.2.0}/tests/unit/test_utils.py +0 -0
  48. {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.struct_check()` — validate field values against types + constraints
54
- - `cls.struct_validate_json(buf)` — deserialize from JSON
55
- - `cls.struct_validate_jsonln(buf)` — deserialize newline-delimited JSON to a list of structs
56
- - `cls.struct_dump_jsonln(items)` — serialize a list of structs as newline-delimited JSON
57
- - `cls.struct_validate(obj)` convert built-in types to struct
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 --group bench benchmarks/bench_compare.py
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: structtype
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: Fast Struct type with validation and JSON serialization for Python.
5
5
  Maintainer-email: Wolfgang Langner <tds333@mailbox.org>
6
6
  License-Expression: BSD-3-Clause
@@ -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 struct_check(
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
+ })