seampact-codegen 1.0.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.
- seampact/codegen/__init__.py +2 -0
- seampact/codegen/boot_check_hooks.py +38 -0
- seampact/codegen/contract_module.py +146 -0
- seampact/codegen/credentials_classes.py +99 -0
- seampact/codegen/handler_forms.py +132 -0
- seampact/codegen/input_schemas.py +130 -0
- seampact/codegen/kept_forms.py +522 -0
- seampact/codegen/matrix_row.py +23 -0
- seampact/codegen/names.py +146 -0
- seampact/codegen/native_docs_file.py +169 -0
- seampact/codegen/operation_types.py +425 -0
- seampact/codegen/placement_lines.py +79 -0
- seampact/codegen/printer.py +196 -0
- seampact/codegen/py.typed +0 -0
- seampact/codegen/pydantic_forms.py +53 -0
- seampact/codegen/pydantic_layout.py +178 -0
- seampact/codegen/pydantic_models.py +405 -0
- seampact/codegen/redacted_members.py +47 -0
- seampact/codegen/refusals.py +84 -0
- seampact/codegen/request_deadline.py +68 -0
- seampact/codegen/response_header_forms.py +73 -0
- seampact/codegen/restricted_sort_keys.py +18 -0
- seampact/codegen/route_helpers.py +109 -0
- seampact/codegen/routes.py +139 -0
- seampact/codegen/runtime/__init__.py +7 -0
- seampact/codegen/runtime/access_kinds.py +24 -0
- seampact/codegen/runtime/access_steps.py +520 -0
- seampact/codegen/runtime/body_reader.py +64 -0
- seampact/codegen/runtime/cache_headers.py +89 -0
- seampact/codegen/runtime/contract.py +232 -0
- seampact/codegen/runtime/cors_preflight.py +20 -0
- seampact/codegen/runtime/deadline.py +72 -0
- seampact/codegen/runtime/digests.py +98 -0
- seampact/codegen/runtime/failures.py +33 -0
- seampact/codegen/runtime/field_rights.py +193 -0
- seampact/codegen/runtime/framework_routes.py +36 -0
- seampact/codegen/runtime/framework_versions.py +91 -0
- seampact/codegen/runtime/handler_call.py +31 -0
- seampact/codegen/runtime/handler_reply.py +148 -0
- seampact/codegen/runtime/hinted_values.py +48 -0
- seampact/codegen/runtime/hook_check.py +77 -0
- seampact/codegen/runtime/idempotency.py +279 -0
- seampact/codegen/runtime/input_decoding.py +226 -0
- seampact/codegen/runtime/ir_view.py +47 -0
- seampact/codegen/runtime/native_docs.py +130 -0
- seampact/codegen/runtime/page_facts.py +33 -0
- seampact/codegen/runtime/page_reply.py +396 -0
- seampact/codegen/runtime/pattern.py +69 -0
- seampact/codegen/runtime/principal_members.py +41 -0
- seampact/codegen/runtime/public_origin.py +102 -0
- seampact/codegen/runtime/pydantic/__init__.py +165 -0
- seampact/codegen/runtime/pydantic/checked_input.py +52 -0
- seampact/codegen/runtime/pydantic/contract_input.py +183 -0
- seampact/codegen/runtime/pydantic/issues.py +142 -0
- seampact/codegen/runtime/raw_target.py +103 -0
- seampact/codegen/runtime/replies.py +144 -0
- seampact/codegen/runtime/reply_verdict.py +215 -0
- seampact/codegen/runtime/requirement_params.py +170 -0
- seampact/codegen/runtime/response_header_entries.py +226 -0
- seampact/codegen/runtime/response_headers.py +114 -0
- seampact/codegen/runtime/route_match.py +88 -0
- seampact/codegen/runtime/run_settings.py +35 -0
- seampact/codegen/runtime/signature_header.py +92 -0
- seampact/codegen/runtime/sync_driver.py +35 -0
- seampact/codegen/runtime/templates.py +132 -0
- seampact/codegen/runtime/uuid_text.py +7 -0
- seampact/codegen/runtime/value_rules.py +218 -0
- seampact/codegen/runtime/version.py +59 -0
- seampact/codegen/runtime/version_token.py +108 -0
- seampact/codegen/runtime/wire_codec.py +229 -0
- seampact/codegen/runtime/wire_values.py +260 -0
- seampact/codegen/source_lines.py +35 -0
- seampact_codegen-1.0.0.dist-info/METADATA +18 -0
- seampact_codegen-1.0.0.dist-info/RECORD +76 -0
- seampact_codegen-1.0.0.dist-info/WHEEL +4 -0
- seampact_codegen-1.0.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
"""Turns a decoded wire body into the tree a generated model validates, and a generated value back into the wire
|
|
2
|
+
tree: object members renamed between their wire names and their Python attributes by the api's member table, unknown
|
|
3
|
+
keys taken out and reported by pointer (the input use's `unknownKeys` policy decides on them), and on the way out
|
|
4
|
+
`date-time` with its offset and the fraction it holds, `date`, `uuid`, decimals and enum members as wire text. Values
|
|
5
|
+
are read by attribute, so a Pydantic model and a frozen dataclass both encode."""
|
|
6
|
+
|
|
7
|
+
from collections.abc import Mapping, Sequence
|
|
8
|
+
from dataclasses import dataclass, field
|
|
9
|
+
from datetime import UTC, date, datetime, timedelta
|
|
10
|
+
from decimal import Decimal
|
|
11
|
+
from enum import Enum
|
|
12
|
+
from typing import Final
|
|
13
|
+
from uuid import UUID
|
|
14
|
+
|
|
15
|
+
from seampact.codegen.runtime.contract import ApiContract
|
|
16
|
+
from seampact.codegen.runtime.failures import BoundaryFailureError
|
|
17
|
+
from seampact.codegen.runtime.ir_view import IrMap, ir_map, ir_maps, ir_string, ir_true
|
|
18
|
+
from seampact.hooks import Absent
|
|
19
|
+
|
|
20
|
+
_LIST_SHAPES: Final = frozenset({"list", "page"})
|
|
21
|
+
_TABLE_SHAPE: Final = "table"
|
|
22
|
+
_OBJECT_SCHEMA: Final = "object"
|
|
23
|
+
_WHOLE_MINUTE: Final = timedelta(minutes=1)
|
|
24
|
+
_FRACTION_DIGITS: Final = 6
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass(frozen=True, slots=True)
|
|
28
|
+
class PythonTree:
|
|
29
|
+
"""The tree a model validates; `unknown_keys` holds the wire pointer of each unknown member taken out."""
|
|
30
|
+
|
|
31
|
+
value: object
|
|
32
|
+
unknown_keys: tuple[str, ...] = field(default=())
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class WireCodec:
|
|
36
|
+
def __init__(self, contract: ApiContract) -> None:
|
|
37
|
+
self._contract = contract
|
|
38
|
+
|
|
39
|
+
def to_python(self, type_use: IrMap, tree: object) -> PythonTree:
|
|
40
|
+
"""Raises `LookupError` for a schema the api has no member table or object declaration for."""
|
|
41
|
+
unknown: list[str] = []
|
|
42
|
+
value = self._to_python(type_use, tree, "", unknown)
|
|
43
|
+
|
|
44
|
+
return PythonTree(value, tuple(unknown))
|
|
45
|
+
|
|
46
|
+
def to_wire(self, type_use: IrMap, value: object) -> object:
|
|
47
|
+
"""Raises `BoundaryFailureError` for a value the contract cannot write: null for a non-nullable opaque
|
|
48
|
+
value, a date-time without an offset, or an object without a declared attribute."""
|
|
49
|
+
if value is None or value is Absent.ABSENT:
|
|
50
|
+
if ir_string(type_use, "kind") == "opaque" and not ir_true(type_use, "nullable"):
|
|
51
|
+
raise BoundaryFailureError(
|
|
52
|
+
"seampact: the handler wrote null for an opaque value the contract does not make nullable; "
|
|
53
|
+
"return a JSON value, or declare it 'nullable: true'."
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
return None
|
|
57
|
+
|
|
58
|
+
kind = ir_string(type_use, "kind")
|
|
59
|
+
|
|
60
|
+
if kind == "ref":
|
|
61
|
+
return self._object_to_wire(ir_string(type_use, "schema") or "", value)
|
|
62
|
+
|
|
63
|
+
if kind == "array":
|
|
64
|
+
return [self.to_wire(ir_map(type_use, "items"), item) for item in _items(value, "an array value")]
|
|
65
|
+
|
|
66
|
+
if kind in ("scalar", "enum", "opaque"):
|
|
67
|
+
return scalar_to_wire(value)
|
|
68
|
+
|
|
69
|
+
raise LookupError(f"seampact: the type kind {kind!r} is not one the boundary encodes.")
|
|
70
|
+
|
|
71
|
+
def body_to_wire(self, response: IrMap, body: object) -> object:
|
|
72
|
+
"""A response body as the wire writes it: a list's items, or a table's values keyed as the handler keyed
|
|
73
|
+
them.
|
|
74
|
+
|
|
75
|
+
Raises `BoundaryFailureError` for a list or page body that is not a list, or a table body that is not a
|
|
76
|
+
mapping."""
|
|
77
|
+
body_type = ir_map(response, "body")
|
|
78
|
+
shape = ir_string(response, "shape")
|
|
79
|
+
|
|
80
|
+
if shape in _LIST_SHAPES:
|
|
81
|
+
return [self.to_wire(body_type, item) for item in _items(body, f"a {shape} body")]
|
|
82
|
+
|
|
83
|
+
if shape == _TABLE_SHAPE:
|
|
84
|
+
return {str(key): self.to_wire(body_type, item) for key, item in _entries(body).items()}
|
|
85
|
+
|
|
86
|
+
return self.to_wire(body_type, body)
|
|
87
|
+
|
|
88
|
+
def _to_python(self, type_use: IrMap, tree: object, pointer: str, unknown: list[str]) -> object:
|
|
89
|
+
kind = ir_string(type_use, "kind")
|
|
90
|
+
|
|
91
|
+
if kind == "ref" and isinstance(tree, dict):
|
|
92
|
+
return self._object_to_python(ir_string(type_use, "schema") or "", tree, pointer, unknown)
|
|
93
|
+
|
|
94
|
+
if kind == "array" and isinstance(tree, list):
|
|
95
|
+
items = ir_map(type_use, "items")
|
|
96
|
+
return [self._to_python(items, item, f"{pointer}/{index}", unknown) for index, item in enumerate(tree)]
|
|
97
|
+
|
|
98
|
+
# A scalar, an enum or an opaque value, or a tree of the wrong type, which the model's type rule reports.
|
|
99
|
+
return tree
|
|
100
|
+
|
|
101
|
+
def _object_to_python(
|
|
102
|
+
self, schema_id: str, tree: dict[str, object], pointer: str, unknown: list[str]
|
|
103
|
+
) -> dict[str, object]:
|
|
104
|
+
attributes, properties = self._object_schema(schema_id)
|
|
105
|
+
renamed: dict[str, object] = {}
|
|
106
|
+
|
|
107
|
+
for wire_name, value in tree.items():
|
|
108
|
+
attribute = attributes.get(wire_name)
|
|
109
|
+
member_pointer = f"{pointer}/{_escaped(wire_name)}"
|
|
110
|
+
|
|
111
|
+
if attribute is None:
|
|
112
|
+
unknown.append(member_pointer)
|
|
113
|
+
continue
|
|
114
|
+
|
|
115
|
+
renamed[attribute] = self._to_python(ir_map(properties[wire_name], "type"), value, member_pointer, unknown)
|
|
116
|
+
|
|
117
|
+
return renamed
|
|
118
|
+
|
|
119
|
+
def _object_to_wire(self, schema_id: str, value: object) -> dict[str, object]:
|
|
120
|
+
attributes, properties = self._object_schema(schema_id)
|
|
121
|
+
encoded: dict[str, object] = {}
|
|
122
|
+
|
|
123
|
+
for wire_name, attribute in attributes.items():
|
|
124
|
+
if not hasattr(value, attribute):
|
|
125
|
+
raise BoundaryFailureError(
|
|
126
|
+
f"seampact: a {type(value).__qualname__} was written for the schema '{schema_id}', which has the "
|
|
127
|
+
f"member '{attribute}'; return the generated type of the schema."
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
held = getattr(value, attribute)
|
|
131
|
+
member = properties[wire_name]
|
|
132
|
+
|
|
133
|
+
# An optional member that is not nullable holds None when absent; a nullable one holds `Field`.
|
|
134
|
+
absent_none = held is None and ir_true(member, "optional") and not ir_true(member, "nullable")
|
|
135
|
+
|
|
136
|
+
if held is Absent.ABSENT or absent_none:
|
|
137
|
+
continue
|
|
138
|
+
|
|
139
|
+
encoded[wire_name] = None if held is None else self.to_wire(ir_map(member, "type"), held)
|
|
140
|
+
|
|
141
|
+
return encoded
|
|
142
|
+
|
|
143
|
+
def _object_schema(self, schema_id: str) -> tuple[Mapping[str, str], dict[str, IrMap]]:
|
|
144
|
+
schema = self._contract.declaration("schemas", schema_id)
|
|
145
|
+
attributes = self._contract.members.get(schema_id)
|
|
146
|
+
|
|
147
|
+
if ir_string(schema, "kind") != _OBJECT_SCHEMA or attributes is None:
|
|
148
|
+
raise LookupError(f"seampact: no generated object type for the schema '{schema_id}'; regenerate the api.")
|
|
149
|
+
|
|
150
|
+
properties = {ir_string(member, "name") or "": member for member in ir_maps(schema, "properties")}
|
|
151
|
+
|
|
152
|
+
return attributes, properties
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def scalar_to_wire(value: object) -> object:
|
|
156
|
+
"""A scalar, enum or opaque value as the wire writes it.
|
|
157
|
+
|
|
158
|
+
Raises `BoundaryFailureError` for a date-time without an offset and a decimal that is not finite, which the
|
|
159
|
+
contract cannot write."""
|
|
160
|
+
if isinstance(value, Enum):
|
|
161
|
+
return value.value
|
|
162
|
+
|
|
163
|
+
if isinstance(value, datetime):
|
|
164
|
+
if value.utcoffset() is None:
|
|
165
|
+
raise BoundaryFailureError(
|
|
166
|
+
"seampact: the handler wrote a date-time without an offset; give it a time zone, such as UTC."
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
return _date_time_text(value)
|
|
170
|
+
|
|
171
|
+
if isinstance(value, date):
|
|
172
|
+
return value.isoformat()
|
|
173
|
+
|
|
174
|
+
if isinstance(value, Decimal):
|
|
175
|
+
return _decimal_text(value)
|
|
176
|
+
|
|
177
|
+
if isinstance(value, UUID):
|
|
178
|
+
return str(value)
|
|
179
|
+
|
|
180
|
+
return value
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def _date_time_text(value: datetime) -> str:
|
|
184
|
+
"""RFC 3339 with the fraction the value holds, without trailing zeros (ADR 0003 section 8); an offset with
|
|
185
|
+
seconds, which RFC 3339 cannot write, is written in UTC."""
|
|
186
|
+
offset = value.utcoffset()
|
|
187
|
+
|
|
188
|
+
if offset is not None and offset % _WHOLE_MINUTE:
|
|
189
|
+
value = value.astimezone(UTC)
|
|
190
|
+
|
|
191
|
+
if value.microsecond == 0:
|
|
192
|
+
return value.isoformat(timespec="seconds")
|
|
193
|
+
|
|
194
|
+
seconds, fraction_and_offset = value.isoformat(timespec="microseconds").split(".", 1)
|
|
195
|
+
fraction = fraction_and_offset[:_FRACTION_DIGITS].rstrip("0")
|
|
196
|
+
|
|
197
|
+
return f"{seconds}.{fraction}{fraction_and_offset[_FRACTION_DIGITS:]}"
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def _decimal_text(value: Decimal) -> str:
|
|
201
|
+
"""ADR 0003's decimal wire form, never an exponent."""
|
|
202
|
+
if not value.is_finite():
|
|
203
|
+
raise BoundaryFailureError(f"seampact: the handler wrote the decimal {value}; write a finite decimal.")
|
|
204
|
+
|
|
205
|
+
return format(value, "f")
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def _items(value: object, held_as: str) -> Sequence[object]:
|
|
209
|
+
if not isinstance(value, list | tuple):
|
|
210
|
+
raise BoundaryFailureError(
|
|
211
|
+
f"seampact: the handler wrote {held_as} as a {type(value).__qualname__}, not a list; return a list."
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
return value
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def _entries(value: object) -> Mapping[object, object]:
|
|
218
|
+
if not isinstance(value, Mapping):
|
|
219
|
+
raise BoundaryFailureError(
|
|
220
|
+
f"seampact: the handler wrote a table body as a {type(value).__qualname__}, not a mapping; return a "
|
|
221
|
+
"mapping keyed as the contract keys it."
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
return value
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def _escaped(key: str) -> str:
|
|
228
|
+
"""A JSON Pointer token (RFC 6901)."""
|
|
229
|
+
return key.replace("~", "~0").replace("/", "~1")
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
"""The IR's wire form of path, query and header values (ADR 0003): each is text on the wire, read as the integer,
|
|
2
|
+
number or boolean its type names when the text spells one, and kept as text otherwise, so the type rule reports it.
|
|
3
|
+
The query is read from the raw query string with form decoding, never through a framework's parsed query."""
|
|
4
|
+
|
|
5
|
+
import math
|
|
6
|
+
import re
|
|
7
|
+
from collections.abc import Mapping, Sequence
|
|
8
|
+
from decimal import Decimal
|
|
9
|
+
from typing import Final
|
|
10
|
+
from urllib.parse import quote, unquote_plus, unquote_to_bytes
|
|
11
|
+
|
|
12
|
+
from seampact.codegen.runtime.ir_view import IrMap, ir_int, ir_map, ir_string
|
|
13
|
+
from seampact.codegen.runtime.uuid_text import UUID_TEXT
|
|
14
|
+
from seampact.codegen.runtime.wire_codec import scalar_to_wire
|
|
15
|
+
|
|
16
|
+
_INTEGER: Final = re.compile(r"-?(0|[1-9][0-9]*)")
|
|
17
|
+
_NUMBER: Final = re.compile(r"-?(0|[1-9][0-9]*)(\.[0-9]+)?([eE][+-]?[0-9]+)?")
|
|
18
|
+
_TRUE: Final = "true"
|
|
19
|
+
_FALSE: Final = "false"
|
|
20
|
+
_PLACEHOLDER: Final = re.compile(r"\{([^{}]+)\}")
|
|
21
|
+
# `encodeURIComponent` keeps the unreserved set and `!*'()`.
|
|
22
|
+
_COMPONENT_SAFE: Final = "-_.!~*'()"
|
|
23
|
+
# RFC 3986's query characters beside the alphanumerics: what a kept query pair may hold unescaped in a `Link` target.
|
|
24
|
+
_QUERY_SAFE: Final = frozenset(b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~!$&'()*+,;=:@/?")
|
|
25
|
+
_PATH_SAFE: Final = frozenset(b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~/")
|
|
26
|
+
_HEX_DIGITS: Final = frozenset(b"0123456789abcdefABCDEF")
|
|
27
|
+
_PERCENT: Final = ord("%")
|
|
28
|
+
# The raw query is text read from its bytes one to one (`RawTarget`), so a pair goes back to its bytes the same way.
|
|
29
|
+
_BYTES_AS_TEXT: Final = "latin-1"
|
|
30
|
+
# `Number#toString` writes plain digits while the decimal point sits at most 21 digits right and 6 zeros left.
|
|
31
|
+
_DECIMAL_DIGITS_MAX: Final = 21
|
|
32
|
+
_DECIMAL_ZEROS_MIN: Final = -6
|
|
33
|
+
_BOOLEAN: Final = (bool,)
|
|
34
|
+
_SCALAR_TYPES: Final = {"integer": (int,), "number": (int, float), "boolean": _BOOLEAN, "string": (str,)}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def scalar_issue(type_use: IrMap, value: object) -> str | None:
|
|
38
|
+
"""The rule a scalar breaks among its type, its `uuid` format and its length bounds, the check a scope id, a
|
|
39
|
+
subject id and a rule's proof get before any hook reads them; None for a use of any other kind, which step 6 checks
|
|
40
|
+
in full."""
|
|
41
|
+
expected = _SCALAR_TYPES.get(ir_string(type_use, "type") or "")
|
|
42
|
+
|
|
43
|
+
if expected is None:
|
|
44
|
+
return None
|
|
45
|
+
|
|
46
|
+
if isinstance(value, bool) and expected is not _BOOLEAN:
|
|
47
|
+
return "type"
|
|
48
|
+
|
|
49
|
+
if not isinstance(value, expected):
|
|
50
|
+
return "type"
|
|
51
|
+
|
|
52
|
+
if not isinstance(value, str):
|
|
53
|
+
return None
|
|
54
|
+
|
|
55
|
+
if ir_string(type_use, "format") == "uuid" and not UUID_TEXT.fullmatch(value):
|
|
56
|
+
return "format"
|
|
57
|
+
|
|
58
|
+
refinements = ir_map(type_use, "refinements")
|
|
59
|
+
min_length = ir_int(refinements, "minLength")
|
|
60
|
+
max_length = ir_int(refinements, "maxLength")
|
|
61
|
+
|
|
62
|
+
if min_length is not None and len(value) < min_length:
|
|
63
|
+
return "minLength"
|
|
64
|
+
|
|
65
|
+
if max_length is not None and len(value) > max_length:
|
|
66
|
+
return "maxLength"
|
|
67
|
+
|
|
68
|
+
return None
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def decoded_segment(raw: str) -> str | None:
|
|
72
|
+
"""A raw path segment percent-decoded, or None when its bytes are not UTF-8."""
|
|
73
|
+
try:
|
|
74
|
+
return unquote_to_bytes(raw).decode("utf-8")
|
|
75
|
+
except UnicodeDecodeError:
|
|
76
|
+
return None
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def path_value(raw: str, type_use: object) -> object:
|
|
80
|
+
decoded = decoded_segment(raw)
|
|
81
|
+
|
|
82
|
+
return raw if decoded is None else scalar(decoded, type_use)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def scalar(text: str, type_use: object) -> object:
|
|
86
|
+
"""The text of a parameter read as its scalar type names it, else the text itself."""
|
|
87
|
+
scalar_type = ir_string(type_use, "type") if ir_string(type_use, "kind") == "scalar" else None
|
|
88
|
+
|
|
89
|
+
if scalar_type == "integer" and _INTEGER.fullmatch(text):
|
|
90
|
+
return int(text)
|
|
91
|
+
|
|
92
|
+
if scalar_type == "number" and _NUMBER.fullmatch(text):
|
|
93
|
+
return int(text) if _INTEGER.fullmatch(text) else float(text)
|
|
94
|
+
|
|
95
|
+
if scalar_type == "boolean" and text in (_TRUE, _FALSE):
|
|
96
|
+
return text == _TRUE
|
|
97
|
+
|
|
98
|
+
return text
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def raw_query(raw: str) -> dict[str, list[str]]:
|
|
102
|
+
"""Each query member by its form-decoded name, with all its raw values in order as received."""
|
|
103
|
+
members: dict[str, list[str]] = {}
|
|
104
|
+
|
|
105
|
+
for pair in raw.split("&"):
|
|
106
|
+
if pair == "":
|
|
107
|
+
continue
|
|
108
|
+
|
|
109
|
+
name, _, value = pair.partition("=")
|
|
110
|
+
members.setdefault(_form_decoded(name), []).append(value)
|
|
111
|
+
|
|
112
|
+
return members
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def list_items(values: Sequence[str], wire: object) -> list[str]:
|
|
116
|
+
"""A parameter's values form-decoded; a list written with `explode: false` is one comma-separated value, split
|
|
117
|
+
on raw commas only, so an encoded `%2C` stays data within one item."""
|
|
118
|
+
unexploded = ir_string(wire, "kind") == "list" and isinstance(wire, Mapping) and wire.get("explode") is False
|
|
119
|
+
items: list[str] = []
|
|
120
|
+
|
|
121
|
+
for value in values:
|
|
122
|
+
items.extend(_form_decoded(item) for item in (value.split(",") if unexploded else [value]))
|
|
123
|
+
|
|
124
|
+
return items
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def query_value(values: Sequence[str], type_use: object) -> object:
|
|
128
|
+
"""An array parameter takes every value, a scalar one its only value; several values for a scalar stay a list,
|
|
129
|
+
which the type rule refuses."""
|
|
130
|
+
if ir_string(type_use, "kind") == "array":
|
|
131
|
+
items = ir_map(type_use, "items")
|
|
132
|
+
return [scalar(value, items) for value in values]
|
|
133
|
+
|
|
134
|
+
return scalar(values[0], type_use) if len(values) == 1 else list(values)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _form_decoded(text: str) -> str:
|
|
138
|
+
"""`+` is a space; bytes that are not UTF-8 keep the text as received, so the type rule reports it."""
|
|
139
|
+
try:
|
|
140
|
+
return unquote_plus(text, encoding="utf-8", errors="strict")
|
|
141
|
+
except UnicodeDecodeError:
|
|
142
|
+
return text
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def contract_path(template: str, values: Mapping[str, object]) -> str:
|
|
146
|
+
"""The contract path `template` with each `{wire name}` replaced by its value encoded as one segment, as the
|
|
147
|
+
generated route helpers build a path.
|
|
148
|
+
|
|
149
|
+
Raises `ValueError` for a value that cannot be one segment."""
|
|
150
|
+
return _PLACEHOLDER.sub(
|
|
151
|
+
lambda placeholder: path_segment(placeholder.group(1), values[placeholder.group(1)]), template
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def path_segment(name: str, value: object) -> str:
|
|
156
|
+
"""A path value as one segment, as the generated clients send it: its wire text, encoded as JavaScript's
|
|
157
|
+
`encodeURIComponent` writes it.
|
|
158
|
+
|
|
159
|
+
Raises `ValueError` for an empty, `.` or `..` text, which a URL parser reads as another route, or a value with no
|
|
160
|
+
wire text."""
|
|
161
|
+
text = _wire_text(name, value)
|
|
162
|
+
|
|
163
|
+
if text in ("", ".", ".."):
|
|
164
|
+
raise ValueError(
|
|
165
|
+
f"The path parameter '{name}' must be one non-empty segment, not '{text}': it would reach another route."
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
return component_encoded(text)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def component_encoded(text: str) -> str:
|
|
172
|
+
"""`text` as JavaScript's `encodeURIComponent` writes it: every UTF-8 byte outside the unreserved set and `!*'()`
|
|
173
|
+
percent-encoded."""
|
|
174
|
+
return quote(text, safe=_COMPONENT_SAFE)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def kept_query_pairs(raw: str, names: Sequence[str]) -> list[str]:
|
|
178
|
+
"""The raw query's pairs whose form-decoded name is not one of `names`, in order, each made link safe."""
|
|
179
|
+
return [
|
|
180
|
+
link_safe(pair) for pair in raw.split("&") if pair != "" and _form_decoded(pair.partition("=")[0]) not in names
|
|
181
|
+
]
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def link_safe(pair: str) -> str:
|
|
185
|
+
"""Bytes outside the RFC 3986 query set, and a `%` that starts no escape, percent-encoded, so a raw `>` or `"` the
|
|
186
|
+
server accepted cannot break a `Link` target built from the pair."""
|
|
187
|
+
return _escaped_outside(pair, _QUERY_SAFE)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def path_link_safe(request_path: str) -> str:
|
|
191
|
+
"""The request path with every byte but the unreserved ones and `/` percent-encoded, valid escapes kept, so a
|
|
192
|
+
segment the server accepted adds no relation to a `Link` header or an absolute link built from it."""
|
|
193
|
+
return _escaped_outside(request_path, _PATH_SAFE)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def _escaped_outside(text: str, safe_bytes: frozenset[int]) -> str:
|
|
197
|
+
data = text.encode(_BYTES_AS_TEXT, errors="strict")
|
|
198
|
+
safe: list[str] = []
|
|
199
|
+
|
|
200
|
+
for index, byte in enumerate(data):
|
|
201
|
+
starts_escape = byte == _PERCENT and _is_escape(data, index)
|
|
202
|
+
kept = starts_escape or (byte != _PERCENT and byte in safe_bytes)
|
|
203
|
+
safe.append(chr(byte) if kept else f"%{byte:02X}")
|
|
204
|
+
|
|
205
|
+
return "".join(safe)
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def _is_escape(data: bytes, index: int) -> bool:
|
|
209
|
+
return index + 2 < len(data) and data[index + 1] in _HEX_DIGITS and data[index + 2] in _HEX_DIGITS
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def scalar_text(value: object) -> str | None:
|
|
213
|
+
"""A scalar's wire text as JavaScript's `String(value)` writes it, so every target writes the same text; None for
|
|
214
|
+
an object, an array or a number that is not finite."""
|
|
215
|
+
if isinstance(value, bool):
|
|
216
|
+
return _TRUE if value else _FALSE
|
|
217
|
+
|
|
218
|
+
if isinstance(value, float):
|
|
219
|
+
return _javascript_number(value) if math.isfinite(value) else None
|
|
220
|
+
|
|
221
|
+
wire = scalar_to_wire(value)
|
|
222
|
+
|
|
223
|
+
return str(wire) if isinstance(wire, str | int) else None
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def _wire_text(name: str, value: object) -> str:
|
|
227
|
+
if isinstance(value, float) and not math.isfinite(value):
|
|
228
|
+
raise ValueError(f"The path parameter '{name}' must be a finite number, not {value}.")
|
|
229
|
+
|
|
230
|
+
text = scalar_text(value)
|
|
231
|
+
|
|
232
|
+
if text is None:
|
|
233
|
+
raise ValueError(f"The path parameter '{name}' has no wire text: {type(value).__qualname__}.")
|
|
234
|
+
|
|
235
|
+
return text
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def _javascript_number(value: float) -> str:
|
|
239
|
+
"""The text JavaScript's `String(value)` writes of a finite number: plain digits from 1e-7 up to 1e21, an exponent
|
|
240
|
+
outside."""
|
|
241
|
+
if value == 0:
|
|
242
|
+
return "0"
|
|
243
|
+
|
|
244
|
+
_sign, digit_tuple, exponent = Decimal(repr(abs(value))).normalize().as_tuple()
|
|
245
|
+
digits = "".join(str(digit) for digit in digit_tuple)
|
|
246
|
+
point = len(digits) + int(exponent)
|
|
247
|
+
prefix = "-" if value < 0 else ""
|
|
248
|
+
|
|
249
|
+
if len(digits) <= point <= _DECIMAL_DIGITS_MAX:
|
|
250
|
+
return prefix + digits + "0" * (point - len(digits))
|
|
251
|
+
|
|
252
|
+
if 0 < point <= _DECIMAL_DIGITS_MAX:
|
|
253
|
+
return prefix + digits[:point] + "." + digits[point:]
|
|
254
|
+
|
|
255
|
+
if _DECIMAL_ZEROS_MIN < point <= 0:
|
|
256
|
+
return prefix + "0." + "0" * -point + digits
|
|
257
|
+
|
|
258
|
+
mantissa = digits[0] + ("." + digits[1:] if len(digits) > 1 else "")
|
|
259
|
+
|
|
260
|
+
return f"{prefix}{mantissa}e{'+' if point > 0 else '-'}{abs(point - 1)}"
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""Lines of generated Python that ruff formats as written: the package-relative imports, which ruff sorts after the
|
|
2
|
+
seampact ones, one blank line before the first statement and two before a class or function."""
|
|
3
|
+
|
|
4
|
+
from collections.abc import Sequence
|
|
5
|
+
from typing import Final
|
|
6
|
+
|
|
7
|
+
from seampact.codegen.printer import INDENT, fits
|
|
8
|
+
|
|
9
|
+
_DEFINITIONS: Final = ("class ", "def ", "async def ", "@")
|
|
10
|
+
OPERATIONS_MODULE: Final = "operations"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def with_local_imports(modules: Sequence[str], names: Sequence[str], first_block: Sequence[str]) -> list[str]:
|
|
14
|
+
"""The first block of a module, preceded by `from . import <module> as _<module>` for each sibling module and
|
|
15
|
+
`from .operations import <names>` for names of the package's operations module, sorted as ruff sorts them."""
|
|
16
|
+
imports = [f"from . import {module} as _{module}" for module in sorted(modules)]
|
|
17
|
+
ordered = sorted(set(names), key=_member_order)
|
|
18
|
+
single = f"from .{OPERATIONS_MODULE} import {', '.join(ordered)}"
|
|
19
|
+
|
|
20
|
+
if ordered and fits(single):
|
|
21
|
+
imports.append(single)
|
|
22
|
+
elif ordered:
|
|
23
|
+
imports += [f"from .{OPERATIONS_MODULE} import (", *(f"{INDENT}{name}," for name in ordered), ")"]
|
|
24
|
+
|
|
25
|
+
separator = ["", ""] if first_block and first_block[0].startswith(_DEFINITIONS) else [""]
|
|
26
|
+
|
|
27
|
+
return [*imports, *separator, *first_block]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _member_order(name: str) -> tuple[int, str]:
|
|
31
|
+
"""ruff's `order-by-type`: constants, then classes, then the rest; modules are lower case."""
|
|
32
|
+
if name.isupper():
|
|
33
|
+
return (0, name)
|
|
34
|
+
|
|
35
|
+
return (1, name) if name[:1].isupper() else (2, name)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: seampact-codegen
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: The neutral code generation of the seampact Python targets and the framework-free runtime of generated code.
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
9
|
+
Classifier: Typing :: Typed
|
|
10
|
+
Requires-Python: >=3.12
|
|
11
|
+
Requires-Dist: seampact-hooks==1.0.0
|
|
12
|
+
Provides-Extra: generate
|
|
13
|
+
Requires-Dist: seampact-ir==1.0.0; extra == 'generate'
|
|
14
|
+
Provides-Extra: pydantic
|
|
15
|
+
Requires-Dist: pydantic<3,>=2.9; extra == 'pydantic'
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
|
|
18
|
+
The neutral code generation plan and Pydantic emitter of the Python targets, and the framework-free runtime their generated code imports.
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
seampact/codegen/__init__.py,sha256=2mu3bKeEzNhyx8_K77J87SITftXRq7E1rq5GUgODwaY,228
|
|
2
|
+
seampact/codegen/boot_check_hooks.py,sha256=AAu6AGEHhAzj5f-7May3hILcjahU95ygpLbXc8mICac,1262
|
|
3
|
+
seampact/codegen/contract_module.py,sha256=L795efHj-JfH3f2YR2m7IBZwLC1-En77B_VaVyd8wLo,6077
|
|
4
|
+
seampact/codegen/credentials_classes.py,sha256=eF_yJ7Pw5Wz3JS9cFXu2UgAncB4SLjKhNAmVmrtv6VI,4213
|
|
5
|
+
seampact/codegen/handler_forms.py,sha256=w3NsBSepz8-qw2S23d5oMPGSC6kJ9xELF8AH1WSaPTQ,4723
|
|
6
|
+
seampact/codegen/input_schemas.py,sha256=JlOrYFp-7pQKZ2FFuVGoM4TVi3RNSJLEuGRd7xNKz2s,4621
|
|
7
|
+
seampact/codegen/kept_forms.py,sha256=ssg8qy0QqGCBTvdhU15fE0w9-oOtNxgk4L9JkC75w5E,22186
|
|
8
|
+
seampact/codegen/matrix_row.py,sha256=T0S8f3JyeCaJvyoUoHTDdkHw6AMBkZbmnaAnQ9-rg9g,991
|
|
9
|
+
seampact/codegen/names.py,sha256=Oc7FWQLK9W5YQRGAX-Ddl38RP-n3EwZ13DY1SOOXqXo,6084
|
|
10
|
+
seampact/codegen/native_docs_file.py,sha256=llDo1G5qs9lHkykaC2RPbAn7rMPnAErjKfrlMsUW_b8,6900
|
|
11
|
+
seampact/codegen/operation_types.py,sha256=uE2mtYpwNwd8qwuld2Wk0qM9flu-vyZ2Gn4nrUTiLOc,17149
|
|
12
|
+
seampact/codegen/placement_lines.py,sha256=ZHDrcOdqX5HVTKl5TC5Xe32xQHBW0jakMykppSvDwmA,3130
|
|
13
|
+
seampact/codegen/printer.py,sha256=Q-agn08z_dHz1vFHYqp27WWJQRBJ4KfUc4k4H8itNx4,7116
|
|
14
|
+
seampact/codegen/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
15
|
+
seampact/codegen/pydantic_forms.py,sha256=nbDQEDfTFTfI2sk9R2WHHwGxDr8kBxzaTiuzbEa-eTE,2377
|
|
16
|
+
seampact/codegen/pydantic_layout.py,sha256=iV_JBHD4RfU0CDBhQ8KqzwhMZfkSMc35NgzwljZpGuI,6904
|
|
17
|
+
seampact/codegen/pydantic_models.py,sha256=AnPgzoNm2ZuxFD7tmtGA94P7Kf2Hs6_aNPWzly8iWvU,16331
|
|
18
|
+
seampact/codegen/redacted_members.py,sha256=LlnJ-B1vRmhWf6t-tmMsdNX_evDFXokjfIgmtvtUYWg,1873
|
|
19
|
+
seampact/codegen/refusals.py,sha256=Wc9n3u4YAxETZfrOIfPt8N2DV2hBp1KZBOwVMB2M_9w,3450
|
|
20
|
+
seampact/codegen/request_deadline.py,sha256=pGQtpKoeIyaJfa7nCZ84dFVAOnE6rZbbn02LztWJCbM,2677
|
|
21
|
+
seampact/codegen/response_header_forms.py,sha256=ZFtgaK1M3df_SZyxyilwvY2ZI3MzmDkZ7LIhU-WbTNs,3213
|
|
22
|
+
seampact/codegen/restricted_sort_keys.py,sha256=MJUV055aE442MxiQ6nsTzNIz4qrmEWZe29nUQYgAJMM,746
|
|
23
|
+
seampact/codegen/route_helpers.py,sha256=aExfo5Ig3JlXZ6r3mKNzuYXlJ5j1_P8OIrV5LjTQ3vo,4675
|
|
24
|
+
seampact/codegen/routes.py,sha256=8LQomkDOp_enNvFivzX2w62Unk4-4BU3r2EkO-2-VJ4,5006
|
|
25
|
+
seampact/codegen/source_lines.py,sha256=xLrhARNjoVaPoLyIbX0MTSfLtfmEjxA3qvkp0KsxXTY,1565
|
|
26
|
+
seampact/codegen/runtime/__init__.py,sha256=A6lh9ZgdhKXBi-K4cwURJ6bIcciOJNcHFRtE2llcUr8,437
|
|
27
|
+
seampact/codegen/runtime/access_kinds.py,sha256=RV4LKI_Bfj3nQL-FrzVcSDQtzIKJWH1sm_OczP4PPbI,1101
|
|
28
|
+
seampact/codegen/runtime/access_steps.py,sha256=YkXtdSPNJDBIEvnDx6YSCxJF37sUYKklncqX8u5qr_4,20754
|
|
29
|
+
seampact/codegen/runtime/body_reader.py,sha256=hKdFyUZQ9HQREORHvQeJIkPtSfkrvcS84Hv1gi-qjus,2442
|
|
30
|
+
seampact/codegen/runtime/cache_headers.py,sha256=L26z4tpBIHVNxFoWYdhGEugbRkcgcYji7HAXOg3EDcA,3257
|
|
31
|
+
seampact/codegen/runtime/contract.py,sha256=MR4dYzOWsVfc2IpDoqFC7OKMNkHi5ux8B52QhBmkCOk,11104
|
|
32
|
+
seampact/codegen/runtime/cors_preflight.py,sha256=tUQIGr0yKJIx5_uRp6SNB0fKiVvW9TTzJPeH11AgxrM,809
|
|
33
|
+
seampact/codegen/runtime/deadline.py,sha256=sB6zzouRGFCiS3BQKKO4aWhfnNCv7DeZHxeDgSXH4D4,3002
|
|
34
|
+
seampact/codegen/runtime/digests.py,sha256=lw6wcesv5Oj595ZAbCN0wO9kl08zG8X_hdD3sDxntj0,3694
|
|
35
|
+
seampact/codegen/runtime/failures.py,sha256=_00KuIjxhXg-yanuMYG4hcRmuUfhDRv99G9Vev4yovk,1479
|
|
36
|
+
seampact/codegen/runtime/field_rights.py,sha256=pmFG19ZvJq8jH0E5DrKv5SXywG_FKaU-4gdk7KDIrTI,7570
|
|
37
|
+
seampact/codegen/runtime/framework_routes.py,sha256=1_ZFqoKvZWXRr5TPXwGBp6P1dngUj4uJ-GoXoxlgHHg,1393
|
|
38
|
+
seampact/codegen/runtime/framework_versions.py,sha256=5Szre-JtK-rI5IcL5CF5ACQFnj0QtrtLoN74HSazTpo,3504
|
|
39
|
+
seampact/codegen/runtime/handler_call.py,sha256=Sv1STll1lGUJXq6D3pXw4Efpj1id6gWtskw_IyjW0H4,1408
|
|
40
|
+
seampact/codegen/runtime/handler_reply.py,sha256=RL0J9kTYpb_0kvP4TJQmcgW4oAAZdjG9THhj3uk7jHM,6842
|
|
41
|
+
seampact/codegen/runtime/hinted_values.py,sha256=UqkVIudACNMp9ehG0vY40Z13WAAIGIP6GI7a1J950Hs,1875
|
|
42
|
+
seampact/codegen/runtime/hook_check.py,sha256=OdLycQIMNeAop_bYUuIXX3MIw7EazRS6EoHvLygVplc,2754
|
|
43
|
+
seampact/codegen/runtime/idempotency.py,sha256=f0prdZC4RyCL4dHWS5nk-cTlCuqh4cwf1fz4eZwD73Q,11651
|
|
44
|
+
seampact/codegen/runtime/input_decoding.py,sha256=COXqqLvV9xyAGQRICB-Q5PWjB79h613U544fbg-TyMw,8236
|
|
45
|
+
seampact/codegen/runtime/ir_view.py,sha256=uf1NXSkBssUiVyXvIotp-yEU67CfVxXgisMHzpitm5Q,1666
|
|
46
|
+
seampact/codegen/runtime/native_docs.py,sha256=tce0NSXrpXpjTgeMWhe7Xu2SIqkRnlwY-VLmVSrl96I,4889
|
|
47
|
+
seampact/codegen/runtime/page_facts.py,sha256=EUdFL7SwY7F1pDP_e2ZXcG5VxR2ybyjJ9NMMIBEBZPQ,1444
|
|
48
|
+
seampact/codegen/runtime/page_reply.py,sha256=96sCe8PGUhHsSlQk5ax28dfcGDVclWHsesXc2fVCVH0,15250
|
|
49
|
+
seampact/codegen/runtime/pattern.py,sha256=k8SAv6fgzK9vpfcgxEr3HGJmY5gWCkviYQkH6BalLDo,2279
|
|
50
|
+
seampact/codegen/runtime/principal_members.py,sha256=rp1YOJOm87xyMRLMs9vQfOrEYStt8YN8S1I-rUKtU5M,1947
|
|
51
|
+
seampact/codegen/runtime/public_origin.py,sha256=tyPV6uEHN9xnhaXG7pUEFzKFBegQsCXFcXt0i60Kmyo,3925
|
|
52
|
+
seampact/codegen/runtime/raw_target.py,sha256=Ph6-ksb14qA9EgBTqCv260CLjhoBm4AtEbpbaZW3310,3699
|
|
53
|
+
seampact/codegen/runtime/replies.py,sha256=czlcIvR9l3hep6KeUm6E81EDomXpo4v74oiTxM1GTU4,5743
|
|
54
|
+
seampact/codegen/runtime/reply_verdict.py,sha256=H6vMAeydO1f4W75_9Ore1LZUHcpCHJfruaeqk6IrYgw,7428
|
|
55
|
+
seampact/codegen/runtime/requirement_params.py,sha256=VthIFeHGgSYmfBkBfcbmAQdiDG_BUHU62qdkF1NHwF8,7514
|
|
56
|
+
seampact/codegen/runtime/response_header_entries.py,sha256=9LZu5RhnTrHnioq9dzpdXK-ZuumueNfbYbihsXwQ9N0,7803
|
|
57
|
+
seampact/codegen/runtime/response_headers.py,sha256=C3cE3QAgO6_vnppaegp1kOQyyJHfz_w6qpvIiTo2DbA,5465
|
|
58
|
+
seampact/codegen/runtime/route_match.py,sha256=-T8ul7EW0H_YZuSnyp5A5AKwugfd-MWxJvJ_ipr8UCM,3128
|
|
59
|
+
seampact/codegen/runtime/run_settings.py,sha256=EMQgl0Ru9idKO0zCYlAufhrTRHukMC-XSqRgof95EAQ,1263
|
|
60
|
+
seampact/codegen/runtime/signature_header.py,sha256=38Zh-vZJbiefA7FrGq23l_HmudcyhEhXSTfJgDK0lsg,2980
|
|
61
|
+
seampact/codegen/runtime/sync_driver.py,sha256=oPVMo7vUAuPTGmr50hMrhWRxFdRs3MP65ExfiuB2YWk,1366
|
|
62
|
+
seampact/codegen/runtime/templates.py,sha256=5PbFqSZhGK2LzoiJeZUSEBbJjuZSc1AX8hjnqQOIj5o,4062
|
|
63
|
+
seampact/codegen/runtime/uuid_text.py,sha256=uC7kmx6B_LRA35xQ241vvjKvfvY7x8GYFAEXJfqkPEE,327
|
|
64
|
+
seampact/codegen/runtime/value_rules.py,sha256=MDY3umSM9ZYfgRvIbYoO3KEwVr1bzp04N8HwSkU1f1E,8292
|
|
65
|
+
seampact/codegen/runtime/version.py,sha256=E4sGeqn4loulZ_8BpjcCCXTmC9TQU_HoE4pXjxFPjOU,2491
|
|
66
|
+
seampact/codegen/runtime/version_token.py,sha256=R1_Apddd4EB6IO8fwbYktMTyWY2Lgna53WIrnxIL9n4,5050
|
|
67
|
+
seampact/codegen/runtime/wire_codec.py,sha256=ImIpQ60MFL5LUcLdYYr-qn27orO2aFE-Mp4iqHqP2pM,9215
|
|
68
|
+
seampact/codegen/runtime/wire_values.py,sha256=OSSei111cbLTVCdz5a1zV0RNaeQ1YBrROuBbsQ8PeaQ,10177
|
|
69
|
+
seampact/codegen/runtime/pydantic/__init__.py,sha256=JPg2u9lS-GibWiKyfO78PKszSLZxPq8CzbN23mV5aHM,6039
|
|
70
|
+
seampact/codegen/runtime/pydantic/checked_input.py,sha256=qHlkV7VD6EcZH2IrDT6RWCev6kx4TraPhvJMZYpJecQ,2377
|
|
71
|
+
seampact/codegen/runtime/pydantic/contract_input.py,sha256=GPOkU5sQCUw_-vFUZFoX7w-83Fz1Bihi0H2MZH5jkjc,8431
|
|
72
|
+
seampact/codegen/runtime/pydantic/issues.py,sha256=rWCnYrwtAHQAwVnItDm_FNXrayhQTquifsOpvJ2MKu0,5295
|
|
73
|
+
seampact_codegen-1.0.0.dist-info/METADATA,sha256=P0MWFggtZ72Jebo7xlDG6M6n6PvE3-riesdsZ49ycgo,754
|
|
74
|
+
seampact_codegen-1.0.0.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
|
|
75
|
+
seampact_codegen-1.0.0.dist-info/licenses/LICENSE,sha256=5ZUBhQlzdoQ7CzKtydHCW5CGsxG5ucoOu1lKo7k3RrY,1070
|
|
76
|
+
seampact_codegen-1.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Julien Metral
|
|
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.
|