structtype 0.3.0__tar.gz → 0.5.0__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.
- structtype-0.5.0/.opencode/opencode.json +6 -0
- structtype-0.5.0/.opencode/plugins/graphify.js +30 -0
- {structtype-0.3.0 → structtype-0.5.0}/AGENTS.md +15 -2
- {structtype-0.3.0 → structtype-0.5.0}/Makefile +27 -3
- {structtype-0.3.0 → structtype-0.5.0}/PKG-INFO +8 -2
- {structtype-0.3.0 → structtype-0.5.0}/README.md +7 -1
- {structtype-0.3.0 → structtype-0.5.0}/pyproject.toml +1 -2
- {structtype-0.3.0 → structtype-0.5.0}/setup.py +9 -2
- {structtype-0.3.0 → structtype-0.5.0}/src/structtype/__init__.py +2 -0
- {structtype-0.3.0 → structtype-0.5.0}/src/structtype/__init__.pyi +30 -26
- {structtype-0.3.0 → structtype-0.5.0}/src/structtype/_adapter.py +59 -23
- {structtype-0.3.0 → structtype-0.5.0}/src/structtype/_core.c +1134 -830
- {structtype-0.3.0 → structtype-0.5.0}/src/structtype/_inspect.py +21 -8
- {structtype-0.3.0 → structtype-0.5.0}/src/structtype/_version.py +2 -2
- {structtype-0.3.0 → structtype-0.5.0}/src/structtype.egg-info/PKG-INFO +8 -2
- {structtype-0.3.0 → structtype-0.5.0}/src/structtype.egg-info/SOURCES.txt +20 -18
- {structtype-0.3.0/tests/unit → structtype-0.5.0/tests}/conftest.py +10 -0
- structtype-0.5.0/tests/test_adapter.py +346 -0
- {structtype-0.3.0/tests/unit → structtype-0.5.0/tests}/test_check.py +6 -7
- {structtype-0.3.0/tests/unit → structtype-0.5.0/tests}/test_constraints.py +7 -1
- structtype-0.5.0/tests/test_free_threading.py +167 -0
- {structtype-0.3.0/tests/unit → structtype-0.5.0/tests}/test_inspect.py +9 -1
- {structtype-0.3.0/tests/unit → structtype-0.5.0/tests}/test_json.py +347 -44
- structtype-0.5.0/tests/test_memory.py +81 -0
- {structtype-0.3.0/tests/unit → structtype-0.5.0/tests}/test_schema.py +11 -0
- {structtype-0.3.0/tests/unit → structtype-0.5.0/tests}/test_struct.py +103 -40
- {structtype-0.3.0 → structtype-0.5.0}/uv.lock +299 -193
- structtype-0.3.0/tests/unit/__init__.py +0 -0
- structtype-0.3.0/tests/unit/test_adapter.py +0 -122
- structtype-0.3.0/tests/unit/test_free_threading.py +0 -59
- {structtype-0.3.0 → structtype-0.5.0}/LICENSE +0 -0
- {structtype-0.3.0 → structtype-0.5.0}/MANIFEST.in +0 -0
- {structtype-0.3.0 → structtype-0.5.0}/setup.cfg +0 -0
- {structtype-0.3.0 → structtype-0.5.0}/src/structtype/_json_schema.py +0 -0
- {structtype-0.3.0 → structtype-0.5.0}/src/structtype/_utils.py +0 -0
- {structtype-0.3.0 → structtype-0.5.0}/src/structtype/atof.h +0 -0
- {structtype-0.3.0 → structtype-0.5.0}/src/structtype/atof_consts.h +0 -0
- {structtype-0.3.0 → structtype-0.5.0}/src/structtype/common.h +0 -0
- {structtype-0.3.0 → structtype-0.5.0}/src/structtype/itoa.h +0 -0
- {structtype-0.3.0 → structtype-0.5.0}/src/structtype/py.typed +0 -0
- {structtype-0.3.0 → structtype-0.5.0}/src/structtype/ryu.h +0 -0
- {structtype-0.3.0 → structtype-0.5.0}/src/structtype.egg-info/dependency_links.txt +0 -0
- {structtype-0.3.0 → structtype-0.5.0}/src/structtype.egg-info/top_level.txt +0 -0
- {structtype-0.3.0 → structtype-0.5.0}/tests/__init__.py +0 -0
- {structtype-0.3.0/tests/unit → structtype-0.5.0/tests}/test_JSONTestSuite.py +0 -0
- {structtype-0.3.0/tests/unit → structtype-0.5.0/tests}/test_cpylint.py +0 -0
- {structtype-0.3.0/tests/unit → structtype-0.5.0/tests}/test_msgspec.py +0 -0
- {structtype-0.3.0/tests/unit → structtype-0.5.0/tests}/test_pydantic.py +0 -0
- {structtype-0.3.0/tests/unit → structtype-0.5.0/tests}/test_raw.py +0 -0
- {structtype-0.3.0/tests/unit → structtype-0.5.0/tests}/test_struct_meta.py +0 -0
- {structtype-0.3.0/tests/unit → structtype-0.5.0/tests}/test_utils.py +0 -0
- {structtype-0.3.0/tests/unit → structtype-0.5.0/tests}/utils.py +0 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// graphify OpenCode plugin
|
|
2
|
+
// Injects a knowledge graph reminder before bash tool calls when the graph exists.
|
|
3
|
+
//
|
|
4
|
+
// IMPORTANT: keep the reminder string free of backticks and $(...) constructs.
|
|
5
|
+
// The hook prepends `echo "<reminder>" && <cmd>` to the user's bash command;
|
|
6
|
+
// backticks inside the double-quoted echo trigger bash command substitution,
|
|
7
|
+
// which both corrupts tool output and silently executes the very graphify
|
|
8
|
+
// command we are only suggesting. Plain words render fine in opencode's TUI.
|
|
9
|
+
import { existsSync } from "fs";
|
|
10
|
+
import { join } from "path";
|
|
11
|
+
|
|
12
|
+
export const GraphifyPlugin = async ({ directory }) => {
|
|
13
|
+
let reminded = false;
|
|
14
|
+
|
|
15
|
+
return {
|
|
16
|
+
"tool.execute.before": async (input, output) => {
|
|
17
|
+
if (reminded) return;
|
|
18
|
+
if (!existsSync(join(directory, "graphify-out", "graph.json"))) return;
|
|
19
|
+
|
|
20
|
+
if (input.tool === "bash") {
|
|
21
|
+
// ';' not '&&' — Windows PowerShell 5.1 rejects '&&' as a statement
|
|
22
|
+
// separator, breaking the first bash command of the session (#1646).
|
|
23
|
+
output.args.command =
|
|
24
|
+
'echo "[graphify] knowledge graph at graphify-out/. For focused questions, run graphify query with your question (scoped subgraph, usually much smaller than GRAPH_REPORT.md) instead of grepping raw files. Read GRAPH_REPORT.md only for broad architecture context." ; ' +
|
|
25
|
+
output.args.command;
|
|
26
|
+
reminded = true;
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
};
|
|
@@ -19,7 +19,7 @@ All commands go through `make`.
|
|
|
19
19
|
| Task | Command |
|
|
20
20
|
|---|---|
|
|
21
21
|
| Unit tests (reinstall + last-failed) | `make test-lf` |
|
|
22
|
-
| Targeted tests | `uv run --reinstall pytest tests/
|
|
22
|
+
| Targeted tests | `uv run --reinstall pytest tests/test_json.py -k test_something` |
|
|
23
23
|
| Coverage | `make test-cov` |
|
|
24
24
|
| Build docs | `make docs` |
|
|
25
25
|
| Format | `make format` |
|
|
@@ -35,6 +35,7 @@ All commands go through `make`.
|
|
|
35
35
|
- C code uses `ms_`/`MS_` prefix
|
|
36
36
|
- Type stubs (`.pyi`) alongside public modules
|
|
37
37
|
- Sentinel values: `NODEFAULT`, `UNSET`, `_NoDefault`, `UnsetType`
|
|
38
|
+
- never do git commit
|
|
38
39
|
|
|
39
40
|
## Key API
|
|
40
41
|
|
|
@@ -48,7 +49,6 @@ All commands go through `make`.
|
|
|
48
49
|
|
|
49
50
|
- `obj.struct_dump_json(*, enc_hook=None, decimal_format=None, uuid_format=None, order=None)` — serialize to JSON bytes
|
|
50
51
|
- `obj.struct_dump()` — convert to built-in Python types (uses `encode_name` for keys)
|
|
51
|
-
- `obj.struct_force_setattr(name, value)` — set attr on frozen struct
|
|
52
52
|
- `obj.struct_validate_self()` — validate field values against types + constraints
|
|
53
53
|
- `cls.struct_validate_json(buf, *, strict=True, dec_hook=None)` — deserialize from JSON
|
|
54
54
|
- `cls.struct_validate(obj, *, strict=True, from_attributes=False, dec_hook=None)` — convert built-in types to struct
|
|
@@ -62,3 +62,16 @@ Struct instances support the mapping protocol:
|
|
|
62
62
|
## Gotchas
|
|
63
63
|
|
|
64
64
|
- `make test-lf` reinstalls the C extension before running (last-failed first)
|
|
65
|
+
|
|
66
|
+
## graphify
|
|
67
|
+
|
|
68
|
+
This project has a knowledge graph at graphify-out/ with god nodes, community structure, and cross-file relationships.
|
|
69
|
+
|
|
70
|
+
When the user types `/graphify`, use the installed graphify skill or instructions before doing anything else.
|
|
71
|
+
|
|
72
|
+
Rules:
|
|
73
|
+
- For codebase questions, first run `graphify query "<question>"` when graphify-out/graph.json exists. Use `graphify path "<A>" "<B>"` for relationships and `graphify explain "<concept>"` for focused concepts. These return a scoped subgraph, usually much smaller than GRAPH_REPORT.md or raw grep output.
|
|
74
|
+
- Dirty graphify-out/ files are expected after hooks or incremental updates; dirty graph files are not a reason to skip graphify. Only skip graphify if the task is about stale or incorrect graph output, or the user explicitly says not to use it.
|
|
75
|
+
- If graphify-out/wiki/index.md exists, use it for broad navigation instead of raw source browsing.
|
|
76
|
+
- Read graphify-out/GRAPH_REPORT.md only for broad architecture review or when query/path/explain do not surface enough context.
|
|
77
|
+
- After modifying code, run `graphify update .` to keep the graph current (AST-only, no API cost).
|
|
@@ -13,13 +13,17 @@ docs: ## build docs
|
|
|
13
13
|
rm -rf .doctrees site
|
|
14
14
|
uv run --group docs sphinx-build -d .doctrees -b html docs site --fail-on-warning
|
|
15
15
|
|
|
16
|
+
.PHONY: docs-serve
|
|
17
|
+
docs-serve: ## Open built docs in browser
|
|
18
|
+
uv run python -m webbrowser site/index.html
|
|
19
|
+
|
|
16
20
|
.PHONY: wheels
|
|
17
21
|
wheels: ## build wheels
|
|
18
|
-
uvx --from cibuildwheel==4.
|
|
22
|
+
uvx --from cibuildwheel==4.2.0 cibuildwheel
|
|
19
23
|
|
|
20
24
|
##@ Quality
|
|
21
25
|
.PHONY: test-cov
|
|
22
|
-
test-cov: ## Run tests with coverage
|
|
26
|
+
test-cov: build ## Run tests with coverage
|
|
23
27
|
uv run pytest --cov-report=term-missing --cov-config=pyproject.toml --cov=structtype
|
|
24
28
|
|
|
25
29
|
.PHONY: test-lf
|
|
@@ -33,9 +37,29 @@ test-doc: ## Run doctests
|
|
|
33
37
|
.PHONY: test-all
|
|
34
38
|
test-all: ## Run tests in all supporte Python versions
|
|
35
39
|
for py_v in $(PY_VERSIONS); do \
|
|
36
|
-
uv run --isolated -p $$py_v pytest; \
|
|
40
|
+
uv run --isolated --reinstall-package structtype -p $$py_v pytest; \
|
|
37
41
|
done
|
|
38
42
|
|
|
43
|
+
UNAME_S := $(shell uname -s)
|
|
44
|
+
ifeq ($(UNAME_S),Darwin)
|
|
45
|
+
ASAN_RUNTIME := $(shell clang --print-file-name=libclang_rt.asan_osx_dynamic.dylib)
|
|
46
|
+
SANITIZE_PRELOAD := DYLD_INSERT_LIBRARIES=$(ASAN_RUNTIME)
|
|
47
|
+
else
|
|
48
|
+
ASAN_RUNTIME := $(shell gcc --print-file-name=libasan.so)
|
|
49
|
+
SANITIZE_PRELOAD := LD_PRELOAD=$(ASAN_RUNTIME)
|
|
50
|
+
endif
|
|
51
|
+
|
|
52
|
+
DEBUG_PY ?= 3.14+debug
|
|
53
|
+
DEBUG_VENV = .venv-debug
|
|
54
|
+
|
|
55
|
+
.PHONY: test-debug
|
|
56
|
+
test-debug: ## Build core with Py_DEBUG + ASan/UBSan + debug allocator and run all tests
|
|
57
|
+
uv venv --clear --python $(DEBUG_PY) $(DEBUG_VENV)
|
|
58
|
+
STRUCTTYPE_SANITIZE=1 uv pip install --python $(DEBUG_VENV) --reinstall --no-cache --group dev -e .
|
|
59
|
+
$(SANITIZE_PRELOAD) STRUCTTYPE_ASAN_RUNTIME=$(ASAN_RUNTIME) ASAN_OPTIONS=detect_leaks=0 \
|
|
60
|
+
PYTHONMALLOC=debug PYTHONFAULTHANDLER=1 PYTHONDEVMODE=1 \
|
|
61
|
+
$(DEBUG_VENV)/bin/python -m pytest
|
|
62
|
+
|
|
39
63
|
.PHONY: check
|
|
40
64
|
check: ## Run all checks
|
|
41
65
|
-uvx ty check ${SOURCE_DIR}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: structtype
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: Fast Struct type with validation and JSON serialization for Python.
|
|
5
5
|
Maintainer-email: Wolfgang Langner <tds333@mailbox.org>
|
|
6
6
|
License-Expression: BSD-3-Clause
|
|
@@ -72,7 +72,13 @@ Full documentation is available at **https://tds333.github.io/structtype/**.
|
|
|
72
72
|
|
|
73
73
|
## Benchmarks
|
|
74
74
|
|
|
75
|
-
structtype is as fast as msgspec and about 3-5x faster than pydantic.
|
|
75
|
+
structtype is as fast as msgspec and about 3-5x faster than pydantic. See the
|
|
76
|
+
[benchmarks page](https://tds333.github.io/structtype/benchmarks.html) for the full numbers.
|
|
77
|
+
|
|
78
|
+
## Links
|
|
79
|
+
|
|
80
|
+
- [Changelog](https://tds333.github.io/structtype/changelog.html)
|
|
81
|
+
- [Source code](https://github.com/tds333/structtype)
|
|
76
82
|
|
|
77
83
|
## License
|
|
78
84
|
|
|
@@ -49,7 +49,13 @@ Full documentation is available at **https://tds333.github.io/structtype/**.
|
|
|
49
49
|
|
|
50
50
|
## Benchmarks
|
|
51
51
|
|
|
52
|
-
structtype is as fast as msgspec and about 3-5x faster than pydantic.
|
|
52
|
+
structtype is as fast as msgspec and about 3-5x faster than pydantic. See the
|
|
53
|
+
[benchmarks page](https://tds333.github.io/structtype/benchmarks.html) for the full numbers.
|
|
54
|
+
|
|
55
|
+
## Links
|
|
56
|
+
|
|
57
|
+
- [Changelog](https://tds333.github.io/structtype/changelog.html)
|
|
58
|
+
- [Source code](https://github.com/tds333/structtype)
|
|
53
59
|
|
|
54
60
|
## License
|
|
55
61
|
|
|
@@ -84,8 +84,7 @@ editable_mode = "compat"
|
|
|
84
84
|
[tool.cibuildwheel]
|
|
85
85
|
build = "cp3*"
|
|
86
86
|
skip = ["pp*", "*-manylinux_i686", "*-musllinux*", "*-win32"]
|
|
87
|
-
enable = ["cpython-prerelease"]
|
|
88
87
|
test-command = "pip install pytest>=9 && pytest {project}/tests"
|
|
89
88
|
|
|
90
89
|
[tool.ruff]
|
|
91
|
-
target-version = "py310"
|
|
90
|
+
target-version = "py310"
|
|
@@ -27,8 +27,15 @@ DEBUG = os.environ.get("STRUCTTYPE_DEBUG", SANITIZE or COVERAGE)
|
|
|
27
27
|
extra_compile_args = []
|
|
28
28
|
extra_link_args = []
|
|
29
29
|
if SANITIZE:
|
|
30
|
-
extra_compile_args.extend(
|
|
31
|
-
|
|
30
|
+
extra_compile_args.extend(
|
|
31
|
+
[
|
|
32
|
+
"-fsanitize=address",
|
|
33
|
+
"-fsanitize=undefined",
|
|
34
|
+
"-fsanitize=signed-integer-overflow",
|
|
35
|
+
]
|
|
36
|
+
)
|
|
37
|
+
if sys.platform != "darwin":
|
|
38
|
+
extra_link_args.extend(["-lasan", "-lubsan"])
|
|
32
39
|
if COVERAGE:
|
|
33
40
|
extra_compile_args.append("--coverage")
|
|
34
41
|
extra_link_args.append("-lgcov")
|
|
@@ -68,7 +68,7 @@ class _NoDefault(enum.Enum):
|
|
|
68
68
|
|
|
69
69
|
NODEFAULT: Final = _NoDefault.NODEFAULT
|
|
70
70
|
|
|
71
|
-
@dataclass_transform(field_specifiers=(Field,))
|
|
71
|
+
@dataclass_transform(field_specifiers=("Field",))
|
|
72
72
|
class Struct(metaclass=StructMeta):
|
|
73
73
|
__struct_fields__: ClassVar[tuple[str, ...]]
|
|
74
74
|
__struct_config__: ClassVar[StructConfig]
|
|
@@ -110,22 +110,19 @@ class Struct(metaclass=StructMeta):
|
|
|
110
110
|
def struct_dump_json(
|
|
111
111
|
self,
|
|
112
112
|
*,
|
|
113
|
-
enc_hook: Callable[[Any], Any] | None = None,
|
|
114
113
|
decimal_format: Literal["string", "number"]
|
|
115
114
|
| Callable[[Any], Any]
|
|
116
115
|
| None = None,
|
|
117
116
|
uuid_format: Literal["canonical", "hex"] | None = None,
|
|
118
|
-
|
|
117
|
+
sort_keys: bool = False,
|
|
119
118
|
) -> bytes: ...
|
|
120
119
|
def struct_dump(
|
|
121
120
|
self,
|
|
122
121
|
*,
|
|
123
|
-
|
|
124
|
-
order: Literal["deterministic", "sorted"] | None = None,
|
|
122
|
+
sort_keys: bool = False,
|
|
125
123
|
str_keys: bool = False,
|
|
126
124
|
builtin_types: Iterable[type] | None = None,
|
|
127
125
|
) -> dict[str, Any] | list[Any]: ...
|
|
128
|
-
def struct_force_setattr(self, name: str, value: Any) -> None: ...
|
|
129
126
|
def struct_validate_self(self) -> None: ...
|
|
130
127
|
@classmethod
|
|
131
128
|
def struct_validate_json(
|
|
@@ -133,7 +130,6 @@ class Struct(metaclass=StructMeta):
|
|
|
133
130
|
buf: str | Buffer,
|
|
134
131
|
*,
|
|
135
132
|
strict: bool = True,
|
|
136
|
-
dec_hook: Callable[[type[Any], Any], Any] | None = None,
|
|
137
133
|
) -> _T: ...
|
|
138
134
|
@classmethod
|
|
139
135
|
def struct_validate(
|
|
@@ -142,7 +138,6 @@ class Struct(metaclass=StructMeta):
|
|
|
142
138
|
*,
|
|
143
139
|
strict: bool = True,
|
|
144
140
|
from_attributes: bool = False,
|
|
145
|
-
dec_hook: Callable[[type[Any], Any], Any] | None = None,
|
|
146
141
|
) -> _T: ...
|
|
147
142
|
|
|
148
143
|
# Lie and say `Raw` is a subclass of `bytes`, so mypy will accept it in most
|
|
@@ -158,6 +153,11 @@ class Raw(bytes):
|
|
|
158
153
|
#: We can't represent this in types, only via a name:
|
|
159
154
|
_NonNegativeInt: TypeAlias = int
|
|
160
155
|
|
|
156
|
+
@final
|
|
157
|
+
class Factory:
|
|
158
|
+
def __init__(self, factory: Callable[[], Any]) -> None: ...
|
|
159
|
+
factory: Final[Callable[[], Any]]
|
|
160
|
+
|
|
161
161
|
@final
|
|
162
162
|
class Field:
|
|
163
163
|
# Numeric:
|
|
@@ -168,13 +168,14 @@ class Field:
|
|
|
168
168
|
gt: int | float | None = None,
|
|
169
169
|
lt: int | float | None = None,
|
|
170
170
|
multiple_of: int | float | None = None,
|
|
171
|
-
default: Any = NODEFAULT,
|
|
172
|
-
default_factory: Callable[[], Any] | None = None,
|
|
173
171
|
alias: str | None = None,
|
|
174
172
|
title: str | None = None,
|
|
175
173
|
description: str | None = None,
|
|
176
174
|
json_schema_extra: dict[str, Any] | None = None,
|
|
177
175
|
examples: list[Any] | None = None,
|
|
176
|
+
deprecated: bool | None = None,
|
|
177
|
+
dump: Callable[[Any], Any] | None = None,
|
|
178
|
+
validate: Callable[[Any], Any] | None = None,
|
|
178
179
|
) -> None: ...
|
|
179
180
|
@overload
|
|
180
181
|
def __init__(
|
|
@@ -183,13 +184,14 @@ class Field:
|
|
|
183
184
|
gt: int | float | None = None,
|
|
184
185
|
le: int | float | None = None,
|
|
185
186
|
multiple_of: int | float | None = None,
|
|
186
|
-
default: Any = NODEFAULT,
|
|
187
|
-
default_factory: Callable[[], Any] | None = None,
|
|
188
187
|
alias: str | None = None,
|
|
189
188
|
title: str | None = None,
|
|
190
189
|
description: str | None = None,
|
|
191
190
|
json_schema_extra: dict[str, Any] | None = None,
|
|
192
191
|
examples: list[Any] | None = None,
|
|
192
|
+
deprecated: bool | None = None,
|
|
193
|
+
dump: Callable[[Any], Any] | None = None,
|
|
194
|
+
validate: Callable[[Any], Any] | None = None,
|
|
193
195
|
) -> None: ...
|
|
194
196
|
@overload
|
|
195
197
|
def __init__(
|
|
@@ -198,13 +200,14 @@ class Field:
|
|
|
198
200
|
ge: int | float | None = None,
|
|
199
201
|
lt: int | float | None = None,
|
|
200
202
|
multiple_of: int | float | None = None,
|
|
201
|
-
default: Any = NODEFAULT,
|
|
202
|
-
default_factory: Callable[[], Any] | None = None,
|
|
203
203
|
alias: str | None = None,
|
|
204
204
|
title: str | None = None,
|
|
205
205
|
description: str | None = None,
|
|
206
206
|
json_schema_extra: dict[str, Any] | None = None,
|
|
207
207
|
examples: list[Any] | None = None,
|
|
208
|
+
deprecated: bool | None = None,
|
|
209
|
+
dump: Callable[[Any], Any] | None = None,
|
|
210
|
+
validate: Callable[[Any], Any] | None = None,
|
|
208
211
|
) -> None: ...
|
|
209
212
|
@overload
|
|
210
213
|
def __init__(
|
|
@@ -213,13 +216,14 @@ class Field:
|
|
|
213
216
|
ge: int | float | None = None,
|
|
214
217
|
le: int | float | None = None,
|
|
215
218
|
multiple_of: int | float | None = None,
|
|
216
|
-
default: Any = NODEFAULT,
|
|
217
|
-
default_factory: Callable[[], Any] | None = None,
|
|
218
219
|
alias: str | None = None,
|
|
219
220
|
title: str | None = None,
|
|
220
221
|
description: str | None = None,
|
|
221
222
|
json_schema_extra: dict[str, Any] | None = None,
|
|
222
223
|
examples: list[Any] | None = None,
|
|
224
|
+
deprecated: bool | None = None,
|
|
225
|
+
dump: Callable[[Any], Any] | None = None,
|
|
226
|
+
validate: Callable[[Any], Any] | None = None,
|
|
223
227
|
) -> None: ...
|
|
224
228
|
# Other (string/datetime):
|
|
225
229
|
@overload
|
|
@@ -230,16 +234,15 @@ class Field:
|
|
|
230
234
|
min_length: _NonNegativeInt | None = None,
|
|
231
235
|
max_length: _NonNegativeInt | None = None,
|
|
232
236
|
tz: bool | None = None,
|
|
233
|
-
default: Any = NODEFAULT,
|
|
234
|
-
default_factory: Callable[[], Any] | None = None,
|
|
235
237
|
alias: str | None = None,
|
|
236
238
|
title: str | None = None,
|
|
237
239
|
description: str | None = None,
|
|
238
240
|
json_schema_extra: dict[str, Any] | None = None,
|
|
239
241
|
examples: list[Any] | None = None,
|
|
242
|
+
deprecated: bool | None = None,
|
|
243
|
+
dump: Callable[[Any], Any] | None = None,
|
|
244
|
+
validate: Callable[[Any], Any] | None = None,
|
|
240
245
|
) -> None: ...
|
|
241
|
-
default: Final[Any]
|
|
242
|
-
default_factory: Final[Callable[[], Any] | None]
|
|
243
246
|
alias: Final[str | None]
|
|
244
247
|
gt: Final[int | float | None]
|
|
245
248
|
ge: Final[int | float | None]
|
|
@@ -253,7 +256,10 @@ class Field:
|
|
|
253
256
|
title: Final[str | None]
|
|
254
257
|
description: Final[str | None]
|
|
255
258
|
examples: Final[list[Any] | None]
|
|
259
|
+
deprecated: Final[bool | None]
|
|
256
260
|
json_schema_extra: Final[dict[str, Any] | None]
|
|
261
|
+
dump: Final[Callable[[Any], Any] | None]
|
|
262
|
+
validate: Final[Callable[[Any], Any] | None]
|
|
257
263
|
def __rich_repr__(self) -> list[tuple[str, Any]]: ...
|
|
258
264
|
|
|
259
265
|
class StructConfig:
|
|
@@ -309,33 +315,29 @@ class StructAdapter:
|
|
|
309
315
|
buf: str | Buffer,
|
|
310
316
|
*,
|
|
311
317
|
strict: bool = True,
|
|
312
|
-
dec_hook: Callable[[type[Any], Any], Any] | None = None,
|
|
313
318
|
) -> Any: ...
|
|
314
319
|
def struct_dump_json(
|
|
315
320
|
self,
|
|
316
321
|
obj: Any,
|
|
317
322
|
*,
|
|
318
|
-
enc_hook: Callable[[Any], Any] | None = None,
|
|
319
323
|
decimal_format: Literal["string", "number"]
|
|
320
324
|
| Callable[[Any], Any]
|
|
321
325
|
| None = None,
|
|
322
326
|
uuid_format: Literal["canonical", "hex"] | None = None,
|
|
323
|
-
|
|
327
|
+
sort_keys: bool = False,
|
|
324
328
|
) -> bytes: ...
|
|
325
329
|
def struct_validate(
|
|
326
330
|
self,
|
|
327
331
|
obj: Any,
|
|
328
332
|
*,
|
|
329
333
|
strict: bool = True,
|
|
330
|
-
dec_hook: Callable[[type[Any], Any], Any] | None = None,
|
|
331
334
|
from_attributes: bool = False,
|
|
332
335
|
) -> Any: ...
|
|
333
336
|
def struct_dump(
|
|
334
337
|
self,
|
|
335
338
|
obj: Any,
|
|
336
339
|
*,
|
|
337
|
-
|
|
338
|
-
order: Literal["deterministic", "sorted"] | None = None,
|
|
340
|
+
sort_keys: bool = False,
|
|
339
341
|
str_keys: bool = False,
|
|
340
342
|
builtin_types: Iterable[type] | None = None,
|
|
341
343
|
) -> Any: ...
|
|
@@ -348,3 +350,5 @@ class DecodeError(ValueError): ...
|
|
|
348
350
|
class ValidationError(ValueError): ...
|
|
349
351
|
|
|
350
352
|
__version__: str
|
|
353
|
+
|
|
354
|
+
ALL_BUILTIN_TYPES: tuple[type, ...]
|
|
@@ -1,6 +1,32 @@
|
|
|
1
|
-
from typing import Any
|
|
2
|
-
|
|
3
|
-
from ._core import
|
|
1
|
+
from typing import Any, get_args
|
|
2
|
+
|
|
3
|
+
from ._core import ( # type: ignore
|
|
4
|
+
Field as _Field,
|
|
5
|
+
)
|
|
6
|
+
from ._core import (
|
|
7
|
+
_dump,
|
|
8
|
+
_json_decode,
|
|
9
|
+
_json_encode,
|
|
10
|
+
_validate,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _has_codec(ann):
|
|
15
|
+
"""True if the annotation carries a ``Field`` with ``dump``/``validate``."""
|
|
16
|
+
metadata = getattr(ann, "__metadata__", None)
|
|
17
|
+
if metadata is not None:
|
|
18
|
+
for meta in metadata:
|
|
19
|
+
if isinstance(meta, _Field) and (
|
|
20
|
+
meta.dump is not None or meta.validate is not None
|
|
21
|
+
):
|
|
22
|
+
return True
|
|
23
|
+
supertype = getattr(ann, "__supertype__", None) # NewType
|
|
24
|
+
if supertype is not None and _has_codec(supertype):
|
|
25
|
+
return True
|
|
26
|
+
value = getattr(ann, "__value__", None) # PEP 695 type alias
|
|
27
|
+
if value is not None and _has_codec(value):
|
|
28
|
+
return True
|
|
29
|
+
return any(_has_codec(arg) for arg in get_args(ann))
|
|
4
30
|
|
|
5
31
|
|
|
6
32
|
class StructAdapter:
|
|
@@ -9,6 +35,11 @@ class StructAdapter:
|
|
|
9
35
|
Useful when you want to validate or serialize plain Python types
|
|
10
36
|
(e.g. ``list[int]``) without defining a full ``Struct`` subclass.
|
|
11
37
|
|
|
38
|
+
``Field(dump=...)`` / ``Field(validate=...)`` codecs are not supported on
|
|
39
|
+
``StructAdapter`` — annotations carrying one are rejected. Implement the
|
|
40
|
+
``struct_dump`` / ``struct_validate`` protocol methods on the custom type,
|
|
41
|
+
or use a ``Struct``.
|
|
42
|
+
|
|
12
43
|
>>> from structtype import StructAdapter
|
|
13
44
|
>>> adapter = StructAdapter(list[int])
|
|
14
45
|
>>> adapter.struct_validate_json(b"[1, 2, 3]")
|
|
@@ -18,9 +49,15 @@ class StructAdapter:
|
|
|
18
49
|
__slots__ = ("_type",)
|
|
19
50
|
|
|
20
51
|
def __init__(self, type: Any):
|
|
52
|
+
if _has_codec(type):
|
|
53
|
+
raise TypeError(
|
|
54
|
+
"`Field(dump=...)`/`Field(validate=...)` codecs are not supported "
|
|
55
|
+
"on StructAdapter; define `struct_dump`/`struct_validate` methods "
|
|
56
|
+
"on the custom type, or use a `Struct` instead"
|
|
57
|
+
)
|
|
21
58
|
self._type = type
|
|
22
59
|
|
|
23
|
-
def struct_validate_json(self, buf, *, strict=True
|
|
60
|
+
def struct_validate_json(self, buf, *, strict=True):
|
|
24
61
|
"""Validate JSON bytes and decode into the adapter's type.
|
|
25
62
|
|
|
26
63
|
Parameters
|
|
@@ -29,13 +66,16 @@ class StructAdapter:
|
|
|
29
66
|
The JSON message to decode.
|
|
30
67
|
strict : bool, optional
|
|
31
68
|
If True (default), unmatched fields cause an error.
|
|
32
|
-
dec_hook : callable, optional
|
|
33
|
-
A callback for customizing decoding of specific types.
|
|
34
69
|
"""
|
|
35
|
-
return _json_decode(buf, type=self._type, strict=strict
|
|
70
|
+
return _json_decode(buf, type=self._type, strict=strict)
|
|
36
71
|
|
|
37
72
|
def struct_dump_json(
|
|
38
|
-
self,
|
|
73
|
+
self,
|
|
74
|
+
obj,
|
|
75
|
+
*,
|
|
76
|
+
decimal_format=None,
|
|
77
|
+
uuid_format=None,
|
|
78
|
+
sort_keys=False,
|
|
39
79
|
):
|
|
40
80
|
"""Encode a validated object to JSON bytes.
|
|
41
81
|
|
|
@@ -43,26 +83,21 @@ class StructAdapter:
|
|
|
43
83
|
----------
|
|
44
84
|
obj : Any
|
|
45
85
|
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
86
|
decimal_format : str or callable, optional
|
|
49
87
|
Controls how ``Decimal`` values are encoded.
|
|
50
88
|
uuid_format : str, optional
|
|
51
89
|
Controls how ``UUID`` values are encoded.
|
|
52
|
-
|
|
53
|
-
|
|
90
|
+
sort_keys : bool, optional
|
|
91
|
+
If True, sort dict keys and set elements for deterministic output.
|
|
54
92
|
"""
|
|
55
93
|
return _json_encode(
|
|
56
94
|
obj,
|
|
57
|
-
enc_hook=enc_hook,
|
|
58
95
|
decimal_format=decimal_format,
|
|
59
96
|
uuid_format=uuid_format,
|
|
60
|
-
|
|
97
|
+
sort_keys=sort_keys,
|
|
61
98
|
)
|
|
62
99
|
|
|
63
|
-
def struct_validate(
|
|
64
|
-
self, obj, *, strict=True, dec_hook=None, from_attributes=False
|
|
65
|
-
):
|
|
100
|
+
def struct_validate(self, obj, *, strict=True, from_attributes=False):
|
|
66
101
|
"""Validate a Python object against the adapter's type.
|
|
67
102
|
|
|
68
103
|
Parameters
|
|
@@ -71,8 +106,6 @@ class StructAdapter:
|
|
|
71
106
|
A Python object to validate and convert.
|
|
72
107
|
strict : bool, optional
|
|
73
108
|
If True (default), unmatched fields cause an error.
|
|
74
|
-
dec_hook : callable, optional
|
|
75
|
-
A callback for customizing decoding of specific types.
|
|
76
109
|
from_attributes : bool, optional
|
|
77
110
|
If True, accept objects with attributes instead of dict keys.
|
|
78
111
|
"""
|
|
@@ -80,20 +113,23 @@ class StructAdapter:
|
|
|
80
113
|
obj,
|
|
81
114
|
self._type,
|
|
82
115
|
strict=strict,
|
|
83
|
-
dec_hook=dec_hook,
|
|
84
116
|
from_attributes=from_attributes,
|
|
85
117
|
)
|
|
86
118
|
|
|
87
119
|
def struct_dump(
|
|
88
|
-
self,
|
|
120
|
+
self,
|
|
121
|
+
obj,
|
|
122
|
+
*,
|
|
123
|
+
sort_keys=False,
|
|
124
|
+
str_keys=False,
|
|
125
|
+
builtin_types=None,
|
|
89
126
|
):
|
|
90
127
|
"""Convert a validated object to built-in Python types (``dict``, ``list``, etc.)."""
|
|
91
128
|
return _dump(
|
|
92
129
|
obj,
|
|
93
130
|
builtin_types=builtin_types,
|
|
94
131
|
str_keys=str_keys,
|
|
95
|
-
|
|
96
|
-
order=order,
|
|
132
|
+
sort_keys=sort_keys,
|
|
97
133
|
)
|
|
98
134
|
|
|
99
135
|
|