cmodel 0.0.1__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.
@@ -0,0 +1,20 @@
1
+ MIT License
2
+
3
+ Copyright (c) Ryan Morshead <ryan.morshead@gmail.com>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this
6
+ software and associated documentation files (the "Software"), to deal in the Software
7
+ without restriction, including without limitation the rights to use, copy, modify,
8
+ merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
9
+ permit persons to whom the Software is furnished to do so, subject to the following
10
+ conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all copies or
13
+ substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
16
+ INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
17
+ PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
19
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
20
+ OTHER DEALINGS IN THE SOFTWARE.
cmodel-0.0.1/PKG-INFO ADDED
@@ -0,0 +1,42 @@
1
+ Metadata-Version: 2.3
2
+ Name: cmodel
3
+ Version: 0.0.1
4
+ Summary: Model C structs with Pydantic
5
+ Keywords:
6
+ Author: Ryan Morshead
7
+ Author-email: Ryan Morshead <ryan.morshead@gmail.com>
8
+ License: MIT License
9
+
10
+ Copyright (c) Ryan Morshead <ryan.morshead@gmail.com>
11
+
12
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this
13
+ software and associated documentation files (the "Software"), to deal in the Software
14
+ without restriction, including without limitation the rights to use, copy, modify,
15
+ merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
16
+ permit persons to whom the Software is furnished to do so, subject to the following
17
+ conditions:
18
+
19
+ The above copyright notice and this permission notice shall be included in all copies or
20
+ substantial portions of the Software.
21
+
22
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
23
+ INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
24
+ PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
25
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
26
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
27
+ OTHER DEALINGS IN THE SOFTWARE.
28
+ Classifier: Development Status :: 4 - Beta
29
+ Classifier: Programming Language :: Python
30
+ Requires-Dist: pydantic>=2.12.5
31
+ Requires-Dist: pydantic-walk-core-schema>=1.0.0
32
+ Requires-Python: >=3.12, <4
33
+ Project-URL: Source, https://github.com/rmorshea/cmodel
34
+ Description-Content-Type: text/markdown
35
+
36
+ # CModel
37
+
38
+ [![PyPI - Version](https://img.shields.io/pypi/v/cmodel.svg)](https://pypi.org/project/cmodel)
39
+ [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/cmodel.svg)](https://pypi.org/project/cmodel)
40
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
41
+
42
+ Model C structs with Pydantic
cmodel-0.0.1/README.md ADDED
@@ -0,0 +1,7 @@
1
+ # CModel
2
+
3
+ [![PyPI - Version](https://img.shields.io/pypi/v/cmodel.svg)](https://pypi.org/project/cmodel)
4
+ [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/cmodel.svg)](https://pypi.org/project/cmodel)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
+
7
+ Model C structs with Pydantic
@@ -0,0 +1,188 @@
1
+ [build-system]
2
+ requires = ["uv_build>=0.10.3,<0.11.0"]
3
+ build-backend = "uv_build"
4
+
5
+ [project]
6
+ name = "cmodel"
7
+ version = "0.0.1"
8
+ description = "Model C structs with Pydantic"
9
+ readme = "README.md"
10
+ requires-python = ">=3.12,<4"
11
+ license = { file = "LICENSE.txt" }
12
+ keywords = []
13
+ authors = [{ name = "Ryan Morshead", email = "ryan.morshead@gmail.com" }]
14
+ classifiers = [
15
+ "Development Status :: 4 - Beta",
16
+ "Programming Language :: Python",
17
+ ]
18
+ dependencies = [
19
+ "pydantic>=2.12.5",
20
+ "pydantic-walk-core-schema>=1.0.0",
21
+ ]
22
+ [project.urls]
23
+ Source = "https://github.com/rmorshea/cmodel"
24
+
25
+ [dependency-groups]
26
+ dev = [
27
+ { include-group = "util" },
28
+ { include-group = "docs" },
29
+ { include-group = "lint" },
30
+ { include-group = "test" },
31
+ ]
32
+ util = [
33
+ "click==8.1.7",
34
+ "ipykernel==6.29.5",
35
+ "copier==9.4.1",
36
+ ]
37
+ docs = [
38
+ { include-group = "test" },
39
+ "mkdocs-gen-files==0.5.0",
40
+ "mkdocs-literate-nav==0.6.1",
41
+ "mkdocs-material==9.5.39",
42
+ "mkdocs-open-in-new-tab==1.0.5",
43
+ "mkdocs==1.6.1",
44
+ "mkdocstrings-python==1.16.12",
45
+ ]
46
+ lint = [
47
+ { include-group = "test" },
48
+ "mdformat-admon @ git+https://github.com/rmorshea/mdformat-admon.git@0e513d7a2c265faf74441938ccbd1010660609f4",
49
+ "mdformat-mkdocs==4.1.0",
50
+ "mdformat-pyproject==0.0.1",
51
+ "mdformat-tables==1.0.0",
52
+ "mdformat==0.7.22",
53
+ "pyright==1.1.389",
54
+ "ruff==0.7.3",
55
+ "yamlfix==1.17.0",
56
+ "doccmd==2024.11.14",
57
+ "deptry==0.24.0",
58
+ ]
59
+ test = [
60
+ "coverage[toml]==7.6.1",
61
+ "diff-cover==9.2.0",
62
+ "pycobertura==3.3.2",
63
+ "pytest-asyncio==0.24.0",
64
+ "pytest-examples==0.0.13",
65
+ "pytest==8.3.3",
66
+ ]
67
+
68
+ [tool.pytest.ini_options]
69
+ asyncio_mode = "auto"
70
+ asyncio_default_fixture_loop_scope = "function"
71
+ python_files = ["test_*.py", "*_test.py", "test/*.py", "tests/**/*.py", "test.py"]
72
+ filterwarnings = ["error"]
73
+
74
+ [tool.ruff]
75
+ line-length = 100
76
+
77
+ [tool.ruff.format]
78
+ docstring-code-format = true
79
+ quote-style = "double"
80
+ indent-style = "space"
81
+
82
+ [tool.ruff.lint]
83
+ preview = true
84
+ select = ["ALL"]
85
+ ignore = [
86
+ "A005", # Module shadowing built-in
87
+ "ANN", # Let pyright handle annotations
88
+ "ANN401", # Allow Any type hints
89
+ "ARG005", # Unused lambda argument
90
+ "B027", # Allow non-abstract empty methods in abstract base classes
91
+ "B039", # Mutable default for contextvars
92
+ "C901", # Ignore complexity
93
+ "COM812", # Trailing comma
94
+ "CPY001", # Copyright at top of file
95
+ "D100", # Docstring for module
96
+ "D104", # Ignore missing docstring for __init__.py
97
+ "D105", # Docstring for magic method
98
+ "D107", # Docstring for __init__ method
99
+ "D203", # One blank line before class
100
+ "D213", # Multi-line docstring summary second line
101
+ "D407", # Docstring dashes under section names
102
+ "D413", # Docstring blank line after last section
103
+ "DOC201", # Return type documentation
104
+ "DOC402", # Yield type documentation
105
+ "DOC501", # Ignore raises missing from docstring
106
+ "ERA001", # Commented out code
107
+ "FBT003", # Allow boolean positional values in function calls, like `dict.get(... True)`
108
+ "PL", # PyLint
109
+ "PYI", # Stub files
110
+ "RET503", # Explicit return
111
+ "RET505", # Unnecessary return statement after return
112
+ "S105", # Ignore checks for possible passwords
113
+ "SIM117", # Use a single `with` statement
114
+ "ISC001", # implicitly concatenated strings on a single line
115
+ ]
116
+ unfixable = [
117
+ "COM819", # Trailing comma
118
+ ]
119
+ fixable = ["ALL"]
120
+ extend-safe-fixes = ["TCH"]
121
+ [tool.ruff.lint.isort]
122
+ known-first-party = ["cmodel"]
123
+ force-single-line = true
124
+ [tool.ruff.lint.flake8-tidy-imports]
125
+ ban-relative-imports = "all"
126
+ [tool.ruff.lint.per-file-ignores]
127
+ "{*_test.py,conftest.py,**/test/*.py,test.py}" = [
128
+ "ARG001", # Unused argument (pytest fixtures)
129
+ "PLC2701", # Private imports
130
+ "RUF029", # Async functions without await
131
+ "S101", # Assert statements
132
+ "D", # Docstrings
133
+ "ANN", # Type annotations
134
+ ]
135
+ "**.ipynb" = [
136
+ "T201", # Print statements
137
+ ]
138
+ "docs/**" = [
139
+ "INP001", # Implicit namespace package
140
+ "D", # Docstrings
141
+ ]
142
+ "doccmd_*.py" = [
143
+ "ANN", # Type annotations
144
+ "B018", # Useless expression
145
+ "FA102", # Unsafe __futures__ annotations usage
146
+ "RUF029", # No await in async function
147
+ "S101", # Assert statements
148
+ "S106", # Possible passwords
149
+ "SIM115", # Use context manager for opening files
150
+ "T201", # Print
151
+ "TCH002", # Move third-party import into a type-checking block
152
+ ]
153
+
154
+ [tool.yamlfix]
155
+ line_length = 100
156
+
157
+ [tool.coverage.run]
158
+ source_pkgs = ["cmodel", "tests"]
159
+ branch = true
160
+ omit = []
161
+
162
+ [tool.coverage.paths]
163
+ cmodel = ["python"]
164
+ tests = ["tests"]
165
+
166
+ [tool.coverage.report]
167
+ exclude_lines = [
168
+ "# nocov",
169
+ "@overload",
170
+ "if TYPE_CHECKING:",
171
+ "raise AssertionError",
172
+ "raise NotImplementedError",
173
+ 'if __name__ == .__main__.:',
174
+ '\.\.\.\n($|\s*#.*)',
175
+ ]
176
+ show_missing = true
177
+ skip_covered = true
178
+ sort = "Name"
179
+
180
+ [tool.diff_cover]
181
+ compare_branch = "origin/main"
182
+ fail_under = 100
183
+ include_untracked = true
184
+
185
+ [tool.uv.build-backend]
186
+ module-name = "cmodel"
187
+ module-root = "python"
188
+ source-exclude = ["test", "tests", "conftest.py", "*_test.py", "test_*.py"]
@@ -0,0 +1,11 @@
1
+ from importlib.metadata import PackageNotFoundError
2
+ from importlib.metadata import version
3
+
4
+ from cmodel import types as types
5
+ from cmodel.base import CFmt as CFmt
6
+ from cmodel.base import CModel as CModel
7
+
8
+ try:
9
+ __version__ = version(__name__)
10
+ except PackageNotFoundError: # nocov
11
+ __version__ = "0.0.0"
@@ -0,0 +1,206 @@
1
+ from collections.abc import Callable
2
+ from dataclasses import dataclass
3
+ from io import BytesIO
4
+ from struct import calcsize
5
+ from struct import pack
6
+ from struct import unpack_from
7
+ from typing import Any
8
+ from typing import ClassVar
9
+ from typing import Literal
10
+ from typing import Self
11
+ from typing import TypedDict
12
+ from typing import overload
13
+
14
+ from pydantic import BaseModel
15
+ from pydantic import GetCoreSchemaHandler
16
+ from pydantic import SerializationInfo
17
+ from pydantic import ValidationInfo
18
+ from pydantic import model_validator
19
+ from pydantic_core import core_schema as cs
20
+ from pydantic_walk_core_schema import walk_core_schema
21
+
22
+
23
+ class CModel(BaseModel):
24
+ """Base class for models that can be packed/unpacked to/from C binary data."""
25
+
26
+ c_field_formats: ClassVar[tuple[str, ...]] = ()
27
+ """The struct format strings for each field used to pack/unpack the C binary data."""
28
+
29
+ @classmethod
30
+ def __get_pydantic_core_schema__(
31
+ cls,
32
+ source: Any,
33
+ handler: GetCoreSchemaHandler,
34
+ ) -> cs.CoreSchema:
35
+ try:
36
+ CModel # type:ignore[reportUnusedExpression] # noqa: B018
37
+ except NameError:
38
+ # we're defining the schema for this class - just return it
39
+ return handler(source)
40
+ else:
41
+ # we're defining the schema for a subclass
42
+ adapter = _ModelSchemaAdapter(handler)
43
+ schema = adapter.adapt(handler(source))
44
+ cls.c_format = tuple(adapter.format)
45
+ return schema
46
+
47
+ @classmethod
48
+ def c_unpack(cls, buffer: BytesIO) -> Self:
49
+ """Read a C binary data buffer as a packed struct and return an instance of the model."""
50
+ ctx: _Context = {"io": buffer}
51
+ return cls.model_validate(_USE_BUFFER, context={_CONTEXT_KEY: ctx})
52
+
53
+ def c_pack(self, buffer: BytesIO) -> None:
54
+ """Write the model instance to a C binary data buffer as a packed struct."""
55
+ ctx: _Context = {"io": buffer}
56
+ self.model_dump(context={_CONTEXT_KEY: ctx})
57
+
58
+ @model_validator(mode="before")
59
+ @classmethod
60
+ def _validate_model(cls, value: Any) -> Any:
61
+ if value is _USE_BUFFER:
62
+ # Indicate that we're unpacking by ensuring each field is present and gets "validated".
63
+ # The validator for each field will then read from the buffer.
64
+ return dict.fromkeys(cls.model_fields, _USE_BUFFER)
65
+ else:
66
+ return value
67
+
68
+
69
+ @dataclass
70
+ class CFmt[T]:
71
+ """Metadata for a C field, used in the Annotated type of each field in a CModel."""
72
+
73
+ fmt: str
74
+ validate: Callable[[tuple[Any, ...]], T] = lambda x: x # pyright: ignore[reportAssignmentType]
75
+ dump: Callable[[T], tuple[Any, ...]] = lambda x: x # pyright: ignore[reportAssignmentType]
76
+
77
+ def __get_pydantic_core_schema__(
78
+ self,
79
+ source: Any,
80
+ handler: GetCoreSchemaHandler,
81
+ ) -> cs.CoreSchema:
82
+ fmt = self.fmt
83
+ size = calcsize(fmt)
84
+ validate = self.validate
85
+ dump = self.dump
86
+
87
+ def validator(value: Any, info: ValidationInfo) -> Any:
88
+ if value is _USE_BUFFER:
89
+ ctx = _get_context(info, required=True)
90
+ io = ctx["io"]
91
+ value = unpack_from(fmt, io.getbuffer(), io.tell())
92
+ io.seek(size, 1)
93
+ return validate(value)
94
+ else:
95
+ return value
96
+
97
+ def serializer(value: Any, info: SerializationInfo) -> Any:
98
+ if ctx := _get_context(info):
99
+ ctx["io"].write(pack(fmt, *dump(value)))
100
+ else:
101
+ return value
102
+
103
+ return cs.with_info_before_validator_function(
104
+ validator,
105
+ schema=handler(source),
106
+ metadata={_METADATA_KEY: self},
107
+ serialization=cs.plain_serializer_function_ser_schema(serializer, info_arg=True),
108
+ )
109
+
110
+
111
+ _USE_BUFFER = object()
112
+
113
+
114
+ type _Recurse = Callable[[cs.CoreSchema, _Recurse], cs.CoreSchema]
115
+
116
+
117
+ class _ModelSchemaAdapter:
118
+ _VISIT_TYPES: ClassVar[set[cs.CoreSchemaType]] = {
119
+ "tuple",
120
+ }
121
+ _ALLOWED_TYPES: ClassVar[set[cs.CoreSchemaType]] = {
122
+ "model",
123
+ "model-fields",
124
+ "function-before",
125
+ "default",
126
+ }
127
+
128
+ def __init__(self, handler: GetCoreSchemaHandler) -> None:
129
+ self.handler = handler
130
+ self.format: list[str] = []
131
+ self.visitors: dict[str, _Recurse] = {}
132
+ for schema_type in self._VISIT_TYPES:
133
+ method_name = f"visit_{schema_type.replace('-', '_')}"
134
+ self.visitors[schema_type] = getattr(self, method_name)
135
+
136
+ def adapt(self, schema: cs.CoreSchema) -> cs.CoreSchema:
137
+ return walk_core_schema(schema, self.visit)
138
+
139
+ def visit(self, schema: cs.CoreSchema, recurse: _Recurse) -> cs.CoreSchema:
140
+ if _get_metadata(schema):
141
+ return schema
142
+ schema_type = schema["type"]
143
+ visit_fn = self.visitors.get(schema_type)
144
+ if visit_fn is not None:
145
+ return visit_fn(schema, recurse)
146
+ elif schema_type in self._ALLOWED_TYPES:
147
+ return recurse(schema, self.visit)
148
+ else:
149
+ msg = f"Unsupported schema type {schema['type']!r} in CModel"
150
+ raise TypeError(msg)
151
+
152
+ def visit_tuple(self, schema: cs.TupleSchema, recurse: _Recurse) -> cs.CoreSchema:
153
+ if schema.get("variadic_item_index") is not None:
154
+ msg = "CModel does not support variadic tuples"
155
+ raise ValueError(msg)
156
+
157
+ size = len(schema["items_schema"])
158
+ use_buffer = (_USE_BUFFER,) * size
159
+
160
+ def before_validator(value: Any) -> Any:
161
+ if value is _USE_BUFFER:
162
+ return use_buffer
163
+ else:
164
+ return value
165
+
166
+ return cs.no_info_before_validator_function(
167
+ before_validator,
168
+ schema=recurse(schema, self.visit),
169
+ )
170
+
171
+ def visit_model(self, schema: cs.ModelSchema, recurse: _Recurse) -> cs.CoreSchema:
172
+ return recurse(schema, self.visit)
173
+
174
+
175
+ class _Context(TypedDict):
176
+ io: BytesIO
177
+
178
+
179
+ @overload
180
+ def _get_context(
181
+ info: ValidationInfo | SerializationInfo, *, required: Literal[True]
182
+ ) -> _Context: ...
183
+
184
+
185
+ @overload
186
+ def _get_context(
187
+ info: ValidationInfo | SerializationInfo, *, required: bool = ...
188
+ ) -> _Context | None: ...
189
+
190
+
191
+ def _get_context(
192
+ info: ValidationInfo | SerializationInfo, *, required: bool = False
193
+ ) -> _Context | None:
194
+ ctx = info.context.get(_CONTEXT_KEY) if isinstance(info.context, dict) else None
195
+ if ctx is None and required:
196
+ msg = "Context is required for CModel packing/unpacking"
197
+ raise ValueError(msg)
198
+ return ctx
199
+
200
+
201
+ def _get_metadata(schema: cs.CoreSchema) -> CFmt:
202
+ return schema.get("metadata", {}).get(_METADATA_KEY, {})
203
+
204
+
205
+ _CONTEXT_KEY = "cmodel"
206
+ _METADATA_KEY = "cmodel"
@@ -0,0 +1 @@
1
+ PEP-561
@@ -0,0 +1,94 @@
1
+ import operator
2
+ from collections.abc import Callable
3
+ from typing import Annotated as An
4
+ from uuid import UUID
5
+
6
+ from cmodel.base import CFmt
7
+
8
+
9
+ def _make_one_or_many[T](_: type[T], fmt: str) -> Callable[[int], CFmt[T]]:
10
+ return lambda count: (
11
+ CFmt[T](fmt, operator.itemgetter(0), lambda x: (x,))
12
+ if count == 1
13
+ else CFmt[T](fmt=f"{count}{fmt}") # pyright: ignore[reportArgumentType]
14
+ )
15
+
16
+
17
+ c_signed_char = _make_one_or_many(int, "b")
18
+ """CFormat for one or more signed chars. For count=1 the value is returned as an int."""
19
+ SignedChar = An[int, c_signed_char(1)]
20
+ """C format for a single signed char."""
21
+
22
+ c_unsigned_char = _make_one_or_many(int, "B")
23
+ """CFormat for one or more unsigned chars. For count=1 the value is returned as an int."""
24
+ UnsignedChar = An[int, c_unsigned_char(1)]
25
+ """C format for a single unsigned char."""
26
+
27
+ c_bool = _make_one_or_many(bool, "?")
28
+ """CFormat for one or more bools. For count=1 the value is returned as a bool."""
29
+ Bool = An[bool, c_bool(1)]
30
+ """C format for a single bool."""
31
+
32
+ c_short = _make_one_or_many(int, "h")
33
+ """CFormat for one or more shorts. For count=1 the value is returned as an int."""
34
+ Short = An[int, c_short(1)]
35
+ """C format for a single short."""
36
+
37
+ c_unsigned_short = _make_one_or_many(int, "H")
38
+ """CFormat for one or more unsigned shorts. For count=1 the value is returned as an int."""
39
+ UnsignedShort = An[int, c_unsigned_short(1)]
40
+ """C format for a single unsigned short."""
41
+
42
+ c_int = _make_one_or_many(int, "i")
43
+ """CFormat for one or more ints. For count=1 the value is returned as an int."""
44
+ Int = An[int, c_int(1)]
45
+ """C format for a single int."""
46
+
47
+ c_unsigned_int = _make_one_or_many(int, "I")
48
+ """CFormat for one or more unsigned ints. For count=1 the value is returned as an int."""
49
+ UnsignedInt = An[int, c_unsigned_int(1)]
50
+ """C format for a single unsigned int."""
51
+
52
+ c_long = _make_one_or_many(int, "l")
53
+ """CFormat for one or more longs. For count=1 the value is returned as an int."""
54
+ Long = An[int, c_long(1)]
55
+ """C format for a single long."""
56
+
57
+ c_unsigned_long = _make_one_or_many(int, "L")
58
+ """CFormat for one or more unsigned longs. For count=1 the value is returned as an int."""
59
+ UnsignedLong = An[int, c_unsigned_long(1)]
60
+ """C format for a single unsigned long."""
61
+
62
+ c_float = _make_one_or_many(float, "f")
63
+ """CFormat for one or more floats. For count=1 the value is returned as a float."""
64
+ Float = An[float, c_float(1)]
65
+ """C format for a single float."""
66
+
67
+ c_double = _make_one_or_many(float, "d")
68
+ """CFormat for one or more doubles. For count=1 the value is returned as a float."""
69
+ Double = An[float, c_double(1)]
70
+ """C format for a single double."""
71
+
72
+ c_complex_float = _make_one_or_many(complex, "F")
73
+ """CFormat for one or more complex floats. For count=1 the value is returned as a complex."""
74
+ ComplexFloat = An[complex, c_complex_float(1)]
75
+ """C format for a single complex float."""
76
+
77
+ c_complex_double = _make_one_or_many(complex, "D")
78
+ """CFormat for one or more complex doubles. For count=1 the value is returned as a complex."""
79
+ ComplexDouble = An[complex, c_complex_double(1)]
80
+ """C format for a single complex double."""
81
+
82
+
83
+ def c_uuid() -> CFmt[UUID]:
84
+ """CFormat for a UUID, represented as a 16-byte array."""
85
+ return CFmt(fmt="16s", validate=lambda x: UUID(bytes=x[0]), dump=lambda x: (x.bytes,))
86
+
87
+
88
+ Uuid = An[UUID, c_uuid()]
89
+ """C format for a single UUID."""
90
+
91
+
92
+ def c_char(count: int) -> CFmt:
93
+ """CFormat for a char array of the given count."""
94
+ return CFmt(fmt=f"{count}s", validate=operator.itemgetter(0), dump=lambda x: (x,))