bytespec 0.1.0__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.
bytespec/missing.py ADDED
@@ -0,0 +1,17 @@
1
+ # Copyright (c) 2026 ink-developer
2
+
3
+ from typing import Final, TypeAlias
4
+
5
+ from typing_extensions import override
6
+
7
+
8
+ class _MissingType:
9
+ __slots__ = ()
10
+
11
+ @override
12
+ def __repr__(self) -> str:
13
+ return "MISSING"
14
+
15
+
16
+ MISSING: Final = _MissingType()
17
+ MissingType: TypeAlias = _MissingType
bytespec/models.py ADDED
@@ -0,0 +1,47 @@
1
+ # Copyright (c) 2026 ink-developer
2
+
3
+ from collections.abc import Callable
4
+ from dataclasses import dataclass
5
+ from typing import Any, Literal, TypeAlias
6
+
7
+ from bytespec.codecs import ICodec
8
+ from bytespec.missing import MissingType
9
+ from bytespec.types import VarUInt
10
+
11
+ DefaultFactory: TypeAlias = Callable[[], Any] | MissingType
12
+
13
+ UIntEncoding = Literal[1, 2, 4, 8] | VarUInt
14
+ PrefixLength = UIntEncoding
15
+
16
+
17
+ @dataclass
18
+ class FieldInfo:
19
+ """Field settings passed to a user-defined codec factory.
20
+
21
+ In models, create them via `field()`. The factory may read
22
+ `prefix_length`, `encoding`, and other settings to construct a codec.
23
+
24
+ Args:
25
+ index: Explicit field position, or None for automatic selection.
26
+ flag: Presence bit index for an optional field, or None.
27
+ default: Default value or the MISSING sentinel.
28
+ default_factory: Zero-argument factory or MISSING.
29
+ prefix_length: Length prefix size, or None to use the default.
30
+ encoding: Text encoding, UTF-8 by default.
31
+ codec: Explicitly assigned codec instance, or None.
32
+ """
33
+
34
+ index: int | None
35
+ flag: int | None
36
+ default: Any
37
+ default_factory: DefaultFactory
38
+ prefix_length: PrefixLength | None
39
+ encoding: str = "utf-8"
40
+ codec: ICodec[Any] | None = None
41
+
42
+
43
+ @dataclass
44
+ class ResolvedType:
45
+ codec: ICodec[Any]
46
+ optional: bool
47
+ annotation: Any
bytespec/py.typed ADDED
File without changes
bytespec/resolver.py ADDED
@@ -0,0 +1,196 @@
1
+ # Copyright (c) 2026 ink-developer
2
+
3
+ import codecs
4
+ from enum import Enum
5
+ from types import NoneType, UnionType
6
+ from typing import Annotated, Any, Union, get_args, get_origin
7
+
8
+ from bytespec.base import ProtoModelBase
9
+ from bytespec.codecs import (
10
+ Float32Codec,
11
+ Float64Codec,
12
+ ICodec,
13
+ UInt8Codec,
14
+ VarIntCodec,
15
+ VarUIntCodec,
16
+ )
17
+ from bytespec.codecs.int import (
18
+ Int8Codec,
19
+ Int16Codec,
20
+ Int32Codec,
21
+ Int64Codec,
22
+ UInt16Codec,
23
+ UInt32Codec,
24
+ UInt64Codec,
25
+ )
26
+ from bytespec.errors import SchemaError
27
+ from bytespec.models import FieldInfo, ResolvedType
28
+ from bytespec.resolvers import (
29
+ CodecFactory,
30
+ ResolveCallback,
31
+ enum_codec_factory,
32
+ list_codec_factory,
33
+ model_codec_factory,
34
+ )
35
+ from bytespec.types import CodecSpec, FloatSpec, IntegerSpec, Spec
36
+ from bytespec.types.spec import VarIntSpec
37
+
38
+ INTEGER_CODECS: dict[tuple[int, bool], type[ICodec[Any]]] = {
39
+ (8, False): UInt8Codec,
40
+ (16, False): UInt16Codec,
41
+ (32, False): UInt32Codec,
42
+ (64, False): UInt64Codec,
43
+ (8, True): Int8Codec,
44
+ (16, True): Int16Codec,
45
+ (32, True): Int32Codec,
46
+ (64, True): Int64Codec,
47
+ }
48
+ FLOAT_CODECS: dict[int, type[ICodec[Any]]] = {
49
+ 32: Float32Codec,
50
+ 64: Float64Codec,
51
+ }
52
+ VARINT_CODECS: dict[bool, type[ICodec[Any]]] = {
53
+ True: VarIntCodec,
54
+ False: VarUIntCodec,
55
+ }
56
+
57
+
58
+ def resolve_integer(_: Any, spec: IntegerSpec, __: FieldInfo, ___: ResolveCallback) -> ICodec[Any]:
59
+ codec = INTEGER_CODECS.get((spec.bits, spec.signed))
60
+
61
+ if not codec:
62
+ raise SchemaError(f"Unsupported numeric spec: {spec!r}")
63
+
64
+ return codec()
65
+
66
+
67
+ def resolve_float(_: Any, spec: FloatSpec, __: FieldInfo, ___: ResolveCallback) -> ICodec[Any]:
68
+ codec = FLOAT_CODECS.get(spec.bits)
69
+
70
+ if not codec:
71
+ raise SchemaError(f"Unsupported numeric spec: {spec!r}")
72
+
73
+ return codec()
74
+
75
+
76
+ def resolve_varint(_: Any, spec: VarIntSpec, __: FieldInfo, ___: ResolveCallback) -> ICodec[Any]:
77
+ codec = VARINT_CODECS.get(spec.signed)
78
+
79
+ if not codec:
80
+ raise SchemaError(f"Unsupported numeric spec: {spec!r}")
81
+
82
+ return codec()
83
+
84
+
85
+ class CodecResolver:
86
+ def __init__(self, scalar_codecs: dict[type, CodecFactory]) -> None:
87
+ self.scalar_codecs = scalar_codecs
88
+
89
+ def _is_type_of(self, annotation: Any, subclass: type) -> bool:
90
+ return isinstance(annotation, type) and issubclass(annotation, subclass)
91
+
92
+ def _normalize_union(self, annotation: Any | UnionType) -> tuple[Any, bool]:
93
+ is_optional = False
94
+ metadata = list(get_args(annotation))
95
+
96
+ if NoneType in metadata:
97
+ is_optional = True
98
+ metadata.pop(metadata.index(NoneType))
99
+
100
+ if len(metadata) > 1:
101
+ raise SchemaError(f"Unsupported union type: {annotation}")
102
+
103
+ return metadata[0], is_optional
104
+
105
+ def _check_encoding(self, encoding: str) -> None:
106
+ try:
107
+ codecs.lookup(encoding)
108
+ except LookupError as exc:
109
+ raise SchemaError(f"Invalid encoding: {encoding!r}") from exc
110
+
111
+ def _resolve_spec(
112
+ self,
113
+ base_type: Any,
114
+ spec: Spec,
115
+ field_info: FieldInfo,
116
+ resolve: ResolveCallback,
117
+ ) -> ICodec[Any]:
118
+ if isinstance(spec, IntegerSpec):
119
+ return resolve_integer(base_type, spec, field_info, resolve)
120
+
121
+ if isinstance(spec, FloatSpec):
122
+ return resolve_float(base_type, spec, field_info, resolve)
123
+
124
+ if isinstance(spec, VarIntSpec):
125
+ return resolve_varint(base_type, spec, field_info, resolve)
126
+
127
+ raise SchemaError(f"Unsupported spec: {type(spec).__name__}")
128
+
129
+ def resolve(
130
+ self, annotation: Any, field_info: FieldInfo, is_optional: bool = False
131
+ ) -> ResolvedType:
132
+ self._check_encoding(field_info.encoding)
133
+
134
+ origin = get_origin(annotation)
135
+
136
+ if origin in (UnionType, Union):
137
+ annotation, is_optional = self._normalize_union(annotation)
138
+
139
+ if field_info.codec is not None:
140
+ return ResolvedType(
141
+ field_info.codec,
142
+ is_optional,
143
+ annotation,
144
+ )
145
+
146
+ scalar_codec = self.scalar_codecs.get(annotation)
147
+
148
+ if scalar_codec:
149
+ return ResolvedType(scalar_codec(annotation, field_info), is_optional, annotation)
150
+
151
+ if get_origin(annotation) is Annotated:
152
+ base_type, *metadata = get_args(annotation)
153
+
154
+ specs = [meta for meta in metadata if isinstance(meta, Spec)]
155
+
156
+ if len(specs) > 1:
157
+ raise SchemaError("Only one Spec is allowed")
158
+
159
+ if not specs:
160
+ return self.resolve(base_type, field_info, is_optional)
161
+
162
+ spec = specs[0]
163
+
164
+ if isinstance(spec, CodecSpec):
165
+ if spec.prefix_length is not None:
166
+ field_info.prefix_length = spec.prefix_length
167
+
168
+ if spec.encoding:
169
+ self._check_encoding(spec.encoding)
170
+ field_info.encoding = spec.encoding
171
+
172
+ if spec.codec is not None:
173
+ field_info.codec = spec.codec
174
+
175
+ return self.resolve(base_type, field_info, is_optional)
176
+
177
+ return ResolvedType(
178
+ self._resolve_spec(base_type, spec, field_info, self.resolve),
179
+ is_optional,
180
+ annotation,
181
+ )
182
+
183
+ if self._is_type_of(annotation, ProtoModelBase):
184
+ return ResolvedType(
185
+ model_codec_factory(annotation, field_info), is_optional, annotation
186
+ )
187
+
188
+ if self._is_type_of(annotation, Enum):
189
+ return ResolvedType(enum_codec_factory(annotation, field_info), is_optional, annotation)
190
+
191
+ if get_origin(annotation) is list:
192
+ return ResolvedType(
193
+ list_codec_factory(annotation, field_info, self.resolve), is_optional, annotation
194
+ )
195
+
196
+ raise SchemaError(f"Unknown type: {annotation}")
bytespec/resolvers.py ADDED
@@ -0,0 +1,114 @@
1
+ # Copyright (c) 2026 ink-developer
2
+
3
+ from collections.abc import Callable
4
+ from enum import Enum
5
+ from typing import Any, Protocol, TypeAlias, get_args
6
+
7
+ from bytespec.codecs import ICodec
8
+ from bytespec.errors import SchemaError
9
+
10
+ from .codecs import (
11
+ BoolCodec,
12
+ BytesCodec,
13
+ DatetimeCodec,
14
+ EnumCodec,
15
+ ListCodec,
16
+ ModelCodec,
17
+ StrCodec,
18
+ UUIDCodec,
19
+ VarIntCodec,
20
+ )
21
+ from .missing import MISSING
22
+ from .models import FieldInfo, ResolvedType
23
+
24
+ ResolveCallback: TypeAlias = Callable[[Any, FieldInfo], ResolvedType]
25
+
26
+
27
+ class CodecFactory(Protocol):
28
+ """Codec factory returned by rules configured via `configure_codecs()`.
29
+
30
+ When selecting a codec for a scalar field, it is called with two arguments:
31
+ the annotation and `FieldInfo`. It returns a ready-to-use codec instance.
32
+ The third protocol argument is optional and is not passed in this path.
33
+ """
34
+
35
+ def __call__(
36
+ self, annotation: Any, field_info: FieldInfo, resolve: ResolveCallback | None = None, /
37
+ ) -> ICodec[Any]: ...
38
+
39
+
40
+ def str_codec_factory(_: Any, field_info: FieldInfo, __: ResolveCallback | None = None) -> StrCodec:
41
+
42
+ if field_info.prefix_length is not None:
43
+ return StrCodec(prefix_length=field_info.prefix_length, encoding=field_info.encoding)
44
+ else:
45
+ return StrCodec(encoding=field_info.encoding)
46
+
47
+
48
+ def bytes_codec_factory(
49
+ _: Any, field_info: FieldInfo, __: ResolveCallback | None = None
50
+ ) -> BytesCodec:
51
+ if field_info.prefix_length is not None:
52
+ return BytesCodec(prefix_length=field_info.prefix_length)
53
+ else:
54
+ return BytesCodec()
55
+
56
+
57
+ def datetime_codec_factory(
58
+ _: Any, field_info: FieldInfo, __: ResolveCallback | None = None
59
+ ) -> DatetimeCodec:
60
+ if field_info.prefix_length is not None:
61
+ return DatetimeCodec(prefix_length=field_info.prefix_length, encoding=field_info.encoding)
62
+ else:
63
+ return DatetimeCodec(encoding=field_info.encoding)
64
+
65
+
66
+ def model_codec_factory(
67
+ annotation: Any, _: FieldInfo, __: ResolveCallback | None = None
68
+ ) -> ModelCodec:
69
+ return ModelCodec(annotation)
70
+
71
+
72
+ def int_codec_factory(_: Any, __: FieldInfo, ___: ResolveCallback | None = None) -> VarIntCodec:
73
+ return VarIntCodec()
74
+
75
+
76
+ def enum_codec_factory(
77
+ annotation: Any, field_info: FieldInfo, __: ResolveCallback | None = None
78
+ ) -> EnumCodec:
79
+ if not issubclass(annotation, Enum):
80
+ raise SchemaError(f"Unknown enum type: {annotation}")
81
+
82
+ if issubclass(annotation, str):
83
+ return EnumCodec(annotation, str_codec_factory(annotation, field_info, __))
84
+ elif issubclass(annotation, int):
85
+ return EnumCodec(annotation, int_codec_factory(annotation, field_info, __))
86
+ else:
87
+ raise SchemaError(f"Unknown enum type: {annotation}")
88
+
89
+
90
+ def bool_codec_factory(_: Any, __: FieldInfo, ___: ResolveCallback | None = None) -> BoolCodec:
91
+ return BoolCodec()
92
+
93
+
94
+ def uuid_codec_factory(_: Any, __: FieldInfo, ___: ResolveCallback | None = None) -> UUIDCodec:
95
+ return UUIDCodec()
96
+
97
+
98
+ def list_codec_factory(
99
+ annotation: Any, field_info: FieldInfo, callback: ResolveCallback
100
+ ) -> ListCodec:
101
+ metadata = list(get_args(annotation))
102
+
103
+ if len(metadata) != 1:
104
+ raise SchemaError(f"Unsupported list type: {annotation}")
105
+
106
+ resolved_type = callback(metadata[0], FieldInfo(None, None, MISSING, MISSING, None))
107
+
108
+ if resolved_type.optional:
109
+ raise SchemaError(f"Unsupported list type: {annotation}")
110
+
111
+ if field_info.prefix_length is not None:
112
+ return ListCodec(resolved_type.codec, field_info.prefix_length)
113
+ else:
114
+ return ListCodec(resolved_type.codec)
bytespec/schema.py ADDED
@@ -0,0 +1,35 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import TYPE_CHECKING
5
+
6
+ from .enums import ByteOrder
7
+
8
+ if TYPE_CHECKING:
9
+ from .core import FieldMetadata, ProtoModel
10
+ from .headers import HeaderElement
11
+
12
+
13
+ @dataclass(frozen=True, slots=True)
14
+ class ModelSchema:
15
+ model: type[ProtoModel]
16
+ fields: tuple[FieldMetadata, ...]
17
+ header: tuple[HeaderElement, ...]
18
+ byte_order: ByteOrder
19
+ constructor: int | None
20
+
21
+
22
+ @dataclass(slots=True)
23
+ class HeaderEncodeContext:
24
+ schema: ModelSchema
25
+ include_constructor: bool
26
+ payload_length: int
27
+ flags: int
28
+
29
+
30
+ @dataclass(slots=True)
31
+ class HeaderDecodeContext:
32
+ schema: ModelSchema
33
+ expect_constructor: bool
34
+ payload_length: int | None = None
35
+ flags: int = 0
@@ -0,0 +1,25 @@
1
+ # Copyright (c) 2026 ink-developer
2
+
3
+ from typing import Annotated, TypeAlias
4
+
5
+ from .spec import CodecSpec as CodecSpec
6
+ from .spec import FloatSpec, IntegerSpec, VarIntSpec
7
+ from .spec import Spec as Spec
8
+
9
+ UInt8 = Annotated[int, IntegerSpec(8, signed=False)]
10
+ UInt16 = Annotated[int, IntegerSpec(16, signed=False)]
11
+ UInt32 = Annotated[int, IntegerSpec(32, signed=False)]
12
+ UInt64 = Annotated[int, IntegerSpec(64, signed=False)]
13
+
14
+ Int8 = Annotated[int, IntegerSpec(8, signed=True)]
15
+ Int16 = Annotated[int, IntegerSpec(16, signed=True)]
16
+ Int32 = Annotated[int, IntegerSpec(32, signed=True)]
17
+ Int64 = Annotated[int, IntegerSpec(64, signed=True)]
18
+
19
+ Float32 = Annotated[float, FloatSpec(32)]
20
+ Float64 = Annotated[float, FloatSpec(64)]
21
+
22
+ VarUInt = Annotated[int, VarIntSpec(signed=False)]
23
+ VarInt = Annotated[int, VarIntSpec(signed=True)]
24
+
25
+ UIntUnion: TypeAlias = UInt16 | UInt32 | UInt64 | UInt8
bytespec/types/spec.py ADDED
@@ -0,0 +1,75 @@
1
+ # Copyright (c) 2026 ink-developer
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from typing import TYPE_CHECKING, Any, Literal, TypeAlias
7
+
8
+ from bytespec.codecs import ICodec
9
+
10
+ if TYPE_CHECKING:
11
+ from bytespec.models import PrefixLength
12
+
13
+
14
+ @dataclass(frozen=True)
15
+ class IntegerSpec:
16
+ """Select a fixed-width integer in ``Annotated[int, ...]``.
17
+
18
+ Args:
19
+ bits: The number of bits: 8, 16, 32, or 64.
20
+ signed: Allow negative values (signed representation).
21
+ """
22
+
23
+ bits: Literal[8, 16, 32, 64]
24
+ signed: bool
25
+
26
+
27
+ @dataclass(frozen=True)
28
+ class FloatSpec:
29
+ """Select an IEEE 754 number in ``Annotated[float, ...]``.
30
+
31
+ Args:
32
+ bits: The number of bits: 32 or 64. Float32 rounds a Python float to its representation.
33
+ """
34
+
35
+ bits: Literal[32, 64]
36
+
37
+
38
+ @dataclass(frozen=True)
39
+ class VarIntSpec:
40
+ """Select a varint in ``Annotated[int, ...]``.
41
+
42
+ Args:
43
+ signed: If True, use ZigZag and the Int64 range; if False, use an unsigned varint and the
44
+ UInt64 range.
45
+ """
46
+
47
+ signed: bool
48
+
49
+
50
+ @dataclass(frozen=True)
51
+ class StructSpec:
52
+ struct_format: str
53
+
54
+
55
+ @dataclass(frozen=True)
56
+ class CodecSpec:
57
+ """Define reusable field settings using ``Annotated``.
58
+
59
+ Args:
60
+ prefix_length: Prefix size: 1, 2, 4, or 8 bytes, or ``VarUInt``. ``None`` leaves the field
61
+ setting unchanged; ``VarInt`` is not supported.
62
+ encoding: The text encoding. A nonempty value replaces the field encoding.
63
+ codec: A ready-to-use codec instance instead of automatic selection.
64
+
65
+ Note:
66
+ One supported specification is allowed per Annotated. An explicit ``field(codec=...)`` takes
67
+ precedence over CodecSpec.
68
+ """
69
+
70
+ prefix_length: PrefixLength | None = None
71
+ encoding: str | None = None
72
+ codec: ICodec[Any] | None = None
73
+
74
+
75
+ Spec: TypeAlias = IntegerSpec | FloatSpec | VarIntSpec | CodecSpec
@@ -0,0 +1,103 @@
1
+ Metadata-Version: 2.4
2
+ Name: bytespec
3
+ Version: 0.1.0
4
+ Summary: Declarative binary serialization and deserialization for Python
5
+ Keywords: binary,serialization,deserialization,codec,protocol
6
+ Author: ink-developer
7
+ Author-email: ink-developer <142109011+ink-developer@users.noreply.github.com>
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Programming Language :: Python
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Programming Language :: Python :: 3.14
18
+ Classifier: Typing :: Typed
19
+ Requires-Dist: typing-extensions>=4.16.0
20
+ Requires-Python: >=3.10
21
+ Project-URL: Homepage, https://github.com/ink-developer/bytespec
22
+ Project-URL: Repository, https://github.com/ink-developer/bytespec
23
+ Project-URL: Issues, https://github.com/ink-developer/bytespec/issues
24
+ Project-URL: Documentation, https://bytespec.pymax.org
25
+ Description-Content-Type: text/markdown
26
+
27
+ # bytespec
28
+
29
+ English · [Русский](README.md)
30
+
31
+ Typed Python models over an explicit sequential binary representation.
32
+ One model reads and writes numbers, strings, lists, and nested objects;
33
+ you choose their representation, and the library tracks lengths and offsets.
34
+
35
+ ```python
36
+ from bytespec import Constructor, PayloadLength, ProtoModel, field
37
+ from bytespec.types import UInt32
38
+
39
+
40
+ class User(ProtoModel):
41
+ __constructor__ = 0x12
42
+ __header__ = (Constructor(1), PayloadLength(1))
43
+
44
+ id: UInt32
45
+ name: str = field(prefix_length=1)
46
+
47
+
48
+ user = User(id=42, name="Anna")
49
+ data = user.encode()
50
+ restored = User.decode(data)
51
+
52
+ print(data.hex(" ")) # 12 09 00 00 00 2a 04 41 6e 6e 61
53
+ print(restored.name) # Anna
54
+ assert restored == user
55
+ ```
56
+
57
+ `12` identifies the message; `09` is the computed length of its fields.
58
+ Next come the four-byte `id` and a UTF-8 name with a one-byte length prefix.
59
+ In your application, these are ordinary `int` and `str` values. Your first
60
+ models can use the default framing without setting `__header__`; for an
61
+ existing format, you can customize or remove it.
62
+
63
+ bytespec fits custom or existing sequential binary protocols when you want
64
+ to work with classes without a separate schema language or code generation.
65
+ It supports optional fields through flags, `IntEnum`, UUID, datetime, numeric
66
+ encodings, and custom codecs. This is a small 0.1.0 library: for a flexible
67
+ binary parser DSL, consider Construct; for standard JSON/MessagePack, msgspec;
68
+ for a schema ecosystem with its own wire format, protobuf.
69
+
70
+ ## Installation
71
+
72
+ Python 3.10+:
73
+
74
+ ```bash
75
+ pip install bytespec
76
+ ```
77
+
78
+ Or `uv add bytespec`.
79
+
80
+ ## Documentation
81
+
82
+ The full guide and API reference are available in Russian and English.
83
+ To build both versions from a checkout:
84
+
85
+ ```bash
86
+ uv sync --locked --group docs
87
+ uv run --no-sync python -m sphinx -E -a -n -W --keep-going -b html -D language=ru docs docs/_build/html/ru
88
+ uv run --no-sync python -m sphinx -E -a -n -W --keep-going -b html -D language=en docs docs/_build/html/en
89
+ ```
90
+
91
+ Open `docs/_build/html/en/index.html`. The language switcher keeps you on
92
+ the same page. Start with **Getting started**, **Why bytespec?**, or
93
+ **Headers and framing**, then consult the **API reference**.
94
+ The [documentation sources](docs/index.rst) and
95
+ [build instructions](docs/building.rst) are in Russian; Sphinx applies
96
+ the [English translation catalogs](docs/locale/en/LC_MESSAGES/index.po)
97
+ when building the English version.
98
+
99
+ Take a known packet from your protocol, describe a few fields, and compare
100
+ the re-encoded result with the original bytes. That is a useful first check
101
+ of whether the library fits your format.
102
+
103
+ [MIT license](LICENSE).
@@ -0,0 +1,33 @@
1
+ bytespec/__init__.py,sha256=L2s5JSDjoixP9aSOftmNLSEKfiZZ70TkLpX7EURARw0,446
2
+ bytespec/base.py,sha256=OSx_NxVK0SaayWBv37RMYvreqQzPMfVxh1xUBNYtMBU,1847
3
+ bytespec/codecs/__init__.py,sha256=3vuT_fvaGSsu6mHagqAXqo1ldCK7MHJ1980XYh5Z1Lk,973
4
+ bytespec/codecs/_utils.py,sha256=LGgt7damTPbyJ9B96jzov-P9C4HfPSZqvsWLBX19wDc,531
5
+ bytespec/codecs/base.py,sha256=gjmKYwJjq3r8cKDQESd0ETRQZFBVnqibbaQjoZVDBc4,2026
6
+ bytespec/codecs/bool.py,sha256=zBiL7sXKKURuMh_lWWkPxk6TES6Zcv1-MhYCG_RFf34,1249
7
+ bytespec/codecs/bytes.py,sha256=epICoyDjSd8iYGE9NApye8D5qVTB0L2Wy9W7VeqeBhM,3178
8
+ bytespec/codecs/datetime.py,sha256=Xeyrh-TH4dsqdlqTo4XGNpXhUpgd-yEov9kmzMcv9Nw,2091
9
+ bytespec/codecs/enum.py,sha256=JW_txevzslLhycgeCALoJ7lcHQNt96ONpnXhf3BaN9c,1573
10
+ bytespec/codecs/float.py,sha256=sFnGwnMEU4tBe2DHm2wapFUBSCIIswudbUWt3BZQDeI,529
11
+ bytespec/codecs/int.py,sha256=C-ktOUwLMA12jH-sv-HvmZETW6T7viJYHoY4vvyTNCI,3754
12
+ bytespec/codecs/list.py,sha256=TIAzORqZaD2q5EBSeKX8KmhXdr0gMeBrJwTUFa92NIM,2620
13
+ bytespec/codecs/map.py,sha256=XdnJDrIqN6JQO5XqLCiNB5eMxNljF1IFOuWp4BDohSo,450
14
+ bytespec/codecs/model.py,sha256=MUfTi3VOYtZcdTmNuSp2SzlW3909jfobPRe-mmveJEU,1342
15
+ bytespec/codecs/str.py,sha256=cCEz9BmTgHE3pQXSit01Byz2R5mpwkK5_-E_NPizwTU,3032
16
+ bytespec/codecs/uuid.py,sha256=75waS0z5A4RUcAv96O_uQobI_CCkmCiG1w4H6r_Jnv4,1118
17
+ bytespec/codecs/varint.py,sha256=3ejY0ncFA2zLoA04wLtDUYsiSp2h6yyFN94-DGNCqVE,3471
18
+ bytespec/core.py,sha256=AxsNQ7oq0CwudXhgAHFlkfYieOHrdnSFDNV8CiKaekI,18921
19
+ bytespec/enums.py,sha256=Fo6FjGsNgA6t93eI4C543RS7qb3FU80STup42uMIF6k,368
20
+ bytespec/errors.py,sha256=nlOgCkYVK7roR4ZlLRlAopWAu4XP5BgQA8Yy7ElYH-o,954
21
+ bytespec/headers.py,sha256=mZHrE5-Z5wXzwSUVQ2bhQV__zr8aK2vTf6sSveSI2go,4657
22
+ bytespec/missing.py,sha256=ABFcpZl_gJMU6wO-MfLluUOV4Bxb3n5wi2hqQFyOOcc,296
23
+ bytespec/models.py,sha256=ZZOxKWHj0hrZe23UC9UqYjh6kAoSO8WJQGBGXyOTaaU,1377
24
+ bytespec/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
25
+ bytespec/resolver.py,sha256=iOB63wFCjb95pMBb49xSRRwWSHZMeQiGUnp01t04Pto,5914
26
+ bytespec/resolvers.py,sha256=tDNin1KH1J-kXp_6ZS4DBM0BAosCdKI_IWQaS5A_JM0,3590
27
+ bytespec/schema.py,sha256=i5hqVeUC52tyUmOvRt7spsA2bT9vTmXL4yu54ttLQxE,761
28
+ bytespec/types/__init__.py,sha256=gNi2fkiwx16T8Ssv3tI9xPb0bThtgDnLvDeTxvU-viY,875
29
+ bytespec/types/spec.py,sha256=ntTnvhwwXk6qXAU9pNil9fkh4TxaSsAvKlvyqULkod4,1902
30
+ bytespec-0.1.0.dist-info/licenses/LICENSE,sha256=b4HOpR9v54uGV7-BR4xWN5pu3pjhJWWRQRdWutlWPwA,1070
31
+ bytespec-0.1.0.dist-info/WHEEL,sha256=Qb5DWjqM6GZuPp3VmTlAFSGqNRK8vceyVqRFxrfa8YA,80
32
+ bytespec-0.1.0.dist-info/METADATA,sha256=9NM3l750FAl-tTCcAsHlYXmm1d_mKBrSMZbxaH7-Hog,3773
33
+ bytespec-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: uv 0.11.4
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ink-developer
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.