structtype 0.9.0__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.9.0 → structtype-0.10.0}/AGENTS.md +7 -4
  2. {structtype-0.9.0 → structtype-0.10.0}/Makefile +18 -6
  3. {structtype-0.9.0/src/structtype.egg-info → structtype-0.10.0}/PKG-INFO +2 -1
  4. {structtype-0.9.0 → structtype-0.10.0}/pyproject.toml +1 -0
  5. {structtype-0.9.0 → structtype-0.10.0}/src/structtype/__init__.pyi +33 -8
  6. {structtype-0.9.0 → structtype-0.10.0}/src/structtype/_adapter.py +28 -12
  7. {structtype-0.9.0 → structtype-0.10.0}/src/structtype/_config.py +1 -0
  8. {structtype-0.9.0 → structtype-0.10.0}/src/structtype/_core.c +213 -386
  9. {structtype-0.9.0 → structtype-0.10.0}/src/structtype/_json_schema.py +16 -2
  10. {structtype-0.9.0 → structtype-0.10.0}/src/structtype/_version.py +2 -2
  11. {structtype-0.9.0 → structtype-0.10.0/src/structtype.egg-info}/PKG-INFO +2 -1
  12. {structtype-0.9.0 → structtype-0.10.0}/tests/test_adapter.py +154 -2
  13. {structtype-0.9.0 → structtype-0.10.0}/tests/test_annotations.py +264 -1
  14. structtype-0.10.0/tests/test_check.py +798 -0
  15. {structtype-0.9.0 → structtype-0.10.0}/tests/test_constraints.py +93 -0
  16. {structtype-0.9.0 → structtype-0.10.0}/tests/test_free_threading.py +37 -0
  17. {structtype-0.9.0 → structtype-0.10.0}/tests/test_json.py +441 -2
  18. {structtype-0.9.0 → structtype-0.10.0}/tests/test_raw.py +1 -4
  19. {structtype-0.9.0 → structtype-0.10.0}/tests/test_schema.py +35 -0
  20. {structtype-0.9.0 → structtype-0.10.0}/tests/test_struct.py +89 -1
  21. {structtype-0.9.0 → structtype-0.10.0}/tests/test_struct_meta.py +62 -6
  22. {structtype-0.9.0 → structtype-0.10.0}/tests/test_utils.py +14 -1
  23. {structtype-0.9.0 → structtype-0.10.0}/tests/typecheck/fields.py +10 -0
  24. {structtype-0.9.0 → structtype-0.10.0}/tests/utils.py +6 -0
  25. {structtype-0.9.0 → structtype-0.10.0}/uv.lock +245 -245
  26. structtype-0.9.0/tests/test_check.py +0 -389
  27. {structtype-0.9.0 → structtype-0.10.0}/.opencode/opencode.json +0 -0
  28. {structtype-0.9.0 → structtype-0.10.0}/.opencode/plugins/graphify.js +0 -0
  29. {structtype-0.9.0 → structtype-0.10.0}/LICENSE +0 -0
  30. {structtype-0.9.0 → structtype-0.10.0}/MANIFEST.in +0 -0
  31. {structtype-0.9.0 → structtype-0.10.0}/README.md +0 -0
  32. {structtype-0.9.0 → structtype-0.10.0}/setup.cfg +0 -0
  33. {structtype-0.9.0 → structtype-0.10.0}/setup.py +0 -0
  34. {structtype-0.9.0 → structtype-0.10.0}/src/structtype/__init__.py +0 -0
  35. {structtype-0.9.0 → structtype-0.10.0}/src/structtype/_inspect.py +0 -0
  36. {structtype-0.9.0 → structtype-0.10.0}/src/structtype/_utils.py +0 -0
  37. {structtype-0.9.0 → structtype-0.10.0}/src/structtype/atof.h +0 -0
  38. {structtype-0.9.0 → structtype-0.10.0}/src/structtype/atof_consts.h +0 -0
  39. {structtype-0.9.0 → structtype-0.10.0}/src/structtype/common.h +0 -0
  40. {structtype-0.9.0 → structtype-0.10.0}/src/structtype/itoa.h +0 -0
  41. {structtype-0.9.0 → structtype-0.10.0}/src/structtype/py.typed +0 -0
  42. {structtype-0.9.0 → structtype-0.10.0}/src/structtype/ryu.h +0 -0
  43. {structtype-0.9.0 → structtype-0.10.0}/src/structtype.egg-info/SOURCES.txt +0 -0
  44. {structtype-0.9.0 → structtype-0.10.0}/src/structtype.egg-info/dependency_links.txt +0 -0
  45. {structtype-0.9.0 → structtype-0.10.0}/src/structtype.egg-info/top_level.txt +0 -0
  46. {structtype-0.9.0 → structtype-0.10.0}/tests/__init__.py +0 -0
  47. {structtype-0.9.0 → structtype-0.10.0}/tests/conftest.py +0 -0
  48. {structtype-0.9.0 → structtype-0.10.0}/tests/test_JSONTestSuite.py +0 -0
  49. {structtype-0.9.0 → structtype-0.10.0}/tests/test_attrs.py +0 -0
  50. {structtype-0.9.0 → structtype-0.10.0}/tests/test_cpylint.py +0 -0
  51. {structtype-0.9.0 → structtype-0.10.0}/tests/test_inspect.py +0 -0
  52. {structtype-0.9.0 → structtype-0.10.0}/tests/test_memory.py +0 -0
  53. {structtype-0.9.0 → structtype-0.10.0}/tests/test_msgspec.py +0 -0
  54. {structtype-0.9.0 → structtype-0.10.0}/tests/test_pydantic.py +0 -0
  55. {structtype-0.9.0 → structtype-0.10.0}/tests/test_typecheck.py +0 -0
  56. {structtype-0.9.0 → structtype-0.10.0}/tests/typecheck/api.py +0 -0
  57. {structtype-0.9.0 → structtype-0.10.0}/tests/typecheck/basic.py +0 -0
  58. {structtype-0.9.0 → 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` |
@@ -49,7 +51,7 @@ All commands go through `make`.
49
51
 
50
52
  ### Struct Methods
51
53
 
52
- - `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
53
55
  - `obj.struct_dump(*, sort_keys=False, str_keys=False, builtin_types=None)` — convert to built-in Python types (uses `alias` for keys)
54
56
  - `obj.struct_check_types()` — validate field values against types + constraints (pure type-check, no conversion)
55
57
  - `cls.struct_validate_json(buf, *, strict=True)` — deserialize from JSON
@@ -63,7 +65,8 @@ Struct instances support the mapping protocol:
63
65
 
64
66
  ## Gotchas
65
67
 
66
- - `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/`.
67
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.
68
71
 
69
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.9.0
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"]
@@ -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(
@@ -166,12 +164,41 @@ class Constraint:
166
164
 
167
165
  @final
168
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: ...
@@ -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