Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 1 addition & 1 deletion codegen/didit_codegen/generate.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ def generate(openapi_dir: pathlib.Path) -> dict[str, str]:
files: dict[str, str] = {}
for rel, text in render_types(builder.registry).items():
files[f"src/didit/types/{rel}"] = text
roots = build_tree(methods)
roots = build_tree(methods, frozenset(builder.registry.defs))
for rel, text in render_resources(roots, builder.registry).items():
files[f"src/didit/resources/{rel}"] = text
for rel, text in render_tests(methods, builder.registry).items():
Expand Down
15 changes: 11 additions & 4 deletions codegen/didit_codegen/render.py
Original file line number Diff line number Diff line change
Expand Up @@ -268,14 +268,19 @@ def _render_types_init(registry: Registry) -> str:


class ResourceNode:
def __init__(self, path: str) -> None:
def __init__(self, path: str, type_names: frozenset[str] = frozenset()) -> None:
self.path = path
self.type_names = type_names
self.methods: list[Method] = []
self.children: dict[str, ResourceNode] = {}

@property
def cls(self) -> str:
return resource_class(self.path)
"""The resource class. A type of the same name (resource ``travel_rule.workspace`` and the
``TravelRuleWorkspace`` response type) would be shadowed in the resource's module, so the
resource takes a ``Resource`` suffix."""
name = resource_class(self.path)
return f"{name}Resource" if name in self.type_names else name

@property
def module(self) -> str:
Expand All @@ -292,15 +297,17 @@ def file(self) -> str:
return "/".join(parts) + ".py"


def build_tree(methods: list[Method]) -> dict[str, ResourceNode]:
def build_tree(
methods: list[Method], type_names: frozenset[str] = frozenset()
) -> dict[str, ResourceNode]:
roots: dict[str, ResourceNode] = {}
nodes: dict[str, ResourceNode] = {}
for method in methods:
parts = method.resource.split(".")
for i in range(1, len(parts) + 1):
path = ".".join(parts[:i])
if path not in nodes:
nodes[path] = ResourceNode(path)
nodes[path] = ResourceNode(path, type_names)
if i == 1:
roots[path] = nodes[path]
else:
Expand Down
20 changes: 18 additions & 2 deletions codegen/didit_codegen/render_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,9 @@ def sample(spec: Spec, schema: Json, *, request: bool, depth: int = 0, full: boo
if depth > MAX_DEPTH:
return []
item = sample(spec, schema.get("items", {}), request=request, depth=depth + 1, full=full)
if item is None and not spec.resolve(schema.get("items", {})).get("nullable"):
# The items sit past the depth limit: an empty list stays valid, a null item would not.
return []
min_items = schema.get("minItems") or 1
return [item] * max(1, min(int(min_items), 3))
if kind == "object" or "properties" in schema:
Expand Down Expand Up @@ -338,6 +341,13 @@ def _kwargs_for(method: Method) -> list[tuple[Arg, Json]]:
for arg in body_args:
if arg.wire in chosen:
out.append((arg, scrub(chosen[arg.wire], arg.wire)))
whole = method.whole_body
if whole is not None:
# A body that is not a property map (a oneOf, an array) is passed whole as ``body=``.
if method.has_body_example and conforms(spec, whole.schema, example, request=True):
out.append((whole, scrub(example, "body")))
elif whole.required:
out.append((whole, scrub(sample(spec, whole.schema, request=True, full=False), "body")))
for arg in (a for a in method.kwargs if a.location == "query"):
if (
arg.has_example
Expand Down Expand Up @@ -386,11 +396,14 @@ def _render_test(method: Method, registry: Registry, imports: set[tuple[str, str
for arg, value in path_values:
path = path.replace("{" + arg.wire + "}", urllib.parse.quote(value, safe=""))
query: dict[str, list[str]] = defaultdict(list)
json_body: dict[str, Json] = {}
json_body: Json = {}
for arg, value in kwargs:
if arg.location == "query":
query[arg.wire].extend(_query_text(value))
elif arg is method.whole_body:
json_body = value
else:
assert isinstance(json_body, dict)
json_body[arg.wire] = value
call_args = [literal(v) for _, v in path_values]
call_args += [f"{a.py}={literal(v)}" for a, v in kwargs]
Expand All @@ -399,7 +412,9 @@ def _render_test(method: Method, registry: Registry, imports: set[tuple[str, str

status, response, strict = _response_value(method)
multipart = method.body_mode == "multipart" or (
method.body_mode == "auto" and any(name in json_body for name in method.file_fields)
method.body_mode == "auto"
and isinstance(json_body, dict)
and any(name in json_body for name in method.file_fields)
)
resp = method.response
if resp.kind == "page":
Expand Down Expand Up @@ -437,6 +452,7 @@ def _render_test(method: Method, registry: Registry, imports: set[tuple[str, str
fields.append(f"query={literal(dict(query))}")
if method.body_mode != "none":
if multipart:
assert isinstance(json_body, dict)
text_fields = sorted(k for k in json_body if k not in method.file_fields)
file_fields = sorted(k for k in json_body if k in method.file_fields)
fields.append(f"multipart_fields={literal(text_fields)}")
Expand Down
8 changes: 4 additions & 4 deletions openapi/SOURCE.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
{
"verification.json": {
"url": "https://docs.didit.me/openapi-25.json",
"sha256": "d0fe3a250c30a0b07d17276463c04ecd26be50b07ac2896995a77563cfde201b",
"fetched_at": "2026-10-17T10:00:00Z"
"sha256": "9ada6192b24a5d85e983e57e87fccb6e762d74060b6f0b892928356dd432137c",
"fetched_at": "2026-10-23T06:48:12Z"
},
"account.json": {
"url": "https://docs.didit.me/openapi-auth.json",
"sha256": "171f5294c6d4a53d96f03b8820afc04a025f344ce2be137b5378399f6fc44ee4",
"fetched_at": "2026-10-17T10:00:00Z"
"sha256": "5fafb88b306ab60c7c105b0bf00fa2a6a49b5d2ca8115eb003b332f073844b39",
"fetched_at": "2026-10-23T06:48:12Z"
},
"webhook-vectors.json": {
"generator": "product-automations scripts/server-sdks/make_vectors.py (seeded 2026-10-17)",
Expand Down
Loading
Loading