From 96bbafc5d3a6ff50836495cad018bdb8eaecaa20 Mon Sep 17 00:00:00 2001 From: Doug Borg Date: Sun, 26 Oct 2025 21:43:57 -0600 Subject: [PATCH 1/2] Add static file infrastructure for generated clients This commit adds the ability to include static Python files in generated clients, which is essential for implementing shared utilities and modules that don't need to be templated. Changes: - Add StaticFile model to represent static content - Extend ConversionResult to include static_files list - Create static_file_generator.py module (currently returns empty list) - Update write_data() to write static files to output directory - Add comprehensive tests for static file generation This infrastructure will be used for Issue #100 enhanced error handling: - errors.py: Exception hierarchy (APIError, ClientError, ServerError) - response.py: DetailedResponse wrapper with fluent API - utils.py: Helper functions (unwrap, expect, match, type guards) All tests passing. Ready for static file content in follow-up PRs. Related to #100 --- src/openapi_python_generator/generate_data.py | 4 ++ .../language_converters/python/generator.py | 5 ++ .../python/static_file_generator.py | 14 ++++ src/openapi_python_generator/models.py | 6 ++ tests/test_static_files.py | 72 +++++++++++++++++++ 5 files changed, 101 insertions(+) create mode 100644 src/openapi_python_generator/language_converters/python/static_file_generator.py create mode 100644 tests/test_static_files.py diff --git a/src/openapi_python_generator/generate_data.py b/src/openapi_python_generator/generate_data.py index 9630e45..3f5fdfd 100644 --- a/src/openapi_python_generator/generate_data.py +++ b/src/openapi_python_generator/generate_data.py @@ -186,6 +186,10 @@ def write_data( # Write the api_config.py file. write_code(Path(output) / "api_config.py", data.api_config.content, formatter) + # Write static files (if any). + for static_file in data.static_files: + write_code(Path(output) / f"{static_file.file_name}.py", static_file.content, formatter) + # Write the __init__.py file. write_code( Path(output) / "__init__.py", diff --git a/src/openapi_python_generator/language_converters/python/generator.py b/src/openapi_python_generator/language_converters/python/generator.py index 0760ff3..f90d125 100644 --- a/src/openapi_python_generator/language_converters/python/generator.py +++ b/src/openapi_python_generator/language_converters/python/generator.py @@ -14,6 +14,9 @@ from openapi_python_generator.language_converters.python.service_generator import ( generate_services, ) +from openapi_python_generator.language_converters.python.static_file_generator import ( + generate_static_files, +) from openapi_python_generator.models import ConversionResult, LibraryConfig # Type alias for both OpenAPI versions @@ -46,9 +49,11 @@ def generator( services = [] api_config = generate_api_config(data, env_token_name, pydantic_version) + static_files = generate_static_files() return ConversionResult( models=models, services=services, api_config=api_config, + static_files=static_files, ) diff --git a/src/openapi_python_generator/language_converters/python/static_file_generator.py b/src/openapi_python_generator/language_converters/python/static_file_generator.py new file mode 100644 index 0000000..4b63076 --- /dev/null +++ b/src/openapi_python_generator/language_converters/python/static_file_generator.py @@ -0,0 +1,14 @@ +"""Generator for static files that are copied to the generated client.""" +from typing import List + +from ...models import StaticFile + + +def generate_static_files() -> List[StaticFile]: + """ + Generate static files that will be copied to the generated client. + + Returns: + List of StaticFile objects containing file names and content. + """ + return [] diff --git a/src/openapi_python_generator/models.py b/src/openapi_python_generator/models.py index c599a4b..e7d37ce 100644 --- a/src/openapi_python_generator/models.py +++ b/src/openapi_python_generator/models.py @@ -94,7 +94,13 @@ class APIConfig(BaseModel): content: str +class StaticFile(BaseModel): + file_name: str + content: str + + class ConversionResult(BaseModel): models: List[Model] services: List[Service] api_config: APIConfig + static_files: List[StaticFile] = [] diff --git a/tests/test_static_files.py b/tests/test_static_files.py new file mode 100644 index 0000000..7a255db --- /dev/null +++ b/tests/test_static_files.py @@ -0,0 +1,72 @@ +"""Test static file generation and inclusion.""" +import tempfile +from pathlib import Path + +from openapi_python_generator.common import Formatter +from openapi_python_generator.generate_data import write_data +from openapi_python_generator.models import ConversionResult, APIConfig, StaticFile + + +def test_static_files_are_written(): + """Test that static files are written to the output directory.""" + # Create a minimal ConversionResult with static files + result = ConversionResult( + models=[], + services=[], + api_config=APIConfig( + file_name="api_config", + base_url="https://api.example.com", + content='class APIConfig:\n pass\n', + ), + static_files=[ + StaticFile( + file_name="errors", + content='"""Error types."""\nclass APIError(Exception):\n pass\n', + ), + StaticFile( + file_name="response", + content='"""Response wrapper."""\nclass DetailedResponse:\n pass\n', + ), + ], + ) + + # Write to temp directory + with tempfile.TemporaryDirectory() as tmpdir: + output_path = Path(tmpdir) / "generated" + write_data(result, output_path, Formatter.NONE) + + # Verify static files were created + assert (output_path / "errors.py").exists() + assert (output_path / "response.py").exists() + + # Verify content + errors_content = (output_path / "errors.py").read_text() + assert "class APIError" in errors_content + + response_content = (output_path / "response.py").read_text() + assert "class DetailedResponse" in response_content + + +def test_empty_static_files(): + """Test that empty static files list doesn't cause issues.""" + result = ConversionResult( + models=[], + services=[], + api_config=APIConfig( + file_name="api_config", + base_url="https://api.example.com", + content='class APIConfig:\n pass\n', + ), + static_files=[], # Empty list + ) + + # Write to temp directory + with tempfile.TemporaryDirectory() as tmpdir: + output_path = Path(tmpdir) / "generated" + write_data(result, output_path, Formatter.NONE) + + # Verify basic structure still created + assert (output_path / "api_config.py").exists() + assert (output_path / "__init__.py").exists() + assert (output_path / "models").is_dir() + assert (output_path / "services").is_dir() From f54fbbf8bd68b58113dfec7df4e93d1d2d95bcb5 Mon Sep 17 00:00:00 2001 From: Doug Borg Date: Sun, 26 Oct 2025 22:09:36 -0600 Subject: [PATCH 2/2] Fix Black formatting issues --- src/openapi_python_generator/generate_data.py | 4 +++- .../language_converters/python/static_file_generator.py | 1 + tests/test_static_files.py | 5 +++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/openapi_python_generator/generate_data.py b/src/openapi_python_generator/generate_data.py index 3f5fdfd..519a4f6 100644 --- a/src/openapi_python_generator/generate_data.py +++ b/src/openapi_python_generator/generate_data.py @@ -188,7 +188,9 @@ def write_data( # Write static files (if any). for static_file in data.static_files: - write_code(Path(output) / f"{static_file.file_name}.py", static_file.content, formatter) + write_code( + Path(output) / f"{static_file.file_name}.py", static_file.content, formatter + ) # Write the __init__.py file. write_code( diff --git a/src/openapi_python_generator/language_converters/python/static_file_generator.py b/src/openapi_python_generator/language_converters/python/static_file_generator.py index 4b63076..43665df 100644 --- a/src/openapi_python_generator/language_converters/python/static_file_generator.py +++ b/src/openapi_python_generator/language_converters/python/static_file_generator.py @@ -1,4 +1,5 @@ """Generator for static files that are copied to the generated client.""" + from typing import List from ...models import StaticFile diff --git a/tests/test_static_files.py b/tests/test_static_files.py index 7a255db..d7f0ede 100644 --- a/tests/test_static_files.py +++ b/tests/test_static_files.py @@ -1,4 +1,5 @@ """Test static file generation and inclusion.""" + import tempfile from pathlib import Path @@ -16,7 +17,7 @@ def test_static_files_are_written(): api_config=APIConfig( file_name="api_config", base_url="https://api.example.com", - content='class APIConfig:\n pass\n', + content="class APIConfig:\n pass\n", ), static_files=[ StaticFile( @@ -55,7 +56,7 @@ def test_empty_static_files(): api_config=APIConfig( file_name="api_config", base_url="https://api.example.com", - content='class APIConfig:\n pass\n', + content="class APIConfig:\n pass\n", ), static_files=[], # Empty list )