From f0bf2fe75b4c35ca5701a17406d3d40a8326968e Mon Sep 17 00:00:00 2001 From: elean-latedev Date: Sat, 7 Nov 2026 11:26:12 +0000 Subject: [PATCH] fix(generator): spec defaults of {} or [] become None, not shared mutable kwargs Co-Authored-By: Claude Opus 5.5 --- scripts/generate_resources.py | 4 +- src/late/resources/_generated/automations.py | 12 ++--- tests/test_generated_mutable_defaults.py | 55 ++++++++++++++++++++ 3 files changed, 64 insertions(+), 7 deletions(-) create mode 100644 tests/test_generated_mutable_defaults.py diff --git a/scripts/generate_resources.py b/scripts/generate_resources.py index 33731ddf..f8fc419d 100644 --- a/scripts/generate_resources.py +++ b/scripts/generate_resources.py @@ -244,7 +244,9 @@ def generate_method_signature( for param in optional_params: default = param.get("default") - if default is None: + # A spec default of {} or [] would become a shared mutable default + # (ruff B006). None omits the field, so the API applies that default. + if default is None or isinstance(default, (dict, list)): default_str = "None" elif isinstance(default, str): default_str = f'"{default}"' diff --git a/src/late/resources/_generated/automations.py b/src/late/resources/_generated/automations.py index 529cb56b..3cfac4f5 100644 --- a/src/late/resources/_generated/automations.py +++ b/src/late/resources/_generated/automations.py @@ -271,7 +271,7 @@ def install_automation_template( template_id: str, *, idempotency_key: str | None = None, - inputs: dict[str, Any] | None = {}, + inputs: dict[str, Any] | None = None, targets: list[dict[str, Any]] | None = None, status: str | None = "paused", ) -> dict[str, Any]: @@ -299,7 +299,7 @@ def install_shared_automation_template( share_token: str, *, idempotency_key: str | None = None, - inputs: dict[str, Any] | None = {}, + inputs: dict[str, Any] | None = None, targets: list[dict[str, Any]] | None = None, status: str | None = "paused", ) -> dict[str, Any]: @@ -327,7 +327,7 @@ def install_zernio_automation_template( template_id: str, *, idempotency_key: str | None = None, - inputs: dict[str, Any] | None = {}, + inputs: dict[str, Any] | None = None, targets: list[dict[str, Any]] | None = None, status: str | None = "paused", ) -> dict[str, Any]: @@ -638,7 +638,7 @@ async def ainstall_automation_template( template_id: str, *, idempotency_key: str | None = None, - inputs: dict[str, Any] | None = {}, + inputs: dict[str, Any] | None = None, targets: list[dict[str, Any]] | None = None, status: str | None = "paused", ) -> dict[str, Any]: @@ -668,7 +668,7 @@ async def ainstall_shared_automation_template( share_token: str, *, idempotency_key: str | None = None, - inputs: dict[str, Any] | None = {}, + inputs: dict[str, Any] | None = None, targets: list[dict[str, Any]] | None = None, status: str | None = "paused", ) -> dict[str, Any]: @@ -698,7 +698,7 @@ async def ainstall_zernio_automation_template( template_id: str, *, idempotency_key: str | None = None, - inputs: dict[str, Any] | None = {}, + inputs: dict[str, Any] | None = None, targets: list[dict[str, Any]] | None = None, status: str | None = "paused", ) -> dict[str, Any]: diff --git a/tests/test_generated_mutable_defaults.py b/tests/test_generated_mutable_defaults.py new file mode 100644 index 00000000..4a253c9f --- /dev/null +++ b/tests/test_generated_mutable_defaults.py @@ -0,0 +1,55 @@ +"""Spec defaults of {} or [] must not become shared mutable kwarg defaults.""" + +import importlib +from pathlib import Path +from unittest.mock import Mock + + +def test_empty_object_and_array_defaults_are_omitted(monkeypatch): + monkeypatch.syspath_prepend(str(Path(__file__).resolve().parents[1] / "scripts")) + generator = importlib.import_module("generate_resources") + operation = { + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "inputs": {"type": "object", "default": {}}, + "targets": { + "type": "array", + "items": {"type": "string"}, + "default": [], + }, + "status": {"type": "string", "default": "paused"}, + }, + } + } + } + } + } + source = generator.generate_resource_class( + "probe", + [ + { + "operation_id": "installTemplate", + "http_method": "post", + "path": "/v1/templates/install", + "summary": "Install template", + "description": "", + "params": generator.extract_parameters(operation, {}), + } + ], + ) + + assert "= {}," not in source + assert "= []," not in source + + namespace = {} + exec(compile(source, "", "exec"), namespace) + client = Mock() + client._post = Mock(return_value={}) + namespace["ProbeResource"](client).install_template() + client._post.assert_called_once_with( + "/v1/templates/install", data={"status": "paused"} + )