structtype 0.1.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.
Files changed (49) hide show
  1. {structtype-0.1.0 → structtype-0.3.0}/AGENTS.md +12 -10
  2. {structtype-0.1.0 → structtype-0.3.0}/MANIFEST.in +0 -1
  3. {structtype-0.1.0 → structtype-0.3.0}/Makefile +9 -2
  4. {structtype-0.1.0/src/structtype.egg-info → structtype-0.3.0}/PKG-INFO +1 -1
  5. {structtype-0.1.0 → structtype-0.3.0}/pyproject.toml +9 -16
  6. {structtype-0.1.0 → structtype-0.3.0}/setup.py +15 -9
  7. {structtype-0.1.0 → structtype-0.3.0}/src/structtype/__init__.py +2 -2
  8. {structtype-0.1.0 → structtype-0.3.0}/src/structtype/__init__.pyi +35 -46
  9. structtype-0.3.0/src/structtype/_adapter.py +128 -0
  10. {structtype-0.1.0 → structtype-0.3.0}/src/structtype/_core.c +648 -1086
  11. {structtype-0.1.0 → structtype-0.3.0}/src/structtype/_inspect.py +120 -31
  12. {structtype-0.1.0 → structtype-0.3.0}/src/structtype/_json_schema.py +12 -7
  13. {structtype-0.1.0 → structtype-0.3.0}/src/structtype/_utils.py +29 -1
  14. {structtype-0.1.0 → structtype-0.3.0}/src/structtype/_version.py +2 -2
  15. {structtype-0.1.0 → structtype-0.3.0}/src/structtype/common.h +2 -0
  16. {structtype-0.1.0 → structtype-0.3.0}/src/structtype/ryu.h +1 -1
  17. {structtype-0.1.0 → structtype-0.3.0/src/structtype.egg-info}/PKG-INFO +1 -1
  18. {structtype-0.1.0 → structtype-0.3.0}/src/structtype.egg-info/SOURCES.txt +1 -1
  19. {structtype-0.1.0 → structtype-0.3.0}/tests/unit/test_JSONTestSuite.py +3 -3
  20. {structtype-0.1.0 → structtype-0.3.0}/tests/unit/test_check.py +21 -58
  21. {structtype-0.1.0 → structtype-0.3.0}/tests/unit/test_constraints.py +3 -3
  22. {structtype-0.1.0 → structtype-0.3.0}/tests/unit/test_inspect.py +19 -0
  23. {structtype-0.1.0 → structtype-0.3.0}/tests/unit/test_json.py +252 -359
  24. {structtype-0.1.0 → structtype-0.3.0}/tests/unit/test_msgspec.py +2 -3
  25. {structtype-0.1.0 → structtype-0.3.0}/tests/unit/test_pydantic.py +1 -1
  26. {structtype-0.1.0 → structtype-0.3.0}/tests/unit/test_schema.py +123 -0
  27. {structtype-0.1.0 → structtype-0.3.0}/tests/unit/test_struct.py +7 -23
  28. {structtype-0.1.0 → structtype-0.3.0}/tests/unit/test_struct_meta.py +13 -13
  29. structtype-0.3.0/uv.lock +1490 -0
  30. structtype-0.1.0/.cibuildwheel.toml +0 -4
  31. structtype-0.1.0/src/structtype/_adapter.py +0 -34
  32. {structtype-0.1.0 → structtype-0.3.0}/LICENSE +0 -0
  33. {structtype-0.1.0 → structtype-0.3.0}/README.md +0 -0
  34. {structtype-0.1.0 → structtype-0.3.0}/setup.cfg +0 -0
  35. {structtype-0.1.0 → structtype-0.3.0}/src/structtype/atof.h +0 -0
  36. {structtype-0.1.0 → structtype-0.3.0}/src/structtype/atof_consts.h +0 -0
  37. {structtype-0.1.0 → structtype-0.3.0}/src/structtype/itoa.h +0 -0
  38. {structtype-0.1.0 → structtype-0.3.0}/src/structtype/py.typed +0 -0
  39. {structtype-0.1.0 → structtype-0.3.0}/src/structtype.egg-info/dependency_links.txt +0 -0
  40. {structtype-0.1.0 → structtype-0.3.0}/src/structtype.egg-info/top_level.txt +0 -0
  41. {structtype-0.1.0 → structtype-0.3.0}/tests/__init__.py +0 -0
  42. {structtype-0.1.0 → structtype-0.3.0}/tests/unit/__init__.py +0 -0
  43. {structtype-0.1.0 → structtype-0.3.0}/tests/unit/conftest.py +0 -0
  44. {structtype-0.1.0 → structtype-0.3.0}/tests/unit/test_adapter.py +0 -0
  45. {structtype-0.1.0 → structtype-0.3.0}/tests/unit/test_cpylint.py +0 -0
  46. {structtype-0.1.0 → structtype-0.3.0}/tests/unit/test_free_threading.py +0 -0
  47. {structtype-0.1.0 → structtype-0.3.0}/tests/unit/test_raw.py +0 -0
  48. {structtype-0.1.0 → structtype-0.3.0}/tests/unit/test_utils.py +0 -0
  49. {structtype-0.1.0 → structtype-0.3.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()` — 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)
@@ -5,4 +5,3 @@ prune docs
5
5
  prune examples
6
6
  prune scripts
7
7
  exclude .*
8
- include .cibuildwheel.toml
@@ -13,6 +13,10 @@ docs: ## build docs
13
13
  rm -rf .doctrees site
14
14
  uv run --group docs sphinx-build -d .doctrees -b html docs site --fail-on-warning
15
15
 
16
+ .PHONY: wheels
17
+ wheels: ## build wheels
18
+ uvx --from cibuildwheel==4.1.1 cibuildwheel
19
+
16
20
  ##@ Quality
17
21
  .PHONY: test-cov
18
22
  test-cov: ## Run tests with coverage
@@ -22,6 +26,10 @@ test-cov: ## Run tests with coverage
22
26
  test-lf: ## Run tests in current Python
23
27
  uv run --reinstall pytest --lf
24
28
 
29
+ .PHONY: test-doc
30
+ test-doc: ## Run doctests
31
+ uv run pytest --doctest-modules --pyargs structtype
32
+
25
33
  .PHONY: test-all
26
34
  test-all: ## Run tests in all supporte Python versions
27
35
  for py_v in $(PY_VERSIONS); do \
@@ -49,8 +57,7 @@ format: ## Format files using ruff format
49
57
  ##@ Benchmark
50
58
  .PHONY: bench
51
59
  bench: ## run benchmarks
52
- uv run --group bench benchmarks/bench_compare.py
53
- uv run --group bench benchmarks/bench_libs.py
60
+ uv run benchmarks/bench_libs.py
54
61
 
55
62
  ##@ Utility
56
63
  .PHONY: clean
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: structtype
3
- Version: 0.1.0
3
+ Version: 0.3.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 = [
@@ -96,3 +80,12 @@ parentdir_prefix_version = "structtype-"
96
80
 
97
81
  [tool.uv.config-settings]
98
82
  editable_mode = "compat"
83
+
84
+ [tool.cibuildwheel]
85
+ build = "cp3*"
86
+ skip = ["pp*", "*-manylinux_i686", "*-musllinux*", "*-win32"]
87
+ enable = ["cpython-prerelease"]
88
+ test-command = "pip install pytest>=9 && pytest {project}/tests"
89
+
90
+ [tool.ruff]
91
+ target-version = "py310"
@@ -36,6 +36,12 @@ if DEBUG:
36
36
  extra_compile_args.extend(["-O0", "-g", "-UNDEBUG"])
37
37
  elif sys.platform != "win32":
38
38
  extra_compile_args.extend(["-g0"])
39
+ extra_compile_args.extend([
40
+ "-Wextra",
41
+ "-Wno-unused-parameter",
42
+ "-Wno-missing-field-initializers",
43
+ "-Wno-cast-function-type",
44
+ ])
39
45
  if sys.platform == "darwin" and platform.machine().lower() == "arm64":
40
46
  extra_compile_args.extend(["-flto=thin"])
41
47
  extra_link_args.extend(["-flto=thin"])
@@ -53,15 +59,15 @@ libraries = []
53
59
  if sys.platform != "win32":
54
60
  libraries.append("m")
55
61
 
56
- ext_modules = [
57
- Extension(
58
- "structtype._core",
59
- [os.path.join("src", "structtype", "_core.c")],
60
- libraries=libraries,
61
- extra_compile_args=extra_compile_args,
62
- extra_link_args=extra_link_args,
63
- )
64
- ]
62
+ ext_modules = [
63
+ Extension(
64
+ "structtype._core",
65
+ [os.path.join("src", "structtype", "_core.c")],
66
+ libraries=libraries,
67
+ extra_compile_args=extra_compile_args,
68
+ extra_link_args=extra_link_args,
69
+ )
70
+ ]
65
71
 
66
72
  setup(
67
73
  ext_modules=ext_modules,
@@ -1,3 +1,4 @@
1
+ from ._adapter import StrAdapter, StructAdapter
1
2
  from ._core import (
2
3
  NODEFAULT,
3
4
  UNSET,
@@ -11,7 +12,6 @@ from ._core import (
11
12
  UnsetType,
12
13
  ValidationError,
13
14
  )
14
- from ._adapter import StructAdapter
15
15
  from ._inspect import FieldInfo, fields
16
- from ._json_schema import json_schema, json_schema_dump, json_schema_components
16
+ from ._json_schema import json_schema, json_schema_components, json_schema_dump
17
17
  from ._version import __version__
@@ -1,23 +1,11 @@
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
- from typing import (
5
- Any,
6
- ClassVar,
7
- Final,
8
- Literal,
9
- Optional,
10
- Tuple,
11
- Type,
12
- TypeVar,
13
- Union,
14
- final,
15
- overload,
16
- )
4
+ from typing import Any, ClassVar, Final, Literal, TypeAlias, TypeVar, final, overload
17
5
 
18
- from typing_extensions import Buffer, Self, TypeAlias, dataclass_transform
6
+ from typing_extensions import Buffer, Self, dataclass_transform
19
7
 
20
- from . import _inspect
8
+ from . import StructConfig
21
9
 
22
10
  # PEP 673 explicitly rejects using Self in metaclass definitions:
23
11
  # https://peps.python.org/pep-0673/#valid-locations-for-self
@@ -34,7 +22,7 @@ class StructMeta(type):
34
22
  @property
35
23
  def __signature__(self) -> Signature: ...
36
24
  @property
37
- def __struct_config__(self) -> structs.StructConfig: ...
25
+ def __struct_config__(self) -> StructConfig: ...
38
26
  def __new__(
39
27
  mcls: type[_SM],
40
28
  name: str,
@@ -85,6 +73,7 @@ class Struct(metaclass=StructMeta):
85
73
  __struct_fields__: ClassVar[tuple[str, ...]]
86
74
  __struct_config__: ClassVar[StructConfig]
87
75
  __struct_encode_fields__: ClassVar[tuple[str, ...]]
76
+ __struct_defaults__: ClassVar[tuple[Any, ...]]
88
77
  __match_args__: ClassVar[tuple[str, ...]]
89
78
  # A default __init__ so that Structs with unknown field types
90
79
  # won't error on every call to `__init__`
@@ -114,25 +103,30 @@ class Struct(metaclass=StructMeta):
114
103
  validate_on_init: bool = False,
115
104
  ) -> None: ...
116
105
  def __rich_repr__(self) -> list[tuple[str, Any]]: ...
106
+ def __copy__(self) -> Self: ...
107
+ def __reduce__(self) -> tuple: ...
117
108
  def __replace__(self, **changes: Any) -> Self: ...
109
+ def __iter__(self) -> Iterator[tuple[str, Any]]: ...
118
110
  def struct_dump_json(
119
111
  self,
120
112
  *,
121
113
  enc_hook: Callable[[Any], Any] | None = None,
122
- decimal_format: Literal["string", "number"] | Callable[[Any], Any] | None = None,
114
+ decimal_format: Literal["string", "number"]
115
+ | Callable[[Any], Any]
116
+ | None = None,
123
117
  uuid_format: Literal["canonical", "hex"] | None = None,
124
118
  order: Literal["deterministic", "sorted"] | None = None,
125
119
  ) -> bytes: ...
126
- 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
- def struct_force_setattr(self, name: str, value: Any) -> None: ...
130
- def struct_check(
120
+ def struct_dump(
131
121
  self,
132
122
  *,
133
- strict: bool = True,
134
- dec_hook: Callable[[type[Any], Any], Any] | None = None,
135
- ) -> None: ...
123
+ enc_hook: Callable[[Any], Any] | None = None,
124
+ order: Literal["deterministic", "sorted"] | None = None,
125
+ str_keys: bool = False,
126
+ builtin_types: Iterable[type] | None = None,
127
+ ) -> dict[str, Any] | list[Any]: ...
128
+ def struct_force_setattr(self, name: str, value: Any) -> None: ...
129
+ def struct_validate_self(self) -> None: ...
136
130
  @classmethod
137
131
  def struct_validate_json(
138
132
  cls: type[_T],
@@ -150,24 +144,6 @@ class Struct(metaclass=StructMeta):
150
144
  from_attributes: bool = False,
151
145
  dec_hook: Callable[[type[Any], Any], Any] | None = None,
152
146
  ) -> _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
147
 
172
148
  # Lie and say `Raw` is a subclass of `bytes`, so mypy will accept it in most
173
149
  # places where an object that implements the buffer protocol is valid
@@ -340,7 +316,9 @@ class StructAdapter:
340
316
  obj: Any,
341
317
  *,
342
318
  enc_hook: Callable[[Any], Any] | None = None,
343
- decimal_format: Literal["string", "number"] | Callable[[Any], Any] | None = None,
319
+ decimal_format: Literal["string", "number"]
320
+ | Callable[[Any], Any]
321
+ | None = None,
344
322
  uuid_format: Literal["canonical", "hex"] | None = None,
345
323
  order: Literal["deterministic", "sorted"] | None = None,
346
324
  ) -> bytes: ...
@@ -352,7 +330,18 @@ class StructAdapter:
352
330
  dec_hook: Callable[[type[Any], Any], Any] | None = None,
353
331
  from_attributes: bool = False,
354
332
  ) -> Any: ...
355
- def struct_dump(self, obj: Any) -> Any: ...
333
+ def struct_dump(
334
+ self,
335
+ obj: Any,
336
+ *,
337
+ enc_hook: Callable[[Any], Any] | None = None,
338
+ order: Literal["deterministic", "sorted"] | None = None,
339
+ str_keys: bool = False,
340
+ builtin_types: Iterable[type] | None = None,
341
+ ) -> Any: ...
342
+
343
+ class StrAdapter:
344
+ def __new__(cls, type: type[Any]) -> type[str]: ...
356
345
 
357
346
  class EncodeError(ValueError): ...
358
347
  class DecodeError(ValueError): ...
@@ -0,0 +1,128 @@
1
+ from typing import Any
2
+
3
+ from ._core import _dump, _json_decode, _json_encode, _validate
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(
38
+ self, obj, *, enc_hook=None, decimal_format=None, uuid_format=None, order=None
39
+ ):
40
+ """Encode a validated object to JSON bytes.
41
+
42
+ Parameters
43
+ ----------
44
+ obj : Any
45
+ A value to encode. Must match the adapter's type.
46
+ enc_hook : callable, optional
47
+ A callback for customizing encoding of specific types.
48
+ decimal_format : str or callable, optional
49
+ Controls how ``Decimal`` values are encoded.
50
+ uuid_format : str, optional
51
+ Controls how ``UUID`` values are encoded.
52
+ order : str, optional
53
+ Determines key ordering in JSON objects.
54
+ """
55
+ return _json_encode(
56
+ obj,
57
+ enc_hook=enc_hook,
58
+ decimal_format=decimal_format,
59
+ uuid_format=uuid_format,
60
+ order=order,
61
+ )
62
+
63
+ def struct_validate(
64
+ self, obj, *, strict=True, dec_hook=None, from_attributes=False
65
+ ):
66
+ """Validate a Python object against the adapter's type.
67
+
68
+ Parameters
69
+ ----------
70
+ obj : Any
71
+ A Python object to validate and convert.
72
+ strict : bool, optional
73
+ If True (default), unmatched fields cause an error.
74
+ dec_hook : callable, optional
75
+ A callback for customizing decoding of specific types.
76
+ from_attributes : bool, optional
77
+ If True, accept objects with attributes instead of dict keys.
78
+ """
79
+ return _validate(
80
+ obj,
81
+ self._type,
82
+ strict=strict,
83
+ dec_hook=dec_hook,
84
+ from_attributes=from_attributes,
85
+ )
86
+
87
+ def struct_dump(
88
+ self, obj, *, enc_hook=None, order=None, str_keys=False, builtin_types=None
89
+ ):
90
+ """Convert a validated object to built-in Python types (``dict``, ``list``, etc.)."""
91
+ return _dump(
92
+ obj,
93
+ builtin_types=builtin_types,
94
+ str_keys=str_keys,
95
+ enc_hook=enc_hook,
96
+ order=order,
97
+ )
98
+
99
+
100
+ class StrAdapter:
101
+ """Create a ``str`` subclass wrapper for validating a type during
102
+ structtype serialization.
103
+
104
+ Wraps a type that has a single-argument string constructor
105
+ (e.g. ``HttpUrl``, ``EmailStr``, ``IPv4Address``) into a ``str``
106
+ subclass. The wrapped value is stored as a string but validated by
107
+ calling ``typ(value)`` on construction. During structtype
108
+ validation and serialization, the wrapper is treated as a native
109
+ ``str``.
110
+
111
+ >>> from structtype import StrAdapter, Struct
112
+ >>> from ipaddress import IPv4Address
113
+ >>>
114
+ >>> class Config(Struct):
115
+ ... ip: StrAdapter(IPv4Address)
116
+ """
117
+
118
+ __slots__ = ()
119
+
120
+ def __new__(cls, typ):
121
+ return type(
122
+ f"_Wrapped_{typ.__name__}",
123
+ (str,),
124
+ {
125
+ "__slots__": (),
126
+ "__new__": lambda self, v: str.__new__(self, str(typ(v))),
127
+ },
128
+ )