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