From 7788dc9c497fee0a65e5c8396456151b617df85c Mon Sep 17 00:00:00 2001 From: "tyrion.lh" Date: Thu, 16 Jul 2026 00:51:56 -0700 Subject: [PATCH 1/2] fix: reuse duplicate capability extension models --- generate_models.sh | 2 + src/ucp_sdk/models/schemas/capability.py | 51 +++--------------- tests/test_generated_models.py | 66 ++++++++++++++++++++++++ 3 files changed, 74 insertions(+), 45 deletions(-) create mode 100644 tests/test_generated_models.py diff --git a/generate_models.sh b/generate_models.sh index a45543c..f6e79ad 100755 --- a/generate_models.sh +++ b/generate_models.sh @@ -66,6 +66,7 @@ mkdir -p "$OUTPUT_DIR" # Run generation using uv # We use --use-schema-description to use descriptions from JSON schema as docstrings # We use --field-constraints to include validation constraints (regex, min/max, etc.) +# We use --reuse-model to collapse structurally identical generated types. # Note: Formatting is done as a post-processing step. uv run \ --link-mode=copy \ @@ -83,6 +84,7 @@ uv run \ --use-double-quotes \ --allow-extra-fields \ --use-type-alias \ + --reuse-model \ --custom-template-dir templates \ --additional-imports pydantic.ConfigDict diff --git a/src/ucp_sdk/models/schemas/capability.py b/src/ucp_sdk/models/schemas/capability.py index 475e6dd..81213b0 100644 --- a/src/ucp_sdk/models/schemas/capability.py +++ b/src/ucp_sdk/models/schemas/capability.py @@ -58,23 +58,10 @@ """ -Extends2 = TypeAliasType( - "Extends2", - Annotated[ - str, Field(..., pattern="^[a-z][a-z0-9]*(?:\\.[a-z][a-z0-9_]*)+$") - ], -) -""" -Parent capability(s) this extends. Present for extensions, absent for root capabilities. Use array for multi-parent extensions. -""" +Extends2 = TypeAliasType("Extends2", Extends) -Extends3Item = TypeAliasType( - "Extends3Item", - Annotated[ - str, Field(..., pattern="^[a-z][a-z0-9]*(?:\\.[a-z][a-z0-9_]*)+$") - ], -) +Extends3Item = TypeAliasType("Extends3Item", Extends1Item) Extends3 = TypeAliasType( @@ -85,23 +72,10 @@ """ -Extends4 = TypeAliasType( - "Extends4", - Annotated[ - str, Field(..., pattern="^[a-z][a-z0-9]*(?:\\.[a-z][a-z0-9_]*)+$") - ], -) -""" -Parent capability(s) this extends. Present for extensions, absent for root capabilities. Use array for multi-parent extensions. -""" +Extends4 = TypeAliasType("Extends4", Extends) -Extends5Item = TypeAliasType( - "Extends5Item", - Annotated[ - str, Field(..., pattern="^[a-z][a-z0-9]*(?:\\.[a-z][a-z0-9_]*)+$") - ], -) +Extends5Item = TypeAliasType("Extends5Item", Extends1Item) Extends5 = TypeAliasType( @@ -112,23 +86,10 @@ """ -Extends6 = TypeAliasType( - "Extends6", - Annotated[ - str, Field(..., pattern="^[a-z][a-z0-9]*(?:\\.[a-z][a-z0-9_]*)+$") - ], -) -""" -Parent capability(s) this extends. Present for extensions, absent for root capabilities. Use array for multi-parent extensions. -""" +Extends6 = TypeAliasType("Extends6", Extends) -Extends7Item = TypeAliasType( - "Extends7Item", - Annotated[ - str, Field(..., pattern="^[a-z][a-z0-9]*(?:\\.[a-z][a-z0-9_]*)+$") - ], -) +Extends7Item = TypeAliasType("Extends7Item", Extends1Item) Extends7 = TypeAliasType( diff --git a/tests/test_generated_models.py b/tests/test_generated_models.py new file mode 100644 index 0000000..617f1a1 --- /dev/null +++ b/tests/test_generated_models.py @@ -0,0 +1,66 @@ +# Copyright 2026 UCP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Tests for properties of the committed generated models.""" + +import ast +import unittest +from pathlib import Path + + +class GeneratedModelReuseTest(unittest.TestCase): + """Tests that code generation reuses structurally identical aliases.""" + + def test_capability_extends_aliases_reuse_canonical_definitions( + self, + ) -> None: + """Numbered aliases stay importable while sharing their definitions.""" + source_path = ( + Path(__file__).parents[1] + / "src/ucp_sdk/models/schemas/capability.py" + ) + tree = ast.parse(source_path.read_text()) + expected_targets = { + "Extends2": "Extends", + "Extends4": "Extends", + "Extends6": "Extends", + "Extends3Item": "Extends1Item", + "Extends5Item": "Extends1Item", + "Extends7Item": "Extends1Item", + } + + alias_values = {} + for node in tree.body: + if not isinstance(node, ast.Assign) or len(node.targets) != 1: + continue + target = node.targets[0] + if not isinstance(target, ast.Name): + continue + if not isinstance(node.value, ast.Call): + continue + if not isinstance(node.value.func, ast.Name): + continue + if node.value.func.id != "TypeAliasType": + continue + alias_values[target.id] = node.value.args[1] + + for alias, canonical in expected_targets.items(): + with self.subTest(alias=alias): + value = alias_values[alias] + self.assertIsInstance(value, ast.Name) + self.assertEqual(value.id, canonical) + + +if __name__ == "__main__": + unittest.main() From 2f24e22a0af4d6908568008b7fa8e71321663845 Mon Sep 17 00:00:00 2001 From: damaz91 Date: Thu, 16 Jul 2026 11:28:36 +0000 Subject: [PATCH 2/2] test: remove fragile generated models reuse test --- tests/test_generated_models.py | 66 ---------------------------------- 1 file changed, 66 deletions(-) delete mode 100644 tests/test_generated_models.py diff --git a/tests/test_generated_models.py b/tests/test_generated_models.py deleted file mode 100644 index 617f1a1..0000000 --- a/tests/test_generated_models.py +++ /dev/null @@ -1,66 +0,0 @@ -# Copyright 2026 UCP Authors -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Tests for properties of the committed generated models.""" - -import ast -import unittest -from pathlib import Path - - -class GeneratedModelReuseTest(unittest.TestCase): - """Tests that code generation reuses structurally identical aliases.""" - - def test_capability_extends_aliases_reuse_canonical_definitions( - self, - ) -> None: - """Numbered aliases stay importable while sharing their definitions.""" - source_path = ( - Path(__file__).parents[1] - / "src/ucp_sdk/models/schemas/capability.py" - ) - tree = ast.parse(source_path.read_text()) - expected_targets = { - "Extends2": "Extends", - "Extends4": "Extends", - "Extends6": "Extends", - "Extends3Item": "Extends1Item", - "Extends5Item": "Extends1Item", - "Extends7Item": "Extends1Item", - } - - alias_values = {} - for node in tree.body: - if not isinstance(node, ast.Assign) or len(node.targets) != 1: - continue - target = node.targets[0] - if not isinstance(target, ast.Name): - continue - if not isinstance(node.value, ast.Call): - continue - if not isinstance(node.value.func, ast.Name): - continue - if node.value.func.id != "TypeAliasType": - continue - alias_values[target.id] = node.value.args[1] - - for alias, canonical in expected_targets.items(): - with self.subTest(alias=alias): - value = alias_values[alias] - self.assertIsInstance(value, ast.Name) - self.assertEqual(value.id, canonical) - - -if __name__ == "__main__": - unittest.main()