OpenAPI Generator Customization — Extending Generated Code with Custom Templates and Logic
In this tutorial, you will learn about OpenAPI Generator Customization. We cover key concepts, practical examples, and best practices to help you master this topic.
OpenAPI Generator customization allows you to modify generated code with custom Mustache templates, post-generation scripts, custom generators, and custom libraries to match your project's conventions and requirements.
What You'll Learn
- Custom Mustache template overrides
- Post-generation hooks for code modification
- Building custom generators for unsupported frameworks
- Custom libraries for shared patterns
- Template variables and context API
Why It Matters
Default generated code rarely matches your project's conventions. Customization ensures consistency, reduces manual cleanup, and adds project-specific features. DodaTech's generators produce code with custom logging, metrics, retry logic, and error handling — all through template overrides.
Real-World Use
A team generates Python SDKs but needs specific customizations: retry logic on every API call, structured logging with correlation IDs, and custom error types. Instead of editing generated code, they override the API client template to include these patterns automatically.
flowchart TD
A["OpenAPI Spec"] --> B["Default Generator"]
B --> C["Default Templates"]
C --> D["Generated Code"]
D -.->|"Manual edits lost on regeneration"| E["❌ Fragile"]
A --> F["Custom Generator"]
G["Custom Templates"] --> F
H["Post-gen Hooks"] --> F
I["Custom Library"] --> F
F --> J["Custom Generated Code"]
J --> K["✅ Regeneration-safe"]
Code Examples
Example 1: Custom Template Override
# Custom template: templates/python/api_client.mustache
# Overrides the default Python API client to add logging
import logging
logger = logging.getLogger(__name__)
class ApiClient:
def __init__(self, configuration=None):
self.configuration = configuration or Configuration()
self.rest_client = RESTClientObject()
def request(self, method, url, **kwargs):
# Custom: add logging before request
logger.info(f"API Request: {method} {url}")
logger.debug(f"Headers: {kwargs.get('headers')}")
response = self.rest_client.request(method, url, **kwargs)
# Custom: add logging after response
logger.info(f"API Response: {response.status}")
logger.debug(f"Response body: {response.data[:500]}")
if response.status >= 400:
logger.error(f"API Error: {response.status} - {response.data}")
return response
# Usage: override the default template
# openapi-generator-cli generate \
# -i spec.yaml \
# -g python \
# -o /output \
# -t /custom/templates/python/
Example 2: Post-Generation Hook Script
#!/usr/bin/env python3
"""
Post-generation hook: process generated code after generation.
"""
import os
import re
import shutil
from pathlib import Path
def add_retry_decorator(client_dir):
"""Add retry logic to all API client methods."""
client_file = Path(client_dir) / 'api_client.py'
content = client_file.read_text()
# Add retry import
if 'from tenacity' not in content:
content = 'from tenacity import retry, stop_after_attempt, wait_exponential\n' + content
# Add retry decorator to request method
content = content.replace(
'def request(self, method, url, **kwargs):',
'@retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=2, max=10))\n def request(self, method, url, **kwargs):'
)
client_file.write_text(content)
print("Added retry decorator to API client")
def add_correlation_ids(sdk_dir):
"""Add correlation ID header to all requests."""
for py_file in Path(sdk_dir).rglob('*.py'):
content = py_file.read_text()
if 'correlation_id' in content:
continue
# Add correlation_id parameter to API methods
content = re.sub(
r'def (\w+)\(self,\s*',
r'def \1(self, correlation_id=None, ',
content
)
py_file.write_text(content)
print("Added correlation ID support")
def remove_generated_logo(sdk_dir):
"""Remove generated logo files."""
for logo in Path(sdk_dir).rglob('logo*'):
logo.unlink()
print(f"Removed {logo}")
# Run all hooks
add_retry_decorator('/tmp/sdks/python')
add_correlation_ids('/tmp/sdks/python')
remove_generated_logo('/tmp/sdks/python')
Example 3: Custom Generator for New Framework
from openapi_generator import DefaultGenerator
from openapi_generator.codegen import CodegenOperation, CodegenParameter
class CustomFastAPIGenerator(DefaultGenerator):
"""
Custom generator for FastAPI-style Python server code.
"""
def __init__(self):
super().__init__()
self.type_mappings = {
'integer': 'int',
'string': 'str',
'boolean': 'bool',
'number': 'float',
'array': 'List',
'object': 'Dict'
}
def generate_operation(self, operation: CodegenOperation) -> str:
"""Generate a FastAPI route handler."""
method = operation.http_method.lower()
path = operation.path
params = []
for param in operation.all_params:
python_type = self.type_mappings.get(param.data_type, 'Any')
if param.is_query:
params.append(f" {param.param_name}: {python_type} = Query(None)")
elif param.is_path:
params.append(f" {param.param_name}: {python_type}")
elif param.is_header:
params.append(f" {param.param_name}: {python_type} = Header(None)")
param_str = '\n'.join(params)
return f"""
@app.{method}('{path}')
async def {operation.operation_id}(
{param_str}
):
\"\"\"
{operation.summary or ''}
{operation.description or ''}
\"\"\"
# TODO: Implement handler
raise NotImplementedError()
"""
def generate(self, spec):
"""Generate FastAPI server code from spec."""
output = []
output.append("from fastapi import FastAPI, Query, Header")
output.append("from typing import List, Dict, Any, Optional")
output.append("")
output.append("app = FastAPI(title='Generated API', version='1.0.0')")
output.append("")
for path, methods in spec.get('paths', {}).items():
for method, operation_data in methods.items():
codegen_op = CodegenOperation(path, method, operation_data)
output.append(self.generate_operation(codegen_op))
return '\n'.join(output)
# Usage
generator = CustomFastAPIGenerator()
code = generator.generate(spec)
Common Mistakes
1. Editing Generated Code Directly
Generated code is ephemeral. Always customize through templates, hooks, or custom generators.
2. Overriding Too Many Templates
Override only the templates you need. Unnecessary overrides make upgrading the generator difficult.
3. Ignoring Template Context Variables
Templates have access to specific variables. Print all context variables before customizing a template.
4. Breaking Template Portability
Custom templates that hardcode framework-specific imports won't work across generator versions.
5. Not Testing Custom Generators
Custom generators need unit tests. Changes to the spec format can break custom generators silently.
Practice Questions
- How do you customize generated code without editing output?
- What is a post-generation hook?
- When should you build a custom generator?
- What are template context variables?
- How do you test custom templates?
Answers:
- Use custom Mustache templates (-t flag), post-generation hooks, or custom generators.
- A script that runs after generation to modify or augment the generated files automatically.
- When your target framework or language isn't supported, or when default generators don't match your conventions.
- Variables passed to Mustache templates containing codegen information (operations, models, parameters, etc.).
- Generate code with your custom templates, then run the generated code's test suite and verify the output structure.
Challenge: Create a custom template set for a Python generated SDK that adds structured logging, correlation ID support, and retry logic. Test that regeneration preserves all customizations.
FAQ
What's Next
Deepen your understanding with {{< ilink "OpenAPI" "Mustache Templates in OpenAPI Generator" }}, then build a {{< ilink "OpenAPI" "OpenAPI Generator Plugins" }} for reusable customizations.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro