structtype 0.8.1__tar.gz → 0.10.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 (58) hide show
  1. {structtype-0.8.1 → structtype-0.10.0}/AGENTS.md +11 -6
  2. {structtype-0.8.1 → structtype-0.10.0}/Makefile +18 -6
  3. {structtype-0.8.1/src/structtype.egg-info → structtype-0.10.0}/PKG-INFO +2 -1
  4. {structtype-0.8.1 → structtype-0.10.0}/pyproject.toml +1 -0
  5. {structtype-0.8.1 → structtype-0.10.0}/src/structtype/__init__.py +12 -12
  6. {structtype-0.8.1 → structtype-0.10.0}/src/structtype/__init__.pyi +41 -16
  7. {structtype-0.8.1 → structtype-0.10.0}/src/structtype/_adapter.py +28 -12
  8. {structtype-0.8.1 → structtype-0.10.0}/src/structtype/_config.py +2 -1
  9. {structtype-0.8.1 → structtype-0.10.0}/src/structtype/_core.c +672 -755
  10. {structtype-0.8.1 → structtype-0.10.0}/src/structtype/_inspect.py +6 -6
  11. {structtype-0.8.1 → structtype-0.10.0}/src/structtype/_json_schema.py +16 -2
  12. {structtype-0.8.1 → structtype-0.10.0}/src/structtype/_version.py +2 -2
  13. {structtype-0.8.1 → structtype-0.10.0/src/structtype.egg-info}/PKG-INFO +2 -1
  14. {structtype-0.8.1 → structtype-0.10.0}/tests/test_adapter.py +159 -7
  15. {structtype-0.8.1 → structtype-0.10.0}/tests/test_annotations.py +501 -238
  16. structtype-0.10.0/tests/test_check.py +798 -0
  17. {structtype-0.8.1 → structtype-0.10.0}/tests/test_constraints.py +184 -91
  18. {structtype-0.8.1 → structtype-0.10.0}/tests/test_free_threading.py +37 -0
  19. {structtype-0.8.1 → structtype-0.10.0}/tests/test_inspect.py +15 -15
  20. {structtype-0.8.1 → structtype-0.10.0}/tests/test_json.py +444 -5
  21. {structtype-0.8.1 → structtype-0.10.0}/tests/test_raw.py +1 -4
  22. {structtype-0.8.1 → structtype-0.10.0}/tests/test_schema.py +52 -17
  23. {structtype-0.8.1 → structtype-0.10.0}/tests/test_struct.py +89 -1
  24. {structtype-0.8.1 → structtype-0.10.0}/tests/test_struct_meta.py +64 -8
  25. {structtype-0.8.1 → structtype-0.10.0}/tests/test_utils.py +14 -1
  26. {structtype-0.8.1 → structtype-0.10.0}/tests/typecheck/basic.py +1 -1
  27. {structtype-0.8.1 → structtype-0.10.0}/tests/typecheck/fields.py +17 -7
  28. {structtype-0.8.1 → structtype-0.10.0}/tests/utils.py +6 -0
  29. {structtype-0.8.1 → structtype-0.10.0}/uv.lock +245 -245
  30. structtype-0.8.1/tests/test_check.py +0 -270
  31. {structtype-0.8.1 → structtype-0.10.0}/.opencode/opencode.json +0 -0
  32. {structtype-0.8.1 → structtype-0.10.0}/.opencode/plugins/graphify.js +0 -0
  33. {structtype-0.8.1 → structtype-0.10.0}/LICENSE +0 -0
  34. {structtype-0.8.1 → structtype-0.10.0}/MANIFEST.in +0 -0
  35. {structtype-0.8.1 → structtype-0.10.0}/README.md +0 -0
  36. {structtype-0.8.1 → structtype-0.10.0}/setup.cfg +0 -0
  37. {structtype-0.8.1 → structtype-0.10.0}/setup.py +0 -0
  38. {structtype-0.8.1 → structtype-0.10.0}/src/structtype/_utils.py +0 -0
  39. {structtype-0.8.1 → structtype-0.10.0}/src/structtype/atof.h +0 -0
  40. {structtype-0.8.1 → structtype-0.10.0}/src/structtype/atof_consts.h +0 -0
  41. {structtype-0.8.1 → structtype-0.10.0}/src/structtype/common.h +0 -0
  42. {structtype-0.8.1 → structtype-0.10.0}/src/structtype/itoa.h +0 -0
  43. {structtype-0.8.1 → structtype-0.10.0}/src/structtype/py.typed +0 -0
  44. {structtype-0.8.1 → structtype-0.10.0}/src/structtype/ryu.h +0 -0
  45. {structtype-0.8.1 → structtype-0.10.0}/src/structtype.egg-info/SOURCES.txt +0 -0
  46. {structtype-0.8.1 → structtype-0.10.0}/src/structtype.egg-info/dependency_links.txt +0 -0
  47. {structtype-0.8.1 → structtype-0.10.0}/src/structtype.egg-info/top_level.txt +0 -0
  48. {structtype-0.8.1 → structtype-0.10.0}/tests/__init__.py +0 -0
  49. {structtype-0.8.1 → structtype-0.10.0}/tests/conftest.py +0 -0
  50. {structtype-0.8.1 → structtype-0.10.0}/tests/test_JSONTestSuite.py +0 -0
  51. {structtype-0.8.1 → structtype-0.10.0}/tests/test_attrs.py +0 -0
  52. {structtype-0.8.1 → structtype-0.10.0}/tests/test_cpylint.py +0 -0
  53. {structtype-0.8.1 → structtype-0.10.0}/tests/test_memory.py +0 -0
  54. {structtype-0.8.1 → structtype-0.10.0}/tests/test_msgspec.py +0 -0
  55. {structtype-0.8.1 → structtype-0.10.0}/tests/test_pydantic.py +0 -0
  56. {structtype-0.8.1 → structtype-0.10.0}/tests/test_typecheck.py +0 -0
  57. {structtype-0.8.1 → structtype-0.10.0}/tests/typecheck/api.py +0 -0
  58. {structtype-0.8.1 → structtype-0.10.0}/tests/typecheck/types.py +0 -0
@@ -18,9 +18,11 @@ All commands go through `make`.
18
18
 
19
19
  | Task | Command |
20
20
  |---|---|
21
- | Unit tests (reinstall + last-failed) | `make test-lf` |
21
+ | Unit tests (reinstall + full suite) | `make test` |
22
22
  | Targeted tests | `uv run --reinstall pytest tests/test_json.py -k test_something` |
23
- | Coverage | `make test-cov` |
23
+ | Coverage (Python) | `make test-cov` |
24
+ | Coverage (Python + C) | `make test-cov-c` |
25
+ | Tests in all supported Pythons | `make test-all` |
24
26
  | Build docs | `make docs` |
25
27
  | Format | `make format` |
26
28
  | Lint | `make ruff-check` |
@@ -40,16 +42,18 @@ All commands go through `make`.
40
42
  ## Key API
41
43
 
42
44
  - `structtype.Struct` — base class with config options (frozen, tag, rename, etc.)
43
- - `structtype.Field` — field constraints (gt, ge, lt, le, min_length, etc.)
45
+ - `structtype.Field` — field metadata (alias, title, description, examples, deprecated, json_schema_extra)
46
+ - `structtype.Constraint` — base constraint (callable `fn`); subclasses: `NumericConstraint`, `StrConstraint`, `BytesConstraint`, `CollectionConstraint`, `TimezoneConstraint`
47
+ - `structtype.Serializer` — custom-type load/dump codecs
44
48
  - `structtype.Raw` — lazy JSON passthrough
45
49
  - `structtype.fields(type_or_instance)` — get FieldInfo tuple for a struct type/instance
46
50
  - `structtype._inspect.type_info()` / `multi_type_info()` — type introspection
47
51
 
48
52
  ### Struct Methods
49
53
 
50
- - `obj.struct_dump_json(*, decimal_format=None, uuid_format=None, sort_keys=False)` — serialize to JSON bytes
54
+ - `obj.struct_dump_json(*, decimal_as_number=False, uuid_as_hex=False, sort_keys=False)` — serialize to JSON bytes
51
55
  - `obj.struct_dump(*, sort_keys=False, str_keys=False, builtin_types=None)` — convert to built-in Python types (uses `alias` for keys)
52
- - `obj.struct_validate_self()` — validate field values against types + constraints
56
+ - `obj.struct_check_types()` — validate field values against types + constraints (pure type-check, no conversion)
53
57
  - `cls.struct_validate_json(buf, *, strict=True)` — deserialize from JSON
54
58
  - `cls.struct_validate(obj, *, strict=True, from_attributes=False)` — convert built-in types to struct
55
59
 
@@ -61,7 +65,8 @@ Struct instances support the mapping protocol:
61
65
 
62
66
  ## Gotchas
63
67
 
64
- - `make test-cov` reinstalls the C extension before running (last-failed first)
68
+ - `make test-cov` reinstalls the C extension before running. `make test-cov-c` builds an `-O0 --coverage` instrumented extension **in place**; afterwards any reinstalling target (`make test`, `make test-cov`) restores the optimized build.
69
+ - C coverage requires `lcov`/`genhtml`; report lands in `htmlcov-c/`.
65
70
  - 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.
66
71
 
67
72
  ## graphify
@@ -23,12 +23,22 @@ wheels: ## build wheels
23
23
 
24
24
  ##@ Quality
25
25
  .PHONY: test-cov
26
- test-cov: build ## Run tests with coverage
27
- uv run pytest --cov-report=term-missing --cov-config=pyproject.toml --cov=structtype
28
-
29
- .PHONY: test-lf
30
- test-lf: ## Run tests in current Python
31
- uv run --reinstall pytest --lf
26
+ test-cov: ## Run tests with coverage
27
+ uv run --reinstall-package structtype pytest --cov-report=term-missing --cov-config=pyproject.toml --cov=structtype
28
+
29
+ .PHONY: test-cov-c
30
+ test-cov-c: ## Run tests with Python + C coverage (lcov report in htmlcov-c/)
31
+ rm -rf build coverage-c.info coverage-c.info.* htmlcov-c
32
+ STRUCTTYPE_COVERAGE=1 uv run --with setuptools python setup.py build_ext --inplace --force
33
+ ./.venv/bin/python -m pytest --cov-report=term-missing --cov-config=pyproject.toml --cov=structtype
34
+ lcov --capture --directory build --output-file coverage-c.info
35
+ lcov --extract coverage-c.info "*/src/structtype/*" --output-file coverage-c.info
36
+ lcov --summary coverage-c.info
37
+ genhtml coverage-c.info --output-directory htmlcov-c >/dev/null
38
+
39
+ .PHONY: test
40
+ test: ## Run tests in current Python
41
+ uv run --reinstall pytest
32
42
 
33
43
  .PHONY: test-doc
34
44
  test-doc: ## Run doctests
@@ -110,7 +120,9 @@ clean: ## Delete all temporary files
110
120
  rm -rf **/__pycache__
111
121
  rm -rf build
112
122
  rm -rf dist
123
+ rm -rf htmlcov-c
113
124
  rm -f .coverage
125
+ rm -f coverage-c.info coverage-c.info.*
114
126
 
115
127
  .PHONY: install
116
128
  install: install-uv ## Install virtual environment
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: structtype
3
- Version: 0.8.1
3
+ Version: 0.10.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
@@ -18,6 +18,7 @@ Classifier: Programming Language :: Python :: 3.12
18
18
  Classifier: Programming Language :: Python :: 3.13
19
19
  Classifier: Programming Language :: Python :: 3.14
20
20
  Classifier: Programming Language :: Python :: 3.15
21
+ Classifier: Programming Language :: Python :: Free Threading
21
22
  Requires-Python: >=3.10
22
23
  Description-Content-Type: text/markdown
23
24
  License-File: LICENSE
@@ -16,6 +16,7 @@ classifiers = [
16
16
  "Programming Language :: Python :: 3.13",
17
17
  "Programming Language :: Python :: 3.14",
18
18
  "Programming Language :: Python :: 3.15",
19
+ "Programming Language :: Python :: Free Threading",
19
20
  ]
20
21
  dependencies = []
21
22
  dynamic = ["version"]
@@ -4,22 +4,22 @@ from ._core import ( # type: ignore
4
4
  ALL_BUILTIN_TYPES,
5
5
  NODEFAULT,
6
6
  UNSET,
7
- BytesValidator,
8
- CollectionValidator,
7
+ BytesConstraint,
8
+ CollectionConstraint,
9
+ Constraint,
9
10
  DecodeError,
10
11
  EncodeError,
11
12
  Factory,
12
13
  Field,
13
- NumericValidator,
14
+ NumericConstraint,
14
15
  Raw,
15
16
  Serializer,
17
+ StrConstraint,
16
18
  Struct,
17
19
  StructMeta,
18
- StrValidator,
19
- TimezoneValidator,
20
+ TimezoneConstraint,
20
21
  UnsetType,
21
22
  ValidationError,
22
- Validator,
23
23
  )
24
24
  from ._inspect import FieldInfo, fields
25
25
  from ._json_schema import json_schema, json_schema_components, json_schema_dump
@@ -29,25 +29,25 @@ __all__ = [
29
29
  "ALL_BUILTIN_TYPES",
30
30
  "NODEFAULT",
31
31
  "UNSET",
32
- "BytesValidator",
33
- "CollectionValidator",
32
+ "BytesConstraint",
33
+ "CollectionConstraint",
34
+ "Constraint",
34
35
  "DecodeError",
35
36
  "EncodeError",
36
37
  "Factory",
37
38
  "Field",
38
39
  "FieldInfo",
39
- "NumericValidator",
40
+ "NumericConstraint",
40
41
  "Raw",
41
42
  "Serializer",
42
- "StrValidator",
43
+ "StrConstraint",
43
44
  "Struct",
44
45
  "StructAdapter",
45
46
  "StructConfig",
46
47
  "StructMeta",
47
- "TimezoneValidator",
48
+ "TimezoneConstraint",
48
49
  "UnsetType",
49
50
  "ValidationError",
50
- "Validator",
51
51
  "__version__",
52
52
  "fields",
53
53
  "json_schema",
@@ -37,7 +37,7 @@ class StructConfig(TypedDict, total=False):
37
37
  repr_omit_defaults: bool
38
38
  omit_defaults: bool
39
39
  forbid_unknown_fields: bool
40
- validate_on_init: bool
40
+ check_types_on_init: bool
41
41
  weakref: bool
42
42
  dict: bool
43
43
  cache_hash: bool
@@ -81,10 +81,8 @@ class Struct(metaclass=StructMeta):
81
81
  def struct_dump_json(
82
82
  self,
83
83
  *,
84
- decimal_format: Literal["string", "number"]
85
- | Callable[[Any], Any]
86
- | None = None,
87
- uuid_format: Literal["canonical", "hex"] | None = None,
84
+ decimal_as_number: bool = False,
85
+ uuid_as_hex: bool = False,
88
86
  sort_keys: bool = False,
89
87
  ) -> bytes: ...
90
88
  def struct_dump(
@@ -94,7 +92,7 @@ class Struct(metaclass=StructMeta):
94
92
  str_keys: bool = False,
95
93
  builtin_types: Iterable[type] | None = None,
96
94
  ) -> dict[str, Any] | list[Any]: ...
97
- def struct_validate_self(self) -> None: ...
95
+ def struct_check_types(self) -> None: ...
98
96
  @classmethod
99
97
  def struct_validate_json(
100
98
  cls,
@@ -160,18 +158,47 @@ class Serializer:
160
158
  load: Final[Callable[[Any], Any] | None]
161
159
  dump: Final[Callable[[Any], Any] | None]
162
160
 
163
- class Validator:
161
+ class Constraint:
164
162
  def __init__(self, fn: Callable[[Any], Any] | None = None) -> None: ...
165
163
  def __call__(self, value: Any) -> None: ...
166
164
 
167
165
  @final
168
- class NumericValidator(Validator):
166
+ class NumericConstraint(Constraint):
167
+ # Numeric bounds. You can't mix:
168
+ # - `gt` and `ge`
169
+ # - `lt` and `le`
170
+ # Cross-pair combinations (`gt` with `le`, `ge` with `lt`) are valid,
171
+ # so all four pairings are enumerated as separate overloads. The C
172
+ # runtime raises `ValueError` for the invalid pairings.
173
+ @overload
174
+ def __init__(
175
+ self,
176
+ *,
177
+ gt: int | float | None = None,
178
+ lt: int | float | None = None,
179
+ multiple_of: int | float | None = None,
180
+ ) -> None: ...
181
+ @overload
169
182
  def __init__(
170
183
  self,
171
184
  *,
172
185
  gt: int | float | None = None,
186
+ le: int | float | None = None,
187
+ multiple_of: int | float | None = None,
188
+ ) -> None: ...
189
+ @overload
190
+ def __init__(
191
+ self,
192
+ *,
173
193
  ge: int | float | None = None,
174
194
  lt: int | float | None = None,
195
+ multiple_of: int | float | None = None,
196
+ ) -> None: ...
197
+ @overload
198
+ def __init__(
199
+ self,
200
+ *,
201
+ ge: int | float | None = None,
175
202
  le: int | float | None = None,
176
203
  multiple_of: int | float | None = None,
177
204
  ) -> None: ...
@@ -182,7 +209,7 @@ class NumericValidator(Validator):
182
209
  multiple_of: Final[int | float | None]
183
210
 
184
211
  @final
185
- class StrValidator(Validator):
212
+ class StrConstraint(Constraint):
186
213
  def __init__(
187
214
  self,
188
215
  *,
@@ -195,7 +222,7 @@ class StrValidator(Validator):
195
222
  max_length: Final[int | None]
196
223
 
197
224
  @final
198
- class BytesValidator(Validator):
225
+ class BytesConstraint(Constraint):
199
226
  def __init__(
200
227
  self,
201
228
  *,
@@ -206,7 +233,7 @@ class BytesValidator(Validator):
206
233
  max_length: Final[int | None]
207
234
 
208
235
  @final
209
- class CollectionValidator(Validator):
236
+ class CollectionConstraint(Constraint):
210
237
  def __init__(
211
238
  self,
212
239
  *,
@@ -217,7 +244,7 @@ class CollectionValidator(Validator):
217
244
  max_length: Final[int | None]
218
245
 
219
246
  @final
220
- class TimezoneValidator(Validator):
247
+ class TimezoneConstraint(Constraint):
221
248
  def __init__(self, *, tz: bool) -> None: ...
222
249
  tz: Final[bool]
223
250
 
@@ -263,10 +290,8 @@ class StructAdapter:
263
290
  self,
264
291
  obj: Any,
265
292
  *,
266
- decimal_format: Literal["string", "number"]
267
- | Callable[[Any], Any]
268
- | None = None,
269
- uuid_format: Literal["canonical", "hex"] | None = None,
293
+ decimal_as_number: bool = False,
294
+ uuid_as_hex: bool = False,
270
295
  sort_keys: bool = False,
271
296
  ) -> bytes: ...
272
297
  def struct_validate(
@@ -1,11 +1,13 @@
1
1
  from typing import Any, get_args
2
2
 
3
- from ._core import ( # type: ignore
3
+ from ._core import (
4
+ JSONDecoder as _JSONDecoder,
5
+ )
6
+ from ._core import (
4
7
  Serializer as _Serializer,
5
8
  )
6
9
  from ._core import ( # type: ignore
7
10
  _dump,
8
- _json_decode,
9
11
  _json_encode,
10
12
  _validate,
11
13
  )
@@ -46,7 +48,7 @@ class StructAdapter:
46
48
  [1, 2, 3]
47
49
  """
48
50
 
49
- __slots__ = ("_type",)
51
+ __slots__ = ("_decoder_loose", "_decoder_strict", "_type")
50
52
 
51
53
  def __init__(self, type: Any):
52
54
  if _has_serializer(type):
@@ -57,6 +59,8 @@ class StructAdapter:
57
59
  "`Struct` instead"
58
60
  )
59
61
  self._type = type
62
+ self._decoder_loose = None
63
+ self._decoder_strict = None
60
64
 
61
65
  def struct_validate_json(self, buf, *, strict=True):
62
66
  """Validate JSON bytes and decode into the adapter's type.
@@ -68,14 +72,24 @@ class StructAdapter:
68
72
  strict : bool, optional
69
73
  If True (default), unmatched fields cause an error.
70
74
  """
71
- return _json_decode(buf, type=self._type, strict=strict)
75
+ if strict:
76
+ decoder = self._decoder_strict
77
+ if decoder is None:
78
+ decoder = _JSONDecoder(self._type, strict=True)
79
+ self._decoder_strict = decoder
80
+ else:
81
+ decoder = self._decoder_loose
82
+ if decoder is None:
83
+ decoder = _JSONDecoder(self._type, strict=False)
84
+ self._decoder_loose = decoder
85
+ return decoder.decode(buf)
72
86
 
73
87
  def struct_dump_json(
74
88
  self,
75
89
  obj,
76
90
  *,
77
- decimal_format=None,
78
- uuid_format=None,
91
+ decimal_as_number=False,
92
+ uuid_as_hex=False,
79
93
  sort_keys=False,
80
94
  ):
81
95
  """Encode a validated object to JSON bytes.
@@ -84,17 +98,19 @@ class StructAdapter:
84
98
  ----------
85
99
  obj : Any
86
100
  A value to encode. Must match the adapter's type.
87
- decimal_format : str or callable, optional
88
- Controls how ``Decimal`` values are encoded.
89
- uuid_format : str, optional
90
- Controls how ``UUID`` values are encoded.
101
+ decimal_as_number : bool, optional
102
+ If True, ``Decimal`` values are encoded as JSON numbers instead
103
+ of strings (may lose precision when decoded).
104
+ uuid_as_hex : bool, optional
105
+ If True, ``UUID`` values are encoded as hex strings instead of
106
+ canonical form.
91
107
  sort_keys : bool, optional
92
108
  If True, sort dict keys and set elements for deterministic output.
93
109
  """
94
110
  return _json_encode(
95
111
  obj,
96
- decimal_format=decimal_format,
97
- uuid_format=uuid_format,
112
+ decimal_as_number=decimal_as_number,
113
+ uuid_as_hex=uuid_as_hex,
98
114
  sort_keys=sort_keys,
99
115
  )
100
116
 
@@ -7,6 +7,7 @@ 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
11
  """
11
12
 
12
13
  frozen: bool
@@ -17,7 +18,7 @@ class StructConfig(TypedDict, total=False):
17
18
  array_like: bool
18
19
  omit_defaults: bool
19
20
  forbid_unknown_fields: bool
20
- validate_on_init: bool
21
+ check_types_on_init: bool
21
22
  weakref: bool
22
23
  dict: bool
23
24
  cache_hash: bool