pytypehint 1.2.1__tar.gz → 1.2.2__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.
- {pytypehint-1.2.1 → pytypehint-1.2.2}/CHANGELOG.md +29 -12
- pytypehint-1.2.2/PKG-INFO +66 -0
- pytypehint-1.2.2/README.md +42 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/docs/build.md +1 -1
- {pytypehint-1.2.1 → pytypehint-1.2.2}/docs/contract.md +3 -3
- pytypehint-1.2.1/docs/philosophy.md → pytypehint-1.2.2/docs/design.md +11 -1
- pytypehint-1.2.1/docs/restrictions.md → pytypehint-1.2.2/docs/limits.md +8 -7
- pytypehint-1.2.2/docs/overview.md +105 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/docs/vocabulary.md +6 -8
- {pytypehint-1.2.1 → pytypehint-1.2.2}/pyproject.toml +14 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/src/pytypehint/__init__.py +1 -1
- {pytypehint-1.2.1 → pytypehint-1.2.2}/tests/test_hardening_a.py +1 -1
- pytypehint-1.2.1/PKG-INFO +0 -162
- pytypehint-1.2.1/README.md +0 -148
- pytypehint-1.2.1/docs/comparison.md +0 -18
- pytypehint-1.2.1/docs/guarantees.md +0 -24
- {pytypehint-1.2.1 → pytypehint-1.2.2}/.github/dependabot.yml +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/.github/workflows/ci.yml +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/.github/workflows/publish.yml +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/.gitignore +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/LICENSE +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/docs/atoms.md +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/docs/decode.md +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/docs/defaults.md +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/docs/immutable.md +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/docs/resolve.md +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/docs/tuples.md +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/src/pytypehint/atoms.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/src/pytypehint/bridge.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/src/pytypehint/contract.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/src/pytypehint/errors.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/src/pytypehint/immutable.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/src/pytypehint/py.typed +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/src/pytypehint/shapes.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/src/pytypehint/signature.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/src/pytypehint/structure.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/src/pytypehint/utils.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/src/pytypehint/validation.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/tests/conftest.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/tests/test_annotated.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/tests/test_annotated_hoisting_properties.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/tests/test_atom_argument_validation.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/tests/test_atom_table_reflection.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/tests/test_atoms.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/tests/test_bool.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/tests/test_bridge.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/tests/test_build.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/tests/test_certification.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/tests/test_collection_and_recursion_matrix.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/tests/test_composition.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/tests/test_contract.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/tests/test_contract_graph.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/tests/test_crossround_x.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/tests/test_crossround_y.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/tests/test_datetime.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/tests/test_decode.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/tests/test_decode_boundary.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/tests/test_decode_routing.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/tests/test_default_container_isolation.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/tests/test_default_types.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/tests/test_discriminated_wrapper.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/tests/test_discriminator.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/tests/test_documentation_contract.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/tests/test_enum.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/tests/test_equality.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/tests/test_errors_pickle.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/tests/test_extra.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/tests/test_field_dataclass_surface.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/tests/test_file_hint.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/tests/test_final_restrictions.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/tests/test_float.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/tests/test_hardening_b.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/tests/test_hardening_c.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/tests/test_hardening_d.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/tests/test_hardening_e.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/tests/test_hardening_f.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/tests/test_hardening_g.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/tests/test_immutable.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/tests/test_initvar_rejection.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/tests/test_instance_validation_and_homonym_enums.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/tests/test_int.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/tests/test_list.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/tests/test_literal.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/tests/test_matches_no_option_notes.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/tests/test_missing.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/tests/test_none.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/tests/test_none_transparency.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/tests/test_numeric_constraint_validation.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/tests/test_optional_toggle.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/tests/test_portable_properties.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/tests/test_presentation_atoms_surface.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/tests/test_recursive.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/tests/test_remat_runtime_failure.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/tests/test_rematerialization.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/tests/test_resolve.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/tests/test_scalar_constraint_validation.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/tests/test_scalar_option_identity.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/tests/test_scalar_type_strictness.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/tests/test_schema_model_invariants.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/tests/test_shape_constraint_certification.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/tests/test_shape_invariants.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/tests/test_signature.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/tests/test_signature_build.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/tests/test_signature_field_rejection.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/tests/test_single_pass_validation.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/tests/test_str.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/tests/test_string_and_literal_integration.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/tests/test_struct.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/tests/test_structured_errors.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/tests/test_temporal_constraint_validation.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/tests/test_time_second_precision.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/tests/test_tuple.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/tests/test_union_field_atoms.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/tests/test_union_option_collisions.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/tests/test_union_routing_matrix.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/tests/test_unions.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/tests/test_validate.py +0 -0
- {pytypehint-1.2.1 → pytypehint-1.2.2}/tests/test_wrapper_layer_composition.py +0 -0
|
@@ -1,11 +1,28 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## 1.2.2 - 2026-09-29
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- Documentation only: the README becomes a short entrance to the documentation
|
|
8
|
+
site at https://offerrall.github.io/pytypehint/, and `docs/overview.md` holds
|
|
9
|
+
the introduction.
|
|
10
|
+
- Rename `docs/restrictions.md` to `docs/limits.md` ("Limits") and
|
|
11
|
+
`docs/philosophy.md` to `docs/design.md`. Merge
|
|
12
|
+
`docs/comparison.md` into Design and remove `docs/guarantees.md`, whose points
|
|
13
|
+
each live on their own page.
|
|
14
|
+
- Add `[project.urls]` (documentation, repository, changelog) and Python
|
|
15
|
+
classifiers to the package metadata.
|
|
16
|
+
- Fix the 1.1.0 release date in this changelog and date every entry.
|
|
17
|
+
|
|
18
|
+
The code is the same as 1.2.1.
|
|
19
|
+
|
|
20
|
+
## 1.2.1 - 2026-09-29
|
|
4
21
|
|
|
5
22
|
- Documentation only: the README title no longer carries the version, and the
|
|
6
23
|
1.2.0 entry below has its release date. The code is the same as 1.2.0.
|
|
7
24
|
|
|
8
|
-
##
|
|
25
|
+
## 1.2.0 - 2026-09-20
|
|
9
26
|
|
|
10
27
|
- Add `@immutable`: deeply immutable, slotted, keyword-only dataclasses with
|
|
11
28
|
automatic validation on construction and `dataclasses.replace`.
|
|
@@ -18,7 +35,7 @@
|
|
|
18
35
|
- Keep the implementation in `immutable.py`; existing schema operations retain
|
|
19
36
|
their behavior. Add examples and the immutable model contract.
|
|
20
37
|
|
|
21
|
-
##
|
|
38
|
+
## 1.1.0 - 2026-09-20
|
|
22
39
|
|
|
23
40
|
- Add fixed tuples (`tuple[X, Y]`), variadic tuples (`tuple[X, ...]`) and empty
|
|
24
41
|
tuples (`tuple[()]`), including nested types, unions and per-position atoms.
|
|
@@ -31,7 +48,7 @@
|
|
|
31
48
|
`v: 1`; consumers need tuple support to read the new shape type.
|
|
32
49
|
- Shorten the README and move detailed guarantees and API reference into docs.
|
|
33
50
|
|
|
34
|
-
##
|
|
51
|
+
## 1.0.0 - 2026-08-10
|
|
35
52
|
|
|
36
53
|
The core now owns the portable form of its own types. Until this release a
|
|
37
54
|
compiled schema could only be read as Python objects, in the process that
|
|
@@ -98,7 +115,7 @@ then what validation and its errors guarantee.
|
|
|
98
115
|
`date`, or one named `list[str]` beside a `list[str]` now fail at compilation
|
|
99
116
|
with `Field 'x': duplicate discriminator name(s): str`. Each such pair left two
|
|
100
117
|
options answering to one `$type`, with one of them unreachable — the rule
|
|
101
|
-
[
|
|
118
|
+
[limits.md](docs/limits.md) already stated, now enforced where the
|
|
102
119
|
implementation had only covered options sharing a runtime type. A dataclass and
|
|
103
120
|
an enum of the same class name use different discriminators and remain
|
|
104
121
|
admissible.
|
|
@@ -331,7 +348,7 @@ then what validation and its errors guarantee.
|
|
|
331
348
|
while filling does not; `restrictions.md` documents the discriminator-name rule
|
|
332
349
|
and the union options a portable tree cannot spell bare.
|
|
333
350
|
|
|
334
|
-
##
|
|
351
|
+
## 0.0.7 - 2026-07-26
|
|
335
352
|
|
|
336
353
|
- Breaking: `IsPathFile` now guarantees that the string names a real file at the
|
|
337
354
|
moment of validation, not merely that its text ends in an accepted suffix. A
|
|
@@ -376,7 +393,7 @@ then what validation and its errors guarantee.
|
|
|
376
393
|
exclusive to `Str`: no new shape, no `PathFile` type, and no compatibility
|
|
377
394
|
switch (`exists=False`, `strict=False`) — the semantics are single and explicit.
|
|
378
395
|
|
|
379
|
-
##
|
|
396
|
+
## 0.0.6 - 2026-07-24
|
|
380
397
|
|
|
381
398
|
- Breaking: `datetime.time` values with non-zero microseconds are no longer
|
|
382
399
|
accepted. Time precision is limited to whole seconds, so the effective range
|
|
@@ -393,7 +410,7 @@ then what validation and its errors guarantee.
|
|
|
393
410
|
`exclusive bound at ... leaves no valid time`. A bound at `time.max` is instead
|
|
394
411
|
rejected as sub-second precision.
|
|
395
412
|
|
|
396
|
-
##
|
|
413
|
+
## 0.0.5 - 2026-07-23
|
|
397
414
|
|
|
398
415
|
- Compilation now rejects a union of two enums that share a class name, e.g.
|
|
399
416
|
two Enum classes both named `Color`, with `Field '<name>': duplicate
|
|
@@ -405,7 +422,7 @@ then what validation and its errors guarantee.
|
|
|
405
422
|
a dataclass of the same name never collide and stay admissible. Previously the
|
|
406
423
|
core admitted the pair and only a wrapper could catch it.
|
|
407
424
|
|
|
408
|
-
##
|
|
425
|
+
## 0.0.4 - 2026-07-22
|
|
409
426
|
|
|
410
427
|
- Enum fields now accept `Extra`, the same namespaced wrapper-notation channel
|
|
411
428
|
the other leaf shapes already carry. `EnumShape` gains `_extras` and a
|
|
@@ -430,7 +447,7 @@ then what validation and its errors guarantee.
|
|
|
430
447
|
degraded the whole line into the leaf and rendered `Field 'x': default <leaf>`.
|
|
431
448
|
Only the message and its structure changed; no behaviour did.
|
|
432
449
|
|
|
433
|
-
##
|
|
450
|
+
## 0.0.3 - 2026-07-20
|
|
434
451
|
|
|
435
452
|
- Unions whose options share one runtime input type now compile. `list[str] |
|
|
436
453
|
list[int]` was rejected as a duplicate; both options are valid Python and
|
|
@@ -453,13 +470,13 @@ then what validation and its errors guarantee.
|
|
|
453
470
|
- No breaking change: every hint accepted by 0.0.2 compiles and behaves as
|
|
454
471
|
before.
|
|
455
472
|
|
|
456
|
-
##
|
|
473
|
+
## 0.0.2 - 2026-07-17
|
|
457
474
|
|
|
458
475
|
- `Extra(value)` becomes `Extra(key, value)`, with a namespaced key
|
|
459
476
|
(`"package.name"`) and any string value, empty included.
|
|
460
477
|
- Shapes replace `extra` with `extras`, a read-only `dict[str, str]` merged from
|
|
461
478
|
every `Extra` atom on the hint. Keys layer independently: the outer atom wins.
|
|
462
479
|
|
|
463
|
-
##
|
|
480
|
+
## 0.0.1 - 2026-07-15
|
|
464
481
|
|
|
465
482
|
- Initial release.
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: pytypehint
|
|
3
|
+
Version: 1.2.2
|
|
4
|
+
Summary: Compiles Python type hints into strict, inspectable, portable contracts
|
|
5
|
+
Project-URL: Documentation, https://offerrall.github.io/pytypehint/
|
|
6
|
+
Project-URL: Repository, https://github.com/offerrall/pytypehint
|
|
7
|
+
Project-URL: Changelog, https://github.com/offerrall/pytypehint/blob/master/CHANGELOG.md
|
|
8
|
+
Author: Beltrán Offerrall
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Typing :: Typed
|
|
18
|
+
Requires-Python: >=3.11
|
|
19
|
+
Provides-Extra: dev
|
|
20
|
+
Requires-Dist: hypothesis; extra == 'dev'
|
|
21
|
+
Requires-Dist: mypy; extra == 'dev'
|
|
22
|
+
Requires-Dist: pytest; extra == 'dev'
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
# pytypehint
|
|
26
|
+
|
|
27
|
+
Define data once with ordinary Python type hints and dataclasses. pytypehint
|
|
28
|
+
compiles them into strict schemas that validate input with exact types and no
|
|
29
|
+
implicit coercion, build nested dataclasses, and export a portable description
|
|
30
|
+
another process can read.
|
|
31
|
+
|
|
32
|
+
Use `@immutable` for deeply immutable models that validate themselves and reuse
|
|
33
|
+
already validated children. pytypehint is the schema and validation core of
|
|
34
|
+
[func-to-web](https://offerrall.github.io/func-to-web/).
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
from dataclasses import dataclass
|
|
38
|
+
from typing import Annotated
|
|
39
|
+
from pytypehint import Min, struct_of
|
|
40
|
+
|
|
41
|
+
@dataclass
|
|
42
|
+
class Item:
|
|
43
|
+
name: str
|
|
44
|
+
quantity: Annotated[int, Min(1)] = 1
|
|
45
|
+
|
|
46
|
+
schema = struct_of(Item)
|
|
47
|
+
item = schema.build({"name": "Pen"}) # Item(name='Pen', quantity=1)
|
|
48
|
+
schema.build({"name": "Pen", "quantity": 0}) # SchemaValueError: quantity
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
The full documentation is at https://offerrall.github.io/pytypehint/.
|
|
52
|
+
|
|
53
|
+
## Documentation
|
|
54
|
+
|
|
55
|
+
- [Overview](docs/overview.md): immutable models, portable data, field metadata and the four operations.
|
|
56
|
+
- [Immutable models](docs/immutable.md): the `@immutable` contract, allowed fields, reuse and Python behavior.
|
|
57
|
+
- [Types and API](docs/vocabulary.md): which hints compile to which shapes, and the public API.
|
|
58
|
+
- [Atoms](docs/atoms.md): constraints and notation per shape, file names, layering and extras.
|
|
59
|
+
- [Build](docs/build.md): validating and constructing instances or keyword arguments, union selection and errors.
|
|
60
|
+
- [Resolve](docs/resolve.md): validated dictionaries with defaults filled at the current level.
|
|
61
|
+
- [Decode](docs/decode.md): restoring portable values such as dates, enums and tuples before validation.
|
|
62
|
+
- [Tuples](docs/tuples.md): fixed, variadic and empty tuples and the `Tuple` shape.
|
|
63
|
+
- [Defaults](docs/defaults.md): certification and rematerialization of default values.
|
|
64
|
+
- [Portable contract](docs/contract.md): the JSON-compatible schema document returned by `to_dict()`.
|
|
65
|
+
- [Limits](docs/limits.md): rejected definitions, option identity and runtime limits.
|
|
66
|
+
- [Design](docs/design.md): what the core owns, what consumers own, and why.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# pytypehint
|
|
2
|
+
|
|
3
|
+
Define data once with ordinary Python type hints and dataclasses. pytypehint
|
|
4
|
+
compiles them into strict schemas that validate input with exact types and no
|
|
5
|
+
implicit coercion, build nested dataclasses, and export a portable description
|
|
6
|
+
another process can read.
|
|
7
|
+
|
|
8
|
+
Use `@immutable` for deeply immutable models that validate themselves and reuse
|
|
9
|
+
already validated children. pytypehint is the schema and validation core of
|
|
10
|
+
[func-to-web](https://offerrall.github.io/func-to-web/).
|
|
11
|
+
|
|
12
|
+
```python
|
|
13
|
+
from dataclasses import dataclass
|
|
14
|
+
from typing import Annotated
|
|
15
|
+
from pytypehint import Min, struct_of
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class Item:
|
|
19
|
+
name: str
|
|
20
|
+
quantity: Annotated[int, Min(1)] = 1
|
|
21
|
+
|
|
22
|
+
schema = struct_of(Item)
|
|
23
|
+
item = schema.build({"name": "Pen"}) # Item(name='Pen', quantity=1)
|
|
24
|
+
schema.build({"name": "Pen", "quantity": 0}) # SchemaValueError: quantity
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The full documentation is at https://offerrall.github.io/pytypehint/.
|
|
28
|
+
|
|
29
|
+
## Documentation
|
|
30
|
+
|
|
31
|
+
- [Overview](docs/overview.md): immutable models, portable data, field metadata and the four operations.
|
|
32
|
+
- [Immutable models](docs/immutable.md): the `@immutable` contract, allowed fields, reuse and Python behavior.
|
|
33
|
+
- [Types and API](docs/vocabulary.md): which hints compile to which shapes, and the public API.
|
|
34
|
+
- [Atoms](docs/atoms.md): constraints and notation per shape, file names, layering and extras.
|
|
35
|
+
- [Build](docs/build.md): validating and constructing instances or keyword arguments, union selection and errors.
|
|
36
|
+
- [Resolve](docs/resolve.md): validated dictionaries with defaults filled at the current level.
|
|
37
|
+
- [Decode](docs/decode.md): restoring portable values such as dates, enums and tuples before validation.
|
|
38
|
+
- [Tuples](docs/tuples.md): fixed, variadic and empty tuples and the `Tuple` shape.
|
|
39
|
+
- [Defaults](docs/defaults.md): certification and rematerialization of default values.
|
|
40
|
+
- [Portable contract](docs/contract.md): the JSON-compatible schema document returned by `to_dict()`.
|
|
41
|
+
- [Limits](docs/limits.md): rejected definitions, option identity and runtime limits.
|
|
42
|
+
- [Design](docs/design.md): what the core owns, what consumers own, and why.
|
|
@@ -42,7 +42,7 @@ Dataclass discriminators use the class name. Other wrappers use
|
|
|
42
42
|
Discriminators are accepted only where needed, including inside sequence items.
|
|
43
43
|
A lone dataclass rejects `$type` as an unexpected key. When dataclass and wrapped
|
|
44
44
|
options coexist, `$value` distinguishes the wrapper from a dataclass dictionary.
|
|
45
|
-
See [option identities](
|
|
45
|
+
See [option identities](limits.md#option-identity).
|
|
46
46
|
|
|
47
47
|
`resolve` preserves validated discriminators; `build` removes them while
|
|
48
48
|
constructing the selected values. Defaults are Python values and need no wrapper.
|
|
@@ -37,8 +37,8 @@ used in data discriminators; consumers must not substitute the definition ID.
|
|
|
37
37
|
|
|
38
38
|
`v` versions the format, not the library. Removing a key or changing its meaning
|
|
39
39
|
raises `v`; additive keys do not. Ignore unknown keys and reject unsupported shape
|
|
40
|
-
types.
|
|
41
|
-
|
|
40
|
+
types. Format version 1 includes `tuple` nodes; consumers need tuple support to
|
|
41
|
+
read them.
|
|
42
42
|
|
|
43
43
|
## Fields and nodes
|
|
44
44
|
|
|
@@ -51,7 +51,7 @@ There are no `optional`, `required` or `nullable` keys: inspect `default` and
|
|
|
51
51
|
Each node has `type`. In a slot containing several options, each also has `id`,
|
|
52
52
|
its `Shape.option_id()`. IDs are unique within the dataclass namespace and within
|
|
53
53
|
the other-option namespace, not across both. Preserve option positions; use IDs
|
|
54
|
-
for discriminators. See [option identities](
|
|
54
|
+
for discriminators. See [option identities](limits.md#option-identity).
|
|
55
55
|
|
|
56
56
|
| `type` | Additional keys when applicable |
|
|
57
57
|
|---|---|
|
|
@@ -5,7 +5,17 @@ into inspectable shapes, validate exact values, and construct ordinary objects.
|
|
|
5
5
|
|
|
6
6
|
The core owns types, constraints, defaults and their portable representation.
|
|
7
7
|
Consumers own input coercion, presentation, JSON text, function invocation and
|
|
8
|
-
checks requiring files, networks or application state
|
|
8
|
+
checks requiring files, networks or application state:
|
|
9
|
+
|
|
10
|
+
| Task | Responsible layer |
|
|
11
|
+
|---|---|
|
|
12
|
+
| Define ordinary models | Python dataclasses and type hints |
|
|
13
|
+
| Inspect/export schemas, validate values, restore portable types, construct models | pytypehint |
|
|
14
|
+
| Parse JSON, coerce form/CLI input, render controls, collect multiple errors | Consumer |
|
|
15
|
+
| Call functions, check files, manage persistence/authentication | Application |
|
|
16
|
+
|
|
17
|
+
Direct dataclass construction is simpler for trusted internal objects. Use
|
|
18
|
+
pytypehint when you need strict input validation or a shared inspectable contract.
|
|
9
19
|
|
|
10
20
|
Keep union selection explicit when types or portable spellings collide. Keep
|
|
11
21
|
missing values separate from `None`. Report the first error with its path.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Limits
|
|
2
2
|
|
|
3
3
|
Only the [documented types](vocabulary.md) and [atoms](atoms.md) are supported.
|
|
4
4
|
Unsupported hints and invalid schemas fail during compilation.
|
|
@@ -25,7 +25,9 @@ def search(query: str):
|
|
|
25
25
|
```
|
|
26
26
|
|
|
27
27
|
`datetime`, path objects, mappings, sets and arbitrary classes are outside the
|
|
28
|
-
vocabulary.
|
|
28
|
+
vocabulary. Represent timestamp policy and other structure with a dataclass, and
|
|
29
|
+
file names with a `str` annotated with [`FileHint`](atoms.md#file-names), which
|
|
30
|
+
checks the suffix only.
|
|
29
31
|
|
|
30
32
|
## Option identity
|
|
31
33
|
|
|
@@ -66,8 +68,7 @@ contents never choose the variant.
|
|
|
66
68
|
- Validation stops at the first error. Constructor exceptions propagate unchanged.
|
|
67
69
|
- Cycles and excessive nesting may raise raw `RecursionError` in decode, resolve
|
|
68
70
|
or build. Recursive schemas are supported; cyclic input values are not.
|
|
69
|
-
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
even when their restored Python types would be distinct.
|
|
71
|
+
- [Default recipes](defaults.md) must be pure, and input must not change during
|
|
72
|
+
construction.
|
|
73
|
+
- Portable spelling collisions need [decode wrappers](decode.md#unions), even
|
|
74
|
+
when their restored Python types would be distinct.
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# Overview
|
|
2
|
+
|
|
3
|
+
pytypehint turns a dataclass or a function signature into a compiled schema:
|
|
4
|
+
`struct_of(Item)` returns a `Struct`, `signature_of(fn)` a `Signature`. Compile
|
|
5
|
+
once and reuse. Validation is exact: `float` rejects `int`, `int` rejects `bool`,
|
|
6
|
+
and nothing is coerced. The first error raises `SchemaTypeError` or
|
|
7
|
+
`SchemaValueError` with the failing field or index in its `path`.
|
|
8
|
+
|
|
9
|
+
## Immutable models
|
|
10
|
+
|
|
11
|
+
Declare constraints once. Normal construction and `dataclasses.replace` validate
|
|
12
|
+
automatically; nested instances keep their identity.
|
|
13
|
+
|
|
14
|
+
```python
|
|
15
|
+
from dataclasses import replace
|
|
16
|
+
from typing import Annotated
|
|
17
|
+
from pytypehint import Max, Min, immutable
|
|
18
|
+
|
|
19
|
+
Positive = Annotated[int, Min(1)]
|
|
20
|
+
Channel = Annotated[float, Min(0.0), Max(1.0)]
|
|
21
|
+
|
|
22
|
+
@immutable
|
|
23
|
+
class Exposure:
|
|
24
|
+
stops: float = 0.0
|
|
25
|
+
|
|
26
|
+
@immutable
|
|
27
|
+
class Grayscale:
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
@immutable
|
|
31
|
+
class Layer:
|
|
32
|
+
name: Annotated[str, Min(1)]
|
|
33
|
+
effect: Exposure | Grayscale
|
|
34
|
+
color: tuple[Channel, Channel, Channel, Channel] = (0.0, 0.0, 0.0, 1.0)
|
|
35
|
+
|
|
36
|
+
@immutable
|
|
37
|
+
class Document:
|
|
38
|
+
size: tuple[Positive, Positive]
|
|
39
|
+
layers: tuple[Layer, ...] = ()
|
|
40
|
+
|
|
41
|
+
photo = Layer(name="Photo", effect=Exposure(stops=1.5))
|
|
42
|
+
original = Document(size=(1920, 1080), layers=(photo,))
|
|
43
|
+
resized = replace(original, size=(3840, 2160))
|
|
44
|
+
|
|
45
|
+
assert resized.layers[0] is photo
|
|
46
|
+
assert original.size == (1920, 1080)
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
The new document checks its size and layer references, without revisiting the
|
|
50
|
+
existing layer's fields. Tuples are checked; existing immutable children are reused.
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
replace(photo, color=(1.0, 0.0, 0.0, 2.0)) # SchemaValueError, path: ('color', 3)
|
|
54
|
+
Exposure(stops="1.5") # SchemaTypeError
|
|
55
|
+
original.size = (800, 600) # FrozenInstanceError
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Fields are restricted to immutable scalars, tuples and other `@immutable` models;
|
|
59
|
+
see [Immutable models](immutable.md) for the full contract, inheritance and defaults.
|
|
60
|
+
|
|
61
|
+
## Portable data and inspection
|
|
62
|
+
|
|
63
|
+
The same models work with the schema API. `decode` restores portable values such
|
|
64
|
+
as JSON arrays and discriminated unions, and `to_dict` describes the schema for a
|
|
65
|
+
UI or another process:
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
from pytypehint import struct_of
|
|
69
|
+
|
|
70
|
+
schema = struct_of(Document)
|
|
71
|
+
loaded = schema.build(schema.decode({
|
|
72
|
+
"size": [1920, 1080],
|
|
73
|
+
"layers": [{
|
|
74
|
+
"name": "Photo",
|
|
75
|
+
"effect": {"$type": "Exposure", "stops": 1.5},
|
|
76
|
+
}],
|
|
77
|
+
}))
|
|
78
|
+
|
|
79
|
+
assert loaded == original
|
|
80
|
+
contract = schema.to_dict() # Portable description for a UI or another process
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Type constraints and interface metadata live on the same field:
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
from pytypehint import Label, Slider, Step
|
|
87
|
+
|
|
88
|
+
@immutable
|
|
89
|
+
class Brush:
|
|
90
|
+
size: Annotated[int, Min(1), Max(256), Label("Brush size"), Step(1), Slider()] = 32
|
|
91
|
+
|
|
92
|
+
size_field, = struct_of(Brush).fields
|
|
93
|
+
size_shape, = size_field.shape
|
|
94
|
+
assert size_field.label.value == "Brush size"
|
|
95
|
+
assert size_shape.max.value == 256
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## The four operations
|
|
99
|
+
|
|
100
|
+
| Method | Result |
|
|
101
|
+
|---|---|
|
|
102
|
+
| [`build(data)`](build.md) | Dataclass instance; `signature_of(fn)` returns kwargs for `fn(**kwargs)` |
|
|
103
|
+
| [`resolve(data)`](resolve.md) | Validated dictionary with missing defaults filled at this level |
|
|
104
|
+
| [`decode(data)`](decode.md) | Portable values restored to Python types |
|
|
105
|
+
| [`to_dict()`](contract.md) | Portable schema description |
|
|
@@ -32,10 +32,7 @@ either: list[str] | list[int] # needs {"$type": "list[str]", "$value": ["a"]}
|
|
|
32
32
|
Union options retain declaration order. Put type constraints on the option:
|
|
33
33
|
`Annotated[int, Min(0)] | str`. Field notation such as `Label` belongs on the
|
|
34
34
|
outer field. See [atoms](atoms.md), [tuples](tuples.md) and
|
|
35
|
-
[
|
|
36
|
-
|
|
37
|
-
There is no `datetime` or path shape. Represent timestamp policy with a dataclass;
|
|
38
|
-
use a `str` annotated with `FileHint` for file names.
|
|
35
|
+
[limits](limits.md).
|
|
39
36
|
|
|
40
37
|
## Public API
|
|
41
38
|
|
|
@@ -51,10 +48,11 @@ Everything public is exported from `pytypehint`:
|
|
|
51
48
|
`Extra`, `OptionalToggle`;
|
|
52
49
|
- `MISSING`.
|
|
53
50
|
|
|
54
|
-
`Struct` and `Signature` expose
|
|
55
|
-
|
|
56
|
-
`
|
|
57
|
-
|
|
51
|
+
`Struct` and `Signature` expose the [four operations](overview.md#the-four-operations):
|
|
52
|
+
`.build(data)`, `.resolve(data)`, `.decode(data)` and `.to_dict()`.
|
|
53
|
+
`Struct.fields` and `Signature.params` contain `Field` objects; `Field.shape`
|
|
54
|
+
contains the available shapes, and `Field.default` is `MISSING` when no default
|
|
55
|
+
exists. `Shape.option_id()` gives the discriminator identity.
|
|
58
56
|
`Struct`, `Field` and `Signature` compare by identity; compile once and reuse.
|
|
59
57
|
|
|
60
58
|
`@immutable` creates deeply immutable, automatically validated dataclasses with a
|
|
@@ -12,6 +12,20 @@ license = "MIT"
|
|
|
12
12
|
license-files = ["LICENSE"]
|
|
13
13
|
authors = [{ name = "Beltrán Offerrall" }]
|
|
14
14
|
dependencies = []
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
18
|
+
"Programming Language :: Python :: 3.11",
|
|
19
|
+
"Programming Language :: Python :: 3.12",
|
|
20
|
+
"Programming Language :: Python :: 3.13",
|
|
21
|
+
"Operating System :: OS Independent",
|
|
22
|
+
"Typing :: Typed",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
[project.urls]
|
|
26
|
+
Documentation = "https://offerrall.github.io/pytypehint/"
|
|
27
|
+
Repository = "https://github.com/offerrall/pytypehint"
|
|
28
|
+
Changelog = "https://github.com/offerrall/pytypehint/blob/master/CHANGELOG.md"
|
|
15
29
|
|
|
16
30
|
[project.optional-dependencies]
|
|
17
31
|
dev = ["mypy", "pytest", "hypothesis"]
|
|
@@ -505,7 +505,7 @@ def test_no_module_level_name_escapes_all_by_accident():
|
|
|
505
505
|
|
|
506
506
|
|
|
507
507
|
def test_the_version_is_the_one_this_release_claims():
|
|
508
|
-
assert pytypehint.__version__ == "1.2.
|
|
508
|
+
assert pytypehint.__version__ == "1.2.2"
|
|
509
509
|
assert "__version__" not in pytypehint.__all__
|
|
510
510
|
|
|
511
511
|
|
pytypehint-1.2.1/PKG-INFO
DELETED
|
@@ -1,162 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.5
|
|
2
|
-
Name: pytypehint
|
|
3
|
-
Version: 1.2.1
|
|
4
|
-
Summary: Compiles Python type hints into strict, inspectable, portable contracts
|
|
5
|
-
Author: Beltrán Offerrall
|
|
6
|
-
License-Expression: MIT
|
|
7
|
-
License-File: LICENSE
|
|
8
|
-
Requires-Python: >=3.11
|
|
9
|
-
Provides-Extra: dev
|
|
10
|
-
Requires-Dist: hypothesis; extra == 'dev'
|
|
11
|
-
Requires-Dist: mypy; extra == 'dev'
|
|
12
|
-
Requires-Dist: pytest; extra == 'dev'
|
|
13
|
-
Description-Content-Type: text/markdown
|
|
14
|
-
|
|
15
|
-
# pytypehint
|
|
16
|
-
|
|
17
|
-
[](https://pypi.org/project/pytypehint/)
|
|
18
|
-
|
|
19
|
-
Define data with Python type hints. Validate input, build nested dataclasses,
|
|
20
|
-
inspect their fields or export a portable schema.
|
|
21
|
-
|
|
22
|
-
Use **`@immutable`** for deeply immutable models that validate themselves and
|
|
23
|
-
reuse validated children without checking their contents again.
|
|
24
|
-
|
|
25
|
-
Exact types, no implicit coercion. Python 3.11+, standard library only.
|
|
26
|
-
The schema and validation core of [FuncToWeb](https://github.com/offerrall/FuncToWeb).
|
|
27
|
-
|
|
28
|
-
```bash
|
|
29
|
-
pip install pytypehint
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
## Validate input into ordinary dataclasses
|
|
33
|
-
|
|
34
|
-
```python
|
|
35
|
-
from dataclasses import dataclass
|
|
36
|
-
from typing import Annotated
|
|
37
|
-
from pytypehint import Min, struct_of
|
|
38
|
-
|
|
39
|
-
@dataclass
|
|
40
|
-
class Item:
|
|
41
|
-
name: str
|
|
42
|
-
quantity: Annotated[int, Min(1)] = 1
|
|
43
|
-
|
|
44
|
-
schema = struct_of(Item)
|
|
45
|
-
item = schema.build({"name": "Pen"}) # Item(name='Pen', quantity=1)
|
|
46
|
-
schema.build({"name": "Pen", "quantity": 0}) # SchemaValueError: quantity
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
Compile once and reuse. Errors include the failing field or index in their `path`.
|
|
50
|
-
|
|
51
|
-
## Immutable models
|
|
52
|
-
|
|
53
|
-
Declare constraints once. Normal construction and `dataclasses.replace` validate
|
|
54
|
-
automatically; nested instances keep their identity.
|
|
55
|
-
|
|
56
|
-
```python
|
|
57
|
-
from dataclasses import replace
|
|
58
|
-
from typing import Annotated
|
|
59
|
-
from pytypehint import Max, Min, immutable
|
|
60
|
-
|
|
61
|
-
Positive = Annotated[int, Min(1)]
|
|
62
|
-
Channel = Annotated[float, Min(0.0), Max(1.0)]
|
|
63
|
-
|
|
64
|
-
@immutable
|
|
65
|
-
class Exposure:
|
|
66
|
-
stops: float = 0.0
|
|
67
|
-
|
|
68
|
-
@immutable
|
|
69
|
-
class Grayscale:
|
|
70
|
-
pass
|
|
71
|
-
|
|
72
|
-
@immutable
|
|
73
|
-
class Layer:
|
|
74
|
-
name: Annotated[str, Min(1)]
|
|
75
|
-
effect: Exposure | Grayscale
|
|
76
|
-
color: tuple[Channel, Channel, Channel, Channel] = (0.0, 0.0, 0.0, 1.0)
|
|
77
|
-
|
|
78
|
-
@immutable
|
|
79
|
-
class Document:
|
|
80
|
-
size: tuple[Positive, Positive]
|
|
81
|
-
layers: tuple[Layer, ...] = ()
|
|
82
|
-
|
|
83
|
-
photo = Layer(name="Photo", effect=Exposure(stops=1.5))
|
|
84
|
-
original = Document(size=(1920, 1080), layers=(photo,))
|
|
85
|
-
resized = replace(original, size=(3840, 2160))
|
|
86
|
-
|
|
87
|
-
assert resized.layers[0] is photo
|
|
88
|
-
assert original.size == (1920, 1080)
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
The new document checks its size and layer references, without revisiting the
|
|
92
|
-
existing layer's fields. Tuples are checked; existing immutable children are reused.
|
|
93
|
-
|
|
94
|
-
```python
|
|
95
|
-
replace(photo, color=(1.0, 0.0, 0.0, 2.0)) # SchemaValueError, path: ('color', 3)
|
|
96
|
-
Exposure(stops="1.5") # SchemaTypeError
|
|
97
|
-
original.size = (800, 600) # FrozenInstanceError
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
Allowed fields are immutable scalars, tuples and other `@immutable` models.
|
|
101
|
-
Mutable containers, ordinary dataclasses and enums are rejected. The guarantee
|
|
102
|
-
covers declared fields through normal Python use; deliberate low-level mutation
|
|
103
|
-
can bypass it. [Full contract, inheritance and defaults](docs/immutable.md).
|
|
104
|
-
|
|
105
|
-
## Load portable data and inspect the same definition
|
|
106
|
-
|
|
107
|
-
The models above also work with the existing schema API:
|
|
108
|
-
|
|
109
|
-
```python
|
|
110
|
-
from pytypehint import struct_of
|
|
111
|
-
|
|
112
|
-
schema = struct_of(Document)
|
|
113
|
-
loaded = schema.build(schema.decode({
|
|
114
|
-
"size": [1920, 1080],
|
|
115
|
-
"layers": [{
|
|
116
|
-
"name": "Photo",
|
|
117
|
-
"effect": {"$type": "Exposure", "stops": 1.5},
|
|
118
|
-
}],
|
|
119
|
-
}))
|
|
120
|
-
|
|
121
|
-
assert loaded == original
|
|
122
|
-
contract = schema.to_dict() # Portable description for a UI or another process
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
Type constraints and interface metadata live on the same field:
|
|
126
|
-
|
|
127
|
-
```python
|
|
128
|
-
from pytypehint import Label, Slider, Step
|
|
129
|
-
|
|
130
|
-
@immutable
|
|
131
|
-
class Brush:
|
|
132
|
-
size: Annotated[int, Min(1), Max(256), Label("Brush size"), Step(1), Slider()] = 32
|
|
133
|
-
|
|
134
|
-
size_field, = struct_of(Brush).fields
|
|
135
|
-
size_shape, = size_field.shape
|
|
136
|
-
assert size_field.label.value == "Brush size"
|
|
137
|
-
assert size_shape.max.value == 256
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
| Method | Result |
|
|
141
|
-
|---|---|
|
|
142
|
-
| `build(data)` | Dataclass instance; `signature_of(fn)` returns kwargs for `fn(**kwargs)` |
|
|
143
|
-
| `resolve(data)` | Validated dictionary with missing defaults filled at this level |
|
|
144
|
-
| `decode(data)` | Portable values restored to Python types |
|
|
145
|
-
| `to_dict()` | Portable schema description |
|
|
146
|
-
|
|
147
|
-
## Documentation
|
|
148
|
-
|
|
149
|
-
- [Immutable models](docs/immutable.md)
|
|
150
|
-
- [Types and API](docs/vocabulary.md)
|
|
151
|
-
- [Atoms](docs/atoms.md)
|
|
152
|
-
- [Build](docs/build.md)
|
|
153
|
-
- [Resolve](docs/resolve.md)
|
|
154
|
-
- [Decode](docs/decode.md)
|
|
155
|
-
- [Tuples](docs/tuples.md)
|
|
156
|
-
- [Defaults](docs/defaults.md)
|
|
157
|
-
- [Portable contract](docs/contract.md)
|
|
158
|
-
- [Restrictions](docs/restrictions.md)
|
|
159
|
-
- [Guarantees](docs/guarantees.md)
|
|
160
|
-
- [Design](docs/philosophy.md)
|
|
161
|
-
- [Comparison](docs/comparison.md)
|
|
162
|
-
- [Changelog](CHANGELOG.md)
|
pytypehint-1.2.1/README.md
DELETED
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
# pytypehint
|
|
2
|
-
|
|
3
|
-
[](https://pypi.org/project/pytypehint/)
|
|
4
|
-
|
|
5
|
-
Define data with Python type hints. Validate input, build nested dataclasses,
|
|
6
|
-
inspect their fields or export a portable schema.
|
|
7
|
-
|
|
8
|
-
Use **`@immutable`** for deeply immutable models that validate themselves and
|
|
9
|
-
reuse validated children without checking their contents again.
|
|
10
|
-
|
|
11
|
-
Exact types, no implicit coercion. Python 3.11+, standard library only.
|
|
12
|
-
The schema and validation core of [FuncToWeb](https://github.com/offerrall/FuncToWeb).
|
|
13
|
-
|
|
14
|
-
```bash
|
|
15
|
-
pip install pytypehint
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
## Validate input into ordinary dataclasses
|
|
19
|
-
|
|
20
|
-
```python
|
|
21
|
-
from dataclasses import dataclass
|
|
22
|
-
from typing import Annotated
|
|
23
|
-
from pytypehint import Min, struct_of
|
|
24
|
-
|
|
25
|
-
@dataclass
|
|
26
|
-
class Item:
|
|
27
|
-
name: str
|
|
28
|
-
quantity: Annotated[int, Min(1)] = 1
|
|
29
|
-
|
|
30
|
-
schema = struct_of(Item)
|
|
31
|
-
item = schema.build({"name": "Pen"}) # Item(name='Pen', quantity=1)
|
|
32
|
-
schema.build({"name": "Pen", "quantity": 0}) # SchemaValueError: quantity
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
Compile once and reuse. Errors include the failing field or index in their `path`.
|
|
36
|
-
|
|
37
|
-
## Immutable models
|
|
38
|
-
|
|
39
|
-
Declare constraints once. Normal construction and `dataclasses.replace` validate
|
|
40
|
-
automatically; nested instances keep their identity.
|
|
41
|
-
|
|
42
|
-
```python
|
|
43
|
-
from dataclasses import replace
|
|
44
|
-
from typing import Annotated
|
|
45
|
-
from pytypehint import Max, Min, immutable
|
|
46
|
-
|
|
47
|
-
Positive = Annotated[int, Min(1)]
|
|
48
|
-
Channel = Annotated[float, Min(0.0), Max(1.0)]
|
|
49
|
-
|
|
50
|
-
@immutable
|
|
51
|
-
class Exposure:
|
|
52
|
-
stops: float = 0.0
|
|
53
|
-
|
|
54
|
-
@immutable
|
|
55
|
-
class Grayscale:
|
|
56
|
-
pass
|
|
57
|
-
|
|
58
|
-
@immutable
|
|
59
|
-
class Layer:
|
|
60
|
-
name: Annotated[str, Min(1)]
|
|
61
|
-
effect: Exposure | Grayscale
|
|
62
|
-
color: tuple[Channel, Channel, Channel, Channel] = (0.0, 0.0, 0.0, 1.0)
|
|
63
|
-
|
|
64
|
-
@immutable
|
|
65
|
-
class Document:
|
|
66
|
-
size: tuple[Positive, Positive]
|
|
67
|
-
layers: tuple[Layer, ...] = ()
|
|
68
|
-
|
|
69
|
-
photo = Layer(name="Photo", effect=Exposure(stops=1.5))
|
|
70
|
-
original = Document(size=(1920, 1080), layers=(photo,))
|
|
71
|
-
resized = replace(original, size=(3840, 2160))
|
|
72
|
-
|
|
73
|
-
assert resized.layers[0] is photo
|
|
74
|
-
assert original.size == (1920, 1080)
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
The new document checks its size and layer references, without revisiting the
|
|
78
|
-
existing layer's fields. Tuples are checked; existing immutable children are reused.
|
|
79
|
-
|
|
80
|
-
```python
|
|
81
|
-
replace(photo, color=(1.0, 0.0, 0.0, 2.0)) # SchemaValueError, path: ('color', 3)
|
|
82
|
-
Exposure(stops="1.5") # SchemaTypeError
|
|
83
|
-
original.size = (800, 600) # FrozenInstanceError
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
Allowed fields are immutable scalars, tuples and other `@immutable` models.
|
|
87
|
-
Mutable containers, ordinary dataclasses and enums are rejected. The guarantee
|
|
88
|
-
covers declared fields through normal Python use; deliberate low-level mutation
|
|
89
|
-
can bypass it. [Full contract, inheritance and defaults](docs/immutable.md).
|
|
90
|
-
|
|
91
|
-
## Load portable data and inspect the same definition
|
|
92
|
-
|
|
93
|
-
The models above also work with the existing schema API:
|
|
94
|
-
|
|
95
|
-
```python
|
|
96
|
-
from pytypehint import struct_of
|
|
97
|
-
|
|
98
|
-
schema = struct_of(Document)
|
|
99
|
-
loaded = schema.build(schema.decode({
|
|
100
|
-
"size": [1920, 1080],
|
|
101
|
-
"layers": [{
|
|
102
|
-
"name": "Photo",
|
|
103
|
-
"effect": {"$type": "Exposure", "stops": 1.5},
|
|
104
|
-
}],
|
|
105
|
-
}))
|
|
106
|
-
|
|
107
|
-
assert loaded == original
|
|
108
|
-
contract = schema.to_dict() # Portable description for a UI or another process
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
Type constraints and interface metadata live on the same field:
|
|
112
|
-
|
|
113
|
-
```python
|
|
114
|
-
from pytypehint import Label, Slider, Step
|
|
115
|
-
|
|
116
|
-
@immutable
|
|
117
|
-
class Brush:
|
|
118
|
-
size: Annotated[int, Min(1), Max(256), Label("Brush size"), Step(1), Slider()] = 32
|
|
119
|
-
|
|
120
|
-
size_field, = struct_of(Brush).fields
|
|
121
|
-
size_shape, = size_field.shape
|
|
122
|
-
assert size_field.label.value == "Brush size"
|
|
123
|
-
assert size_shape.max.value == 256
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
| Method | Result |
|
|
127
|
-
|---|---|
|
|
128
|
-
| `build(data)` | Dataclass instance; `signature_of(fn)` returns kwargs for `fn(**kwargs)` |
|
|
129
|
-
| `resolve(data)` | Validated dictionary with missing defaults filled at this level |
|
|
130
|
-
| `decode(data)` | Portable values restored to Python types |
|
|
131
|
-
| `to_dict()` | Portable schema description |
|
|
132
|
-
|
|
133
|
-
## Documentation
|
|
134
|
-
|
|
135
|
-
- [Immutable models](docs/immutable.md)
|
|
136
|
-
- [Types and API](docs/vocabulary.md)
|
|
137
|
-
- [Atoms](docs/atoms.md)
|
|
138
|
-
- [Build](docs/build.md)
|
|
139
|
-
- [Resolve](docs/resolve.md)
|
|
140
|
-
- [Decode](docs/decode.md)
|
|
141
|
-
- [Tuples](docs/tuples.md)
|
|
142
|
-
- [Defaults](docs/defaults.md)
|
|
143
|
-
- [Portable contract](docs/contract.md)
|
|
144
|
-
- [Restrictions](docs/restrictions.md)
|
|
145
|
-
- [Guarantees](docs/guarantees.md)
|
|
146
|
-
- [Design](docs/philosophy.md)
|
|
147
|
-
- [Comparison](docs/comparison.md)
|
|
148
|
-
- [Changelog](CHANGELOG.md)
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
# Scope and ecosystem
|
|
2
|
-
|
|
3
|
-
| Task | Responsible layer |
|
|
4
|
-
|---|---|
|
|
5
|
-
| Define ordinary models | Python dataclasses and type hints |
|
|
6
|
-
| Inspect/export schemas, validate values, restore portable types, construct models | pytypehint |
|
|
7
|
-
| Parse JSON, coerce form/CLI input, render controls, collect multiple errors | Consumer |
|
|
8
|
-
| Call functions, check files, manage persistence/authentication | Application |
|
|
9
|
-
|
|
10
|
-
Direct dataclass construction is simpler for trusted internal objects. Use
|
|
11
|
-
pytypehint when you need strict input validation or a shared inspectable contract.
|
|
12
|
-
Compile once and reuse schemas.
|
|
13
|
-
|
|
14
|
-
Optional consumers, maintained separately:
|
|
15
|
-
|
|
16
|
-
- [pytypehintweb](https://github.com/offerrall/pytypehintweb): browser forms.
|
|
17
|
-
- [FuncToWeb](https://github.com/offerrall/FuncToWeb): web interfaces for functions.
|
|
18
|
-
- [pytypehintstore](https://github.com/offerrall/pytypehintstore): validated storage.
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
# Guarantees
|
|
2
|
-
|
|
3
|
-
- Exact validation, with explicit selection for ambiguous unions; no guessing
|
|
4
|
-
from contents or implicit coercion. See [build](build.md).
|
|
5
|
-
- The first schema error includes its full field/index path in both its message
|
|
6
|
-
and `path`, with the reason in `leaf`.
|
|
7
|
-
- Defaults are certified at compilation and validated at every missing-key
|
|
8
|
-
serving. Reconstructed contents are fresh; factories must honor the author's
|
|
9
|
-
[purity requirements](defaults.md).
|
|
10
|
-
- `resolve` validates recursively, preserves supplied values by reference and
|
|
11
|
-
fills defaults only at the current level. `build` constructs nested instances.
|
|
12
|
-
- `decode` does not mutate input, fill defaults or discard unknown keys. It
|
|
13
|
-
rebuilds traversed plain containers; actual tuples and subclasses pass through.
|
|
14
|
-
- `to_dict()` returns a fresh tree with deterministic ordering. Deterministic
|
|
15
|
-
defaults and matching interpreter configuration are required for equal output.
|
|
16
|
-
- The core's checks depend on the schema and value. File access and other external
|
|
17
|
-
checks belong to the consumer; user factories and constructors remain user code.
|
|
18
|
-
- `Signature.build` prepares kwargs and never calls the function.
|
|
19
|
-
|
|
20
|
-
`@immutable` additionally validates construction and restricts fields to deeply
|
|
21
|
-
immutable values, allowing already validated children to be reused without
|
|
22
|
-
traversing them again. See [its contract and limits](immutable.md).
|
|
23
|
-
|
|
24
|
-
For accepted definitions and runtime limits, see [restrictions](restrictions.md).
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|