From e8dfba165f1adc8de7cda27928be57a91850bccb Mon Sep 17 00:00:00 2001 From: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com> Date: Wed, 29 Jul 2026 14:22:22 +0200 Subject: [PATCH] fix(api): initialize compatibility exports safely --- scripts/patch_generated_score_compat.py | 22 ++++++++++++++++--- .../unit/test_patch_generated_score_compat.py | 7 +++++- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/scripts/patch_generated_score_compat.py b/scripts/patch_generated_score_compat.py index 1636cc424..46c23ec6b 100644 --- a/scripts/patch_generated_score_compat.py +++ b/scripts/patch_generated_score_compat.py @@ -10,6 +10,7 @@ from __future__ import annotations import argparse +import ast from pathlib import Path CLIENT_IMPORTS = """\ @@ -141,10 +142,25 @@ def _append_type_exports(path: Path, imports_by_type: dict[str, str]) -> None: for type_name, module_name in imports_by_type.items() ) names = ", ".join(repr(type_name) for type_name in TYPE_NAMES) - path.write_text( - f"{contents.rstrip()}\n\n{exports_marker}\n{imports}\n" - f"__all__ = [*__all__, {names}]\n" + defines_all = any( + ( + isinstance(node, ast.Assign) + and any( + isinstance(target, ast.Name) and target.id == "__all__" + for target in node.targets + ) + ) + or ( + isinstance(node, ast.AnnAssign) + and isinstance(node.target, ast.Name) + and node.target.id == "__all__" + ) + for node in ast.parse(contents).body + ) + exports = ( + f"__all__ = [*__all__, {names}]" if defines_all else f"__all__ = [{names}]" ) + path.write_text(f"{contents.rstrip()}\n\n{exports_marker}\n{imports}\n{exports}\n") def _create_type_aliases(types_dir: Path) -> None: diff --git a/tests/unit/test_patch_generated_score_compat.py b/tests/unit/test_patch_generated_score_compat.py index 5d154c80a..384abc4be 100644 --- a/tests/unit/test_patch_generated_score_compat.py +++ b/tests/unit/test_patch_generated_score_compat.py @@ -33,7 +33,9 @@ def _write_generated_fixture(api_root: Path, *, canonical_has_create: bool) -> N " async def delete(self):\n" " pass\n" ) - (legacy_dir / "__init__.py").write_text("__all__ = []\n") + (legacy_dir / "__init__.py").write_text( + "# This file was auto-generated by Fern from our API Definition.\n" + ) (legacy_dir.parent / "__init__.py").write_text("__all__ = []\n") (legacy_types_dir / "__init__.py").write_text("__all__ = []\n") @@ -78,6 +80,9 @@ def test_postprocessor_adds_delegating_methods_and_type_aliases(tmp_path: Path) "CreateScoreResponse" in (api_root / "legacy" / "score_v1" / "__init__.py").read_text() ) + legacy_package = (api_root / "legacy" / "score_v1" / "__init__.py").read_text() + assert "__all__ = [" in legacy_package + assert "[*__all__" not in legacy_package assert "CreateScoreResponse" in (api_root / "legacy" / "__init__.py").read_text() second_result = _run_postprocessor(api_root)