enforced 0.1.1__py3-none-any.whl

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 (52) hide show
  1. enforced/__init__.py +48 -0
  2. enforced/_compat.py +152 -0
  3. enforced/_dataclass/__init__.py +1 -0
  4. enforced/_dataclass/_assignment.py +509 -0
  5. enforced/_dataclass/_field_messages.py +85 -0
  6. enforced/_dataclass/_init.py +337 -0
  7. enforced/_dataclass/api.py +316 -0
  8. enforced/_dispatch/__init__.py +1 -0
  9. enforced/_dispatch/_candidates.py +291 -0
  10. enforced/_dispatch/_errors.py +176 -0
  11. enforced/_dispatch/_match.py +120 -0
  12. enforced/_dispatch/_reach.py +469 -0
  13. enforced/_dispatch/api.py +455 -0
  14. enforced/_environment.py +108 -0
  15. enforced/_formatting_tool.py +205 -0
  16. enforced/_json.py +145 -0
  17. enforced/_validator/__init__.py +9 -0
  18. enforced/_validator/_annotations.py +278 -0
  19. enforced/_validator/_checker.py +904 -0
  20. enforced/_validator/_checks.py +139 -0
  21. enforced/_validator/_class_rules.py +162 -0
  22. enforced/_validator/_codegen.py +424 -0
  23. enforced/_validator/_container.py +602 -0
  24. enforced/_validator/_deep.py +1236 -0
  25. enforced/_validator/_defaults.py +131 -0
  26. enforced/_validator/_errors.py +664 -0
  27. enforced/_validator/_file_types.py +184 -0
  28. enforced/_validator/_general_path.py +68 -0
  29. enforced/_validator/_lifetime.py +156 -0
  30. enforced/_validator/_messages.py +411 -0
  31. enforced/_validator/_problems.py +170 -0
  32. enforced/_validator/_record_strings.py +84 -0
  33. enforced/_validator/_remembered.py +154 -0
  34. enforced/_validator/_scope.py +384 -0
  35. enforced/_validator/_self_routing.py +153 -0
  36. enforced/_validator/_signatures.py +167 -0
  37. enforced/_validator/_slow_path.py +238 -0
  38. enforced/_validator/_specs.py +428 -0
  39. enforced/_validator/_text.py +201 -0
  40. enforced/_validator/_transform.py +268 -0
  41. enforced/_validator/_type_conversion.py +435 -0
  42. enforced/_validator/_type_data_model.py +161 -0
  43. enforced/_validator/_wrapping.py +250 -0
  44. enforced/_validator/api.py +760 -0
  45. enforced/data_types.py +330 -0
  46. enforced/py.typed +0 -0
  47. enforced/utils.py +54 -0
  48. enforced-0.1.1.dist-info/METADATA +907 -0
  49. enforced-0.1.1.dist-info/RECORD +52 -0
  50. enforced-0.1.1.dist-info/WHEEL +5 -0
  51. enforced-0.1.1.dist-info/licenses/LICENSE +21 -0
  52. enforced-0.1.1.dist-info/top_level.txt +1 -0
enforced/__init__.py ADDED
@@ -0,0 +1,48 @@
1
+ from ._validator import *
2
+ from ._validator._type_data_model import TypeModel
3
+ from ._validator._transform import Transform
4
+ from ._environment import set_compile_after, turn_validation_off, turn_validation_on, validation_is_off
5
+ from ._formatting_tool import turn_colours_off, turn_colours_on
6
+ from ._json import to_json
7
+ from ._dispatch.api import dispatch, dispatchmethod
8
+ from ._dataclass.api import (
9
+ dataclass,
10
+ make_dataclass,
11
+ )
12
+ from .utils import protect, Ok, Err
13
+ from dataclasses import field, asdict
14
+
15
+
16
+ __version__ = "0.1.1"
17
+
18
+ #* What the package offers. Type checkers read this list too: in a package that ships ``py.typed``,
19
+ #* a name imported above only counts as part of the package when it is named here.
20
+ __all__ = [
21
+ "Check",
22
+ "Enforced",
23
+ "Err",
24
+ "Ok",
25
+ "InvalidDataError",
26
+ "InvalidDataWarning",
27
+ "Transform",
28
+ "TypeModel",
29
+ "dataclass",
30
+ "dispatch",
31
+ "dispatchmethod",
32
+ "enforce_class",
33
+ "enforce",
34
+ "make_dataclass",
35
+ "field",
36
+ "asdict",
37
+ "protect",
38
+ "to_json",
39
+ "set_compile_after",
40
+ "validate",
41
+ "validate_record",
42
+ "validate_function",
43
+ "turn_colours_off",
44
+ "turn_colours_on",
45
+ "turn_validation_off",
46
+ "turn_validation_on",
47
+ "validation_is_off",
48
+ ]
enforced/_compat.py ADDED
@@ -0,0 +1,152 @@
1
+ """
2
+ What differs between Python 3.13 and the versions after it, in one place: the rest of the package
3
+ never asks ``sys.version_info``. Every branch reads the same way: 3.13 is the bridge, and whatever
4
+ comes after 3.13 follows 3.14's rules (the owner's decision, 2026-11-03).
5
+
6
+ Imports nothing from the package: any module may use it.
7
+ """
8
+ from types import UnionType
9
+ from typing import Annotated, Any, Callable, Optional, Union
10
+ import dataclasses
11
+ import sys
12
+
13
+ __all__ = [
14
+ "Format", "PUBLIC_MODULES", "UNION_TYPES",
15
+ "annotated_of", "annotation_text",
16
+ "class_repr", "get_annotations",
17
+ "standard_make_dataclass", "union_of"
18
+ ]
19
+
20
+ #* ``isinstance(x, UNION_TYPES)``: a union, however it was written. ``Union[int, str]`` is one object with
21
+ #* ``int | str`` from 3.14; on 3.13 another class, equal to it all the same.
22
+ UNION_TYPES: Any = (UnionType,) if sys.version_info >= (3, 14) else (UnionType, type(Optional[int]))
23
+
24
+
25
+ if sys.version_info >= (3, 14):
26
+ from annotationlib import Format, get_annotations
27
+
28
+ standard_make_dataclass = dataclasses.make_dataclass
29
+
30
+ #* A class's module as messages show it.
31
+ PUBLIC_MODULES: dict[str, str] = {}
32
+
33
+ def annotation_text(annotation: Any) -> str:
34
+ """``str(annotation)``, as messages show it."""
35
+ return str(annotation)
36
+
37
+ def class_repr(cls: type) -> str:
38
+ """``repr(cls)``, as messages show it."""
39
+ return repr(cls)
40
+
41
+ else:
42
+ from enum import IntEnum
43
+ from types import new_class, NoneType
44
+ from typing import get_args, get_origin, Literal
45
+ import inspect
46
+ import keyword
47
+
48
+ PUBLIC_MODULES = {"pathlib._local": "pathlib"}
49
+
50
+ class Format(IntEnum):
51
+ """``annotationlib.Format``, for the names the package uses."""
52
+ VALUE = 1
53
+ FORWARDREF = 3
54
+ STRING = 4
55
+
56
+ def get_annotations(obj: Any, *, format: Format = Format.VALUE) -> dict[str, Any]:
57
+ """
58
+ 3.13 evaluated the annotations when it ran their ``def`` or class, so ``format`` changes nothing. Only
59
+ what has an ``__annotate__`` is asked for ``STRING`` (``_annotation_texts``), and nothing has one here.
60
+ """
61
+ return inspect.get_annotations(obj)
62
+
63
+ def standard_make_dataclass(
64
+ cls_name: str,
65
+ fields: Any,
66
+ *,
67
+ bases: tuple[type, ...] = (),
68
+ namespace: dict[str, Any] | None = None,
69
+ module: str | None = None,
70
+ decorator: Callable[..., Any] = dataclasses.dataclass,
71
+ **options: Any,
72
+ ) -> type:
73
+ """``dataclasses.make_dataclass`` of 3.14: 3.13's calls its own ``dataclass``, and has no ``decorator``."""
74
+ seen: set[str] = set()
75
+ annotations: dict[str, Any] = {}
76
+ defaults: dict[str, Any] = {}
77
+ for item in fields:
78
+ if isinstance(item, str):
79
+ name, annotation = item, Any
80
+ elif len(item) == 2:
81
+ name, annotation = item
82
+ elif len(item) == 3:
83
+ name, annotation, defaults[item[0]] = item
84
+ else:
85
+ raise TypeError(f"Invalid field: {item!r}")
86
+ if not isinstance(name, str) or not name.isidentifier():
87
+ raise TypeError(f"Field names must be valid identifiers: {name!r}")
88
+ if keyword.iskeyword(name):
89
+ raise TypeError(f"Field names must not be keywords: {name!r}")
90
+ if name in seen:
91
+ raise TypeError(f"Field name duplicated: {name!r}")
92
+ seen.add(name)
93
+ annotations[name] = annotation
94
+
95
+ def body(class_namespace: dict[str, Any]) -> None:
96
+ class_namespace.update(namespace or {})
97
+ class_namespace.update(defaults)
98
+ class_namespace["__annotations__"] = annotations
99
+
100
+ cls = new_class(cls_name, bases, {}, body)
101
+ if module is not None:
102
+ cls.__module__ = module
103
+ return decorator(cls, **options) # type: ignore[no-any-return]
104
+
105
+ def annotation_text(annotation: Any) -> str:
106
+ """
107
+ ``str(annotation)`` as 3.14 prints it, where 3.13 says ``Optional[X]``, ``Union[X, Y]``, ``NoneType``
108
+ and ``pathlib._local.Path``. Made from the objects, which still know the order of a union (``None | X``).
109
+ """
110
+ return annotation if isinstance(annotation, str) else _written(annotation)
111
+
112
+ def _written(annotation: Any) -> str:
113
+ if isinstance(annotation, UNION_TYPES):
114
+ return " | ".join(_written(member) for member in annotation.__args__)
115
+ if annotation is NoneType:
116
+ return "None"
117
+ if annotation is ...:
118
+ return "..."
119
+ if isinstance(annotation, list): # the parameters of a ``Callable``
120
+ return f"[{', '.join(_written(part) for part in annotation)}]"
121
+ args = get_args(annotation)
122
+ if isinstance(annotation, type) and not args:
123
+ module, name = annotation.__module__, annotation.__qualname__
124
+ return name if module == "builtins" else f"{PUBLIC_MODULES.get(module, module)}.{name}"
125
+ if not args or get_origin(annotation) is Literal:
126
+ return repr(annotation) # inside another annotation a string shows its quotes
127
+ head = repr(annotation).partition("[")[0]
128
+ if get_origin(annotation) is Annotated: # what follows the type is no annotation
129
+ return f"{head}[{_written(args[0])}, {', '.join(repr(part) for part in args[1:])}]"
130
+ return f"{head}[{', '.join(_written(part) for part in args)}]"
131
+
132
+ def class_repr(cls: type) -> str:
133
+ """``repr(cls)``, without the module 3.13 keeps the classes of ``pathlib`` in."""
134
+ return repr(cls).replace(f"{cls.__module__}.", f"{PUBLIC_MODULES[cls.__module__]}.", 1)
135
+
136
+
137
+ #* ``typing`` keeps what ``Union[...]`` (on 3.13) and ``Annotated[...]`` (on every version) made in a cache,
138
+ #* the 128 most recent, and with it the classes they name. An annotation enforced writes for a class made
139
+ #* at run time must not keep that class alive, so these two step past the cache. Private names, read
140
+ #* with ``getattr``: where they are gone the annotation comes from the cache, and is just as good.
141
+ _UNION_UNCACHED: Any = getattr(Union, "_getitem", None) if sys.version_info < (3, 14) else None
142
+ _ANNOTATED_UNCACHED: Any = getattr(getattr(Annotated, "_getitem", None), "__wrapped__", None)
143
+
144
+
145
+ def union_of(members: tuple[Any, ...]) -> Any:
146
+ """``Union[members]``, with nothing kept by ``typing``."""
147
+ return Union[members] if _UNION_UNCACHED is None else _UNION_UNCACHED(Union, members)
148
+
149
+
150
+ def annotated_of(parts: tuple[Any, ...]) -> Any:
151
+ """``Annotated[parts]``, with nothing kept by ``typing``."""
152
+ return Annotated[parts] if _ANNOTATED_UNCACHED is None else _ANNOTATED_UNCACHED(Annotated, *parts)
@@ -0,0 +1 @@
1
+ """enforced.dataclass: a type-checked drop-in for dataclasses.dataclass (the decorator is in ``api``)."""