0
# Template System
1
2
Extensible Jinja2-based template system for customizing generated code structure, formatting, and content. The template system provides complete control over how OpenAPI specifications are transformed into Python client code.
3
4
## Capabilities
5
6
### Template Environment
7
8
The core template system built on Jinja2 with custom filters and globals.
9
10
```python { .api }
11
class Project:
12
env: Environment # Jinja2 environment with custom configuration
13
14
def __init__(
15
self,
16
*,
17
openapi: GeneratorData,
18
config: Config,
19
custom_template_path: Optional[Path] = None,
20
) -> None:
21
"""
22
Initialize project with template environment.
23
24
The environment includes:
25
- Custom template filters (snakecase, kebabcase, pascalcase, any)
26
- Global variables (config, utils, openapi data, etc.)
27
- Template loader with optional custom template override
28
29
Parameters:
30
- openapi: Parsed OpenAPI specification data
31
- config: Generation configuration
32
- custom_template_path: Optional directory containing custom templates
33
"""
34
```
35
36
### Template Filters
37
38
Built-in filters for string transformation and formatting.
39
40
```python { .api }
41
# Internal constant used by the template system
42
TEMPLATE_FILTERS = {
43
"snakecase": utils.snake_case, # Convert to snake_case
44
"kebabcase": utils.kebab_case, # Convert to kebab-case
45
"pascalcase": utils.pascal_case, # Convert to PascalCase
46
"any": any, # Python any() function
47
}
48
```
49
50
### Template Globals
51
52
Global variables and functions available in all templates.
53
54
```python { .api }
55
# Template globals include:
56
config: Config # Generation configuration
57
utils: module # Utility functions module
58
python_identifier: Callable # Create valid Python identifiers
59
class_name: Callable # Create valid class names
60
package_name: str # Generated package name
61
package_dir: Path # Package directory path
62
package_description: str # Package description
63
package_version: str # Package version
64
project_name: str # Project name
65
project_dir: Path # Project directory path
66
openapi: GeneratorData # Parsed OpenAPI data
67
endpoint_collections_by_tag: dict # Endpoints grouped by tag
68
```
69
70
### Code Generation Pipeline
71
72
The template system generates code through a structured pipeline.
73
74
```python { .api }
75
class Project:
76
def build(self) -> Sequence[GeneratorError]:
77
"""
78
Create the project from templates using this pipeline:
79
80
1. _create_package() - Generate package structure and __init__.py
81
2. _build_metadata() - Generate project metadata files
82
3. _build_models() - Generate data model classes
83
4. _build_api() - Generate API endpoint functions and client classes
84
5. _run_post_hooks() - Execute post-generation commands
85
86
Returns:
87
List of errors encountered during generation
88
"""
89
```
90
91
## Template Structure
92
93
### Default Templates
94
95
The built-in templates generate a complete Python client structure:
96
97
```
98
templates/
99
├── package_init.py.jinja # Package __init__.py
100
├── types.py.jinja # Type definitions
101
├── client.py.jinja # HTTP client classes
102
├── errors.py.jinja # Error definitions
103
├── model.py.jinja # Individual model classes
104
├── str_enum.py.jinja # String enum classes
105
├── int_enum.py.jinja # Integer enum classes
106
├── literal_enum.py.jinja # Literal enum types
107
├── models_init.py.jinja # Models package __init__.py
108
├── api_init.py.jinja # API package __init__.py
109
├── endpoint_module.py.jinja # API endpoint functions
110
├── endpoint_init.py.jinja # Tag-specific API __init__.py
111
├── pyproject.toml.jinja # Project metadata
112
├── setup.py.jinja # Setup.py metadata
113
├── README.md.jinja # Generated documentation
114
└── .gitignore.jinja # Git ignore rules
115
```
116
117
### Custom Templates
118
119
Override any default template by creating a custom template directory:
120
121
```
122
custom-templates/
123
├── client.py.jinja # Custom client implementation
124
├── model.py.jinja # Custom model generation
125
└── README.md.jinja # Custom documentation
126
```
127
128
## Template Variables
129
130
### OpenAPI Data Access
131
132
Templates have access to the complete parsed OpenAPI specification:
133
134
```jinja2
135
{# Access OpenAPI metadata #}
136
{{ openapi.title }}
137
{{ openapi.version }}
138
{{ openapi.description }}
139
140
{# Access models and enums #}
141
{% for model in openapi.models %}
142
{{ model.class_info.name }}
143
{{ model.class_info.module_name }}
144
{% endfor %}
145
146
{% for enum in openapi.enums %}
147
{{ enum.class_info.name }}
148
{{ enum.value_type }}
149
{% endfor %}
150
151
{# Access API endpoints #}
152
{% for tag, collection in endpoint_collections_by_tag.items() %}
153
{% for endpoint in collection.endpoints %}
154
{{ endpoint.name }}
155
{{ endpoint.method }}
156
{{ endpoint.path }}
157
{% endfor %}
158
{% endfor %}
159
```
160
161
### Configuration Access
162
163
All configuration options are available in templates:
164
165
```jinja2
166
{# Project configuration #}
167
{{ config.project_name_override }}
168
{{ config.package_name_override }}
169
{{ config.field_prefix }}
170
171
{# Generation options #}
172
{% if config.docstrings_on_attributes %}
173
# Generate attribute docstrings
174
{% endif %}
175
176
{% if config.literal_enums %}
177
# Use literal types
178
{% endif %}
179
```
180
181
### Utility Functions
182
183
Access utility functions for string manipulation:
184
185
```jinja2
186
{# String transformations #}
187
{{ "MyClassName" | snakecase }} {# -> my_class_name #}
188
{{ "my_function" | pascalcase }} {# -> MyFunction #}
189
{{ "some-value" | kebabcase }} {# -> some-value #}
190
191
{# Python identifier creation #}
192
{{ python_identifier("field name", config.field_prefix) }}
193
{{ class_name("model-name", config.field_prefix) }}
194
```
195
196
## Custom Template Usage
197
198
### Creating Custom Templates
199
200
1. Create a directory for your custom templates
201
2. Copy default templates you want to modify
202
3. Customize the templates using Jinja2 syntax
203
4. Use the custom template path in generation
204
205
```bash
206
# Copy default templates
207
cp -r /path/to/openapi_python_client/templates ./my-templates
208
209
# Modify templates as needed
210
vim ./my-templates/client.py.jinja
211
212
# Use custom templates
213
openapi-python-client generate \
214
--url https://api.example.com/openapi.json \
215
--custom-template-path ./my-templates
216
```
217
218
### Example Custom Client Template
219
220
```jinja2
221
{# custom-templates/client.py.jinja #}
222
"""Custom HTTP client implementation."""
223
224
from typing import Dict, Optional, Union
225
import httpx
226
227
class CustomClient:
228
"""Custom client with enhanced features."""
229
230
def __init__(
231
self,
232
base_url: str,
233
*,
234
cookies: Optional[Dict[str, str]] = None,
235
headers: Optional[Dict[str, str]] = None,
236
timeout: float = 30.0,
237
verify_ssl: bool = True,
238
follow_redirects: bool = False,
239
custom_auth: Optional[str] = None, # Custom authentication
240
) -> None:
241
self._base_url = base_url
242
self._cookies = cookies
243
self._headers = headers or {}
244
245
# Add custom authentication header
246
if custom_auth:
247
self._headers["X-Custom-Auth"] = custom_auth
248
249
self._client = httpx.Client(
250
base_url=base_url,
251
cookies=cookies,
252
headers=self._headers,
253
timeout=timeout,
254
verify=verify_ssl,
255
follow_redirects=follow_redirects,
256
)
257
258
def get_headers(self) -> Dict[str, str]:
259
"""Get current headers."""
260
return self._client.headers.copy()
261
262
def close(self) -> None:
263
"""Close the underlying client."""
264
self._client.close()
265
266
def __enter__(self) -> "CustomClient":
267
return self
268
269
def __exit__(self, *args) -> None:
270
self.close()
271
```
272
273
### Template Development Tips
274
275
1. **Use template inheritance** for common patterns:
276
```jinja2
277
{# base_model.jinja #}
278
{% macro model_docstring(model) %}
279
"""{{ model.description | default("Generated model class.") }}"""
280
{% endmacro %}
281
282
{# model.py.jinja #}
283
{% from "base_model.jinja" import model_docstring %}
284
285
class {{ model.class_info.name }}:
286
{{ model_docstring(model) }}
287
# ... rest of model
288
```
289
290
2. **Add custom filters** programmatically:
291
```python
292
from openapi_python_client import Project
293
294
def custom_filter(value):
295
return value.upper().replace(" ", "_")
296
297
project = Project(openapi=data, config=config)
298
project.env.filters["custom"] = custom_filter
299
```
300
301
3. **Test templates** with sample data:
302
```python
303
from jinja2 import Environment, FileSystemLoader
304
305
env = Environment(loader=FileSystemLoader("./my-templates"))
306
template = env.get_template("model.py.jinja")
307
result = template.render(model=sample_model, config=sample_config)
308
```
309
310
## Template Best Practices
311
312
### Code Quality
313
314
- **Format generated code**: Use post-hooks for consistent formatting
315
- **Type annotations**: Always include complete type annotations
316
- **Documentation**: Generate comprehensive docstrings
317
- **Error handling**: Include proper error handling patterns
318
319
### Maintainability
320
321
- **Modular templates**: Break complex templates into smaller, reusable components
322
- **Version compatibility**: Test templates with different OpenAPI versions
323
- **Configuration driven**: Use configuration options rather than hardcoding values
324
- **Default fallbacks**: Provide sensible defaults for missing data
325
326
### Performance
327
328
- **Minimize template complexity**: Keep template logic simple
329
- **Cache static content**: Avoid regenerating static template parts
330
- **Efficient imports**: Generate only necessary imports
331
- **Lazy loading**: Use lazy imports where appropriate