structtype 0.3.0__cp315-cp315-win_amd64.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.
structtype/__init__.py ADDED
@@ -0,0 +1,17 @@
1
+ from ._adapter import StrAdapter, StructAdapter
2
+ from ._core import (
3
+ NODEFAULT,
4
+ UNSET,
5
+ DecodeError,
6
+ EncodeError,
7
+ Field,
8
+ Raw,
9
+ Struct,
10
+ StructConfig,
11
+ StructMeta,
12
+ UnsetType,
13
+ ValidationError,
14
+ )
15
+ from ._inspect import FieldInfo, fields
16
+ from ._json_schema import json_schema, json_schema_components, json_schema_dump
17
+ from ._version import __version__
@@ -0,0 +1,350 @@
1
+ import enum
2
+ from collections.abc import Callable, Iterable, Iterator, Mapping
3
+ from inspect import Signature
4
+ from typing import Any, ClassVar, Final, Literal, TypeAlias, TypeVar, final, overload
5
+
6
+ from typing_extensions import Buffer, Self, dataclass_transform
7
+
8
+ from . import StructConfig
9
+
10
+ # PEP 673 explicitly rejects using Self in metaclass definitions:
11
+ # https://peps.python.org/pep-0673/#valid-locations-for-self
12
+ #
13
+ # Typeshed works around this by using a type variable as well:
14
+ # https://github.com/python/typeshed/blob/17bde1bd5e556de001adde3c2f340ba1c3581bd2/stdlib/abc.pyi#L14-L19
15
+ _SM = TypeVar("_SM", bound="StructMeta")
16
+
17
+ class StructMeta(type):
18
+ __struct_fields__: ClassVar[tuple[str, ...]]
19
+ __struct_defaults__: ClassVar[tuple[Any, ...]]
20
+ __struct_encode_fields__: ClassVar[tuple[str, ...]]
21
+ __match_args__: ClassVar[tuple[str, ...]]
22
+ @property
23
+ def __signature__(self) -> Signature: ...
24
+ @property
25
+ def __struct_config__(self) -> StructConfig: ...
26
+ def __new__(
27
+ mcls: type[_SM],
28
+ name: str,
29
+ bases: tuple[type, ...],
30
+ namespace: dict[str, Any],
31
+ /,
32
+ *,
33
+ tag: bool | str | int | Callable[[str], str | int] | None = None,
34
+ tag_field: str | None = None,
35
+ rename: (
36
+ None
37
+ | Literal["lower", "upper", "camel", "pascal", "kebab"]
38
+ | Callable[[str], str | None]
39
+ | Mapping[str, str]
40
+ ) = None,
41
+ omit_defaults: bool = False,
42
+ forbid_unknown_fields: bool = False,
43
+ frozen: bool = False,
44
+ eq: bool = True,
45
+ order: bool = False,
46
+ kw_only: bool = False,
47
+ repr_omit_defaults: bool = False,
48
+ array_like: bool = False,
49
+ gc: bool = True,
50
+ weakref: bool = False,
51
+ dict: bool = False,
52
+ cache_hash: bool = False,
53
+ validate_on_init: bool = False,
54
+ ) -> _SM: ...
55
+
56
+ _T = TypeVar("_T")
57
+
58
+ @final
59
+ class UnsetType(enum.Enum):
60
+ UNSET = "UNSET"
61
+ def __bool__(self) -> Literal[False]: ...
62
+
63
+ UNSET: Final = UnsetType.UNSET
64
+
65
+ @final
66
+ class _NoDefault(enum.Enum):
67
+ NODEFAULT = "NODEFAULT"
68
+
69
+ NODEFAULT: Final = _NoDefault.NODEFAULT
70
+
71
+ @dataclass_transform(field_specifiers=(Field,))
72
+ class Struct(metaclass=StructMeta):
73
+ __struct_fields__: ClassVar[tuple[str, ...]]
74
+ __struct_config__: ClassVar[StructConfig]
75
+ __struct_encode_fields__: ClassVar[tuple[str, ...]]
76
+ __struct_defaults__: ClassVar[tuple[Any, ...]]
77
+ __match_args__: ClassVar[tuple[str, ...]]
78
+ # A default __init__ so that Structs with unknown field types
79
+ # won't error on every call to `__init__`
80
+ def __init__(self, *args: Any, **kwargs: Any) -> None: ...
81
+ def __init_subclass__(
82
+ cls,
83
+ tag: bool | str | int | Callable[[str], str | int] | None = None,
84
+ tag_field: str | None = None,
85
+ rename: (
86
+ None
87
+ | Literal["lower", "upper", "camel", "pascal", "kebab"]
88
+ | Callable[[str], str | None]
89
+ | Mapping[str, str]
90
+ ) = None,
91
+ omit_defaults: bool = False,
92
+ forbid_unknown_fields: bool = False,
93
+ frozen: bool = False,
94
+ eq: bool = True,
95
+ order: bool = False,
96
+ kw_only: bool = False,
97
+ repr_omit_defaults: bool = False,
98
+ array_like: bool = False,
99
+ gc: bool = True,
100
+ weakref: bool = False,
101
+ dict: bool = False,
102
+ cache_hash: bool = False,
103
+ validate_on_init: bool = False,
104
+ ) -> None: ...
105
+ def __rich_repr__(self) -> list[tuple[str, Any]]: ...
106
+ def __copy__(self) -> Self: ...
107
+ def __reduce__(self) -> tuple: ...
108
+ def __replace__(self, **changes: Any) -> Self: ...
109
+ def __iter__(self) -> Iterator[tuple[str, Any]]: ...
110
+ def struct_dump_json(
111
+ self,
112
+ *,
113
+ enc_hook: Callable[[Any], Any] | None = None,
114
+ decimal_format: Literal["string", "number"]
115
+ | Callable[[Any], Any]
116
+ | None = None,
117
+ uuid_format: Literal["canonical", "hex"] | None = None,
118
+ order: Literal["deterministic", "sorted"] | None = None,
119
+ ) -> bytes: ...
120
+ def struct_dump(
121
+ self,
122
+ *,
123
+ enc_hook: Callable[[Any], Any] | None = None,
124
+ order: Literal["deterministic", "sorted"] | None = None,
125
+ str_keys: bool = False,
126
+ builtin_types: Iterable[type] | None = None,
127
+ ) -> dict[str, Any] | list[Any]: ...
128
+ def struct_force_setattr(self, name: str, value: Any) -> None: ...
129
+ def struct_validate_self(self) -> None: ...
130
+ @classmethod
131
+ def struct_validate_json(
132
+ cls: type[_T],
133
+ buf: str | Buffer,
134
+ *,
135
+ strict: bool = True,
136
+ dec_hook: Callable[[type[Any], Any], Any] | None = None,
137
+ ) -> _T: ...
138
+ @classmethod
139
+ def struct_validate(
140
+ cls: type[_T],
141
+ obj: Any,
142
+ *,
143
+ strict: bool = True,
144
+ from_attributes: bool = False,
145
+ dec_hook: Callable[[type[Any], Any], Any] | None = None,
146
+ ) -> _T: ...
147
+
148
+ # Lie and say `Raw` is a subclass of `bytes`, so mypy will accept it in most
149
+ # places where an object that implements the buffer protocol is valid
150
+ @final
151
+ class Raw(bytes):
152
+ @overload
153
+ def __new__(cls) -> "Raw": ...
154
+ @overload
155
+ def __new__(cls, msg: Buffer | str) -> "Raw": ...
156
+ def copy(self) -> "Raw": ...
157
+
158
+ #: We can't represent this in types, only via a name:
159
+ _NonNegativeInt: TypeAlias = int
160
+
161
+ @final
162
+ class Field:
163
+ # Numeric:
164
+ @overload
165
+ def __init__(
166
+ self,
167
+ *,
168
+ gt: int | float | None = None,
169
+ lt: int | float | None = None,
170
+ multiple_of: int | float | None = None,
171
+ default: Any = NODEFAULT,
172
+ default_factory: Callable[[], Any] | None = None,
173
+ alias: str | None = None,
174
+ title: str | None = None,
175
+ description: str | None = None,
176
+ json_schema_extra: dict[str, Any] | None = None,
177
+ examples: list[Any] | None = None,
178
+ ) -> None: ...
179
+ @overload
180
+ def __init__(
181
+ self,
182
+ *,
183
+ gt: int | float | None = None,
184
+ le: int | float | None = None,
185
+ multiple_of: int | float | None = None,
186
+ default: Any = NODEFAULT,
187
+ default_factory: Callable[[], Any] | None = None,
188
+ alias: str | None = None,
189
+ title: str | None = None,
190
+ description: str | None = None,
191
+ json_schema_extra: dict[str, Any] | None = None,
192
+ examples: list[Any] | None = None,
193
+ ) -> None: ...
194
+ @overload
195
+ def __init__(
196
+ self,
197
+ *,
198
+ ge: int | float | None = None,
199
+ lt: int | float | None = None,
200
+ multiple_of: int | float | None = None,
201
+ default: Any = NODEFAULT,
202
+ default_factory: Callable[[], Any] | None = None,
203
+ alias: str | None = None,
204
+ title: str | None = None,
205
+ description: str | None = None,
206
+ json_schema_extra: dict[str, Any] | None = None,
207
+ examples: list[Any] | None = None,
208
+ ) -> None: ...
209
+ @overload
210
+ def __init__(
211
+ self,
212
+ *,
213
+ ge: int | float | None = None,
214
+ le: int | float | None = None,
215
+ multiple_of: int | float | None = None,
216
+ default: Any = NODEFAULT,
217
+ default_factory: Callable[[], Any] | None = None,
218
+ alias: str | None = None,
219
+ title: str | None = None,
220
+ description: str | None = None,
221
+ json_schema_extra: dict[str, Any] | None = None,
222
+ examples: list[Any] | None = None,
223
+ ) -> None: ...
224
+ # Other (string/datetime):
225
+ @overload
226
+ def __init__(
227
+ self,
228
+ *,
229
+ pattern: str | None = None,
230
+ min_length: _NonNegativeInt | None = None,
231
+ max_length: _NonNegativeInt | None = None,
232
+ tz: bool | None = None,
233
+ default: Any = NODEFAULT,
234
+ default_factory: Callable[[], Any] | None = None,
235
+ alias: str | None = None,
236
+ title: str | None = None,
237
+ description: str | None = None,
238
+ json_schema_extra: dict[str, Any] | None = None,
239
+ examples: list[Any] | None = None,
240
+ ) -> None: ...
241
+ default: Final[Any]
242
+ default_factory: Final[Callable[[], Any] | None]
243
+ alias: Final[str | None]
244
+ gt: Final[int | float | None]
245
+ ge: Final[int | float | None]
246
+ lt: Final[int | float | None]
247
+ le: Final[int | float | None]
248
+ multiple_of: Final[int | float | None]
249
+ pattern: Final[str | None]
250
+ min_length: Final[int | None]
251
+ max_length: Final[int | None]
252
+ tz: Final[int | None]
253
+ title: Final[str | None]
254
+ description: Final[str | None]
255
+ examples: Final[list[Any] | None]
256
+ json_schema_extra: Final[dict[str, Any] | None]
257
+ def __rich_repr__(self) -> list[tuple[str, Any]]: ...
258
+
259
+ class StructConfig:
260
+ frozen: bool
261
+ eq: bool
262
+ order: bool
263
+ array_like: bool
264
+ gc: bool
265
+ repr_omit_defaults: bool
266
+ omit_defaults: bool
267
+ forbid_unknown_fields: bool
268
+ validate_on_init: bool
269
+ weakref: bool
270
+ dict: bool
271
+ cache_hash: bool
272
+ tag: str | int | None
273
+ tag_field: str | None
274
+
275
+ class FieldInfo(Struct):
276
+ name: str
277
+ encode_name: str
278
+ type: Any
279
+ default: Any = NODEFAULT
280
+ default_factory: Any = NODEFAULT
281
+
282
+ @property
283
+ def required(self) -> bool: ...
284
+
285
+ def fields(type_or_instance: Struct | type[Struct]) -> tuple[FieldInfo, ...]: ...
286
+ def json_schema(
287
+ type: Any,
288
+ *,
289
+ schema_hook: Callable[[type[Any]], dict[str, Any]] | None = None,
290
+ ref_template: str = "#/$defs/{name}",
291
+ ) -> dict[str, Any]: ...
292
+ def json_schema_dump(
293
+ type: Any,
294
+ *,
295
+ schema_hook: Callable[[type[Any]], dict[str, Any]] | None = None,
296
+ ref_template: str = "#/$defs/{name}",
297
+ ) -> bytes: ...
298
+ def json_schema_components(
299
+ types: Iterable[Any],
300
+ *,
301
+ schema_hook: Callable[[type[Any]], dict[str, Any]] | None = None,
302
+ ref_template: str = "#/$defs/{name}",
303
+ ) -> tuple[tuple[dict[str, Any], ...], dict[str, Any]]: ...
304
+
305
+ class StructAdapter:
306
+ def __init__(self, type: Any): ...
307
+ def struct_validate_json(
308
+ self,
309
+ buf: str | Buffer,
310
+ *,
311
+ strict: bool = True,
312
+ dec_hook: Callable[[type[Any], Any], Any] | None = None,
313
+ ) -> Any: ...
314
+ def struct_dump_json(
315
+ self,
316
+ obj: Any,
317
+ *,
318
+ enc_hook: Callable[[Any], Any] | None = None,
319
+ decimal_format: Literal["string", "number"]
320
+ | Callable[[Any], Any]
321
+ | None = None,
322
+ uuid_format: Literal["canonical", "hex"] | None = None,
323
+ order: Literal["deterministic", "sorted"] | None = None,
324
+ ) -> bytes: ...
325
+ def struct_validate(
326
+ self,
327
+ obj: Any,
328
+ *,
329
+ strict: bool = True,
330
+ dec_hook: Callable[[type[Any], Any], Any] | None = None,
331
+ from_attributes: bool = False,
332
+ ) -> Any: ...
333
+ def struct_dump(
334
+ self,
335
+ obj: Any,
336
+ *,
337
+ enc_hook: Callable[[Any], Any] | None = None,
338
+ order: Literal["deterministic", "sorted"] | None = None,
339
+ str_keys: bool = False,
340
+ builtin_types: Iterable[type] | None = None,
341
+ ) -> Any: ...
342
+
343
+ class StrAdapter:
344
+ def __new__(cls, type: type[Any]) -> type[str]: ...
345
+
346
+ class EncodeError(ValueError): ...
347
+ class DecodeError(ValueError): ...
348
+ class ValidationError(ValueError): ...
349
+
350
+ __version__: str
structtype/_adapter.py ADDED
@@ -0,0 +1,128 @@
1
+ from typing import Any
2
+
3
+ from ._core import _dump, _json_decode, _json_encode, _validate
4
+
5
+
6
+ class StructAdapter:
7
+ """Adapter for validating and serializing types without subclassing ``Struct``.
8
+
9
+ Useful when you want to validate or serialize plain Python types
10
+ (e.g. ``list[int]``) without defining a full ``Struct`` subclass.
11
+
12
+ >>> from structtype import StructAdapter
13
+ >>> adapter = StructAdapter(list[int])
14
+ >>> adapter.struct_validate_json(b"[1, 2, 3]")
15
+ [1, 2, 3]
16
+ """
17
+
18
+ __slots__ = ("_type",)
19
+
20
+ def __init__(self, type: Any):
21
+ self._type = type
22
+
23
+ def struct_validate_json(self, buf, *, strict=True, dec_hook=None):
24
+ """Validate JSON bytes and decode into the adapter's type.
25
+
26
+ Parameters
27
+ ----------
28
+ buf : str or bytes
29
+ The JSON message to decode.
30
+ strict : bool, optional
31
+ If True (default), unmatched fields cause an error.
32
+ dec_hook : callable, optional
33
+ A callback for customizing decoding of specific types.
34
+ """
35
+ return _json_decode(buf, type=self._type, strict=strict, dec_hook=dec_hook)
36
+
37
+ def struct_dump_json(
38
+ self, obj, *, enc_hook=None, decimal_format=None, uuid_format=None, order=None
39
+ ):
40
+ """Encode a validated object to JSON bytes.
41
+
42
+ Parameters
43
+ ----------
44
+ obj : Any
45
+ A value to encode. Must match the adapter's type.
46
+ enc_hook : callable, optional
47
+ A callback for customizing encoding of specific types.
48
+ decimal_format : str or callable, optional
49
+ Controls how ``Decimal`` values are encoded.
50
+ uuid_format : str, optional
51
+ Controls how ``UUID`` values are encoded.
52
+ order : str, optional
53
+ Determines key ordering in JSON objects.
54
+ """
55
+ return _json_encode(
56
+ obj,
57
+ enc_hook=enc_hook,
58
+ decimal_format=decimal_format,
59
+ uuid_format=uuid_format,
60
+ order=order,
61
+ )
62
+
63
+ def struct_validate(
64
+ self, obj, *, strict=True, dec_hook=None, from_attributes=False
65
+ ):
66
+ """Validate a Python object against the adapter's type.
67
+
68
+ Parameters
69
+ ----------
70
+ obj : Any
71
+ A Python object to validate and convert.
72
+ strict : bool, optional
73
+ If True (default), unmatched fields cause an error.
74
+ dec_hook : callable, optional
75
+ A callback for customizing decoding of specific types.
76
+ from_attributes : bool, optional
77
+ If True, accept objects with attributes instead of dict keys.
78
+ """
79
+ return _validate(
80
+ obj,
81
+ self._type,
82
+ strict=strict,
83
+ dec_hook=dec_hook,
84
+ from_attributes=from_attributes,
85
+ )
86
+
87
+ def struct_dump(
88
+ self, obj, *, enc_hook=None, order=None, str_keys=False, builtin_types=None
89
+ ):
90
+ """Convert a validated object to built-in Python types (``dict``, ``list``, etc.)."""
91
+ return _dump(
92
+ obj,
93
+ builtin_types=builtin_types,
94
+ str_keys=str_keys,
95
+ enc_hook=enc_hook,
96
+ order=order,
97
+ )
98
+
99
+
100
+ class StrAdapter:
101
+ """Create a ``str`` subclass wrapper for validating a type during
102
+ structtype serialization.
103
+
104
+ Wraps a type that has a single-argument string constructor
105
+ (e.g. ``HttpUrl``, ``EmailStr``, ``IPv4Address``) into a ``str``
106
+ subclass. The wrapped value is stored as a string but validated by
107
+ calling ``typ(value)`` on construction. During structtype
108
+ validation and serialization, the wrapper is treated as a native
109
+ ``str``.
110
+
111
+ >>> from structtype import StrAdapter, Struct
112
+ >>> from ipaddress import IPv4Address
113
+ >>>
114
+ >>> class Config(Struct):
115
+ ... ip: StrAdapter(IPv4Address)
116
+ """
117
+
118
+ __slots__ = ()
119
+
120
+ def __new__(cls, typ):
121
+ return type(
122
+ f"_Wrapped_{typ.__name__}",
123
+ (str,),
124
+ {
125
+ "__slots__": (),
126
+ "__new__": lambda self, v: str.__new__(self, str(typ(v))),
127
+ },
128
+ )
Binary file