CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-openapi-python-client

Generate modern Python clients from OpenAPI 3.0 and 3.1 documents

Pending
Overview
Eval results
Files

templates.mddocs/

Template System

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.

Capabilities

Template Environment

The core template system built on Jinja2 with custom filters and globals.

class Project:
    env: Environment  # Jinja2 environment with custom configuration
    
    def __init__(
        self,
        *,
        openapi: GeneratorData,
        config: Config,
        custom_template_path: Optional[Path] = None,
    ) -> None:
        """
        Initialize project with template environment.
        
        The environment includes:
        - Custom template filters (snakecase, kebabcase, pascalcase, any)
        - Global variables (config, utils, openapi data, etc.)
        - Template loader with optional custom template override
        
        Parameters:
        - openapi: Parsed OpenAPI specification data
        - config: Generation configuration
        - custom_template_path: Optional directory containing custom templates
        """

Template Filters

Built-in filters for string transformation and formatting.

# Internal constant used by the template system
TEMPLATE_FILTERS = {
    "snakecase": utils.snake_case,     # Convert to snake_case
    "kebabcase": utils.kebab_case,     # Convert to kebab-case  
    "pascalcase": utils.pascal_case,   # Convert to PascalCase
    "any": any,                        # Python any() function
}

Template Globals

Global variables and functions available in all templates.

# Template globals include:
config: Config                              # Generation configuration
utils: module                              # Utility functions module
python_identifier: Callable               # Create valid Python identifiers
class_name: Callable                       # Create valid class names
package_name: str                          # Generated package name
package_dir: Path                          # Package directory path
package_description: str                   # Package description
package_version: str                       # Package version
project_name: str                          # Project name
project_dir: Path                          # Project directory path
openapi: GeneratorData                     # Parsed OpenAPI data
endpoint_collections_by_tag: dict         # Endpoints grouped by tag

Code Generation Pipeline

The template system generates code through a structured pipeline.

class Project:
    def build(self) -> Sequence[GeneratorError]:
        """
        Create the project from templates using this pipeline:
        
        1. _create_package() - Generate package structure and __init__.py
        2. _build_metadata() - Generate project metadata files
        3. _build_models() - Generate data model classes
        4. _build_api() - Generate API endpoint functions and client classes
        5. _run_post_hooks() - Execute post-generation commands
        
        Returns:
        List of errors encountered during generation
        """

Template Structure

Default Templates

The built-in templates generate a complete Python client structure:

templates/
├── package_init.py.jinja          # Package __init__.py
├── types.py.jinja                 # Type definitions
├── client.py.jinja                # HTTP client classes
├── errors.py.jinja                # Error definitions
├── model.py.jinja                 # Individual model classes
├── str_enum.py.jinja              # String enum classes
├── int_enum.py.jinja              # Integer enum classes
├── literal_enum.py.jinja          # Literal enum types
├── models_init.py.jinja           # Models package __init__.py
├── api_init.py.jinja              # API package __init__.py
├── endpoint_module.py.jinja       # API endpoint functions
├── endpoint_init.py.jinja         # Tag-specific API __init__.py
├── pyproject.toml.jinja           # Project metadata
├── setup.py.jinja                 # Setup.py metadata
├── README.md.jinja                # Generated documentation
└── .gitignore.jinja               # Git ignore rules

Custom Templates

Override any default template by creating a custom template directory:

custom-templates/
├── client.py.jinja                # Custom client implementation
├── model.py.jinja                 # Custom model generation
└── README.md.jinja                # Custom documentation

Template Variables

OpenAPI Data Access

Templates have access to the complete parsed OpenAPI specification:

{# Access OpenAPI metadata #}
{{ openapi.title }}
{{ openapi.version }}
{{ openapi.description }}

{# Access models and enums #}
{% for model in openapi.models %}
  {{ model.class_info.name }}
  {{ model.class_info.module_name }}
{% endfor %}

{% for enum in openapi.enums %}
  {{ enum.class_info.name }}
  {{ enum.value_type }}
{% endfor %}

{# Access API endpoints #}
{% for tag, collection in endpoint_collections_by_tag.items() %}
  {% for endpoint in collection.endpoints %}
    {{ endpoint.name }}
    {{ endpoint.method }}
    {{ endpoint.path }}
  {% endfor %}
{% endfor %}

Configuration Access

All configuration options are available in templates:

{# Project configuration #}
{{ config.project_name_override }}
{{ config.package_name_override }}
{{ config.field_prefix }}

{# Generation options #}
{% if config.docstrings_on_attributes %}
  # Generate attribute docstrings
{% endif %}

{% if config.literal_enums %}
  # Use literal types
{% endif %}

Utility Functions

Access utility functions for string manipulation:

{# String transformations #}
{{ "MyClassName" | snakecase }}        {# -> my_class_name #}
{{ "my_function" | pascalcase }}       {# -> MyFunction #}
{{ "some-value" | kebabcase }}         {# -> some-value #}

{# Python identifier creation #}
{{ python_identifier("field name", config.field_prefix) }}
{{ class_name("model-name", config.field_prefix) }}

Custom Template Usage

Creating Custom Templates

  1. Create a directory for your custom templates
  2. Copy default templates you want to modify
  3. Customize the templates using Jinja2 syntax
  4. Use the custom template path in generation
# Copy default templates
cp -r /path/to/openapi_python_client/templates ./my-templates

# Modify templates as needed
vim ./my-templates/client.py.jinja

# Use custom templates
openapi-python-client generate \
  --url https://api.example.com/openapi.json \
  --custom-template-path ./my-templates

Example Custom Client Template

{# custom-templates/client.py.jinja #}
"""Custom HTTP client implementation."""

from typing import Dict, Optional, Union
import httpx

class CustomClient:
    """Custom client with enhanced features."""
    
    def __init__(
        self,
        base_url: str,
        *,
        cookies: Optional[Dict[str, str]] = None,
        headers: Optional[Dict[str, str]] = None,
        timeout: float = 30.0,
        verify_ssl: bool = True,
        follow_redirects: bool = False,
        custom_auth: Optional[str] = None,  # Custom authentication
    ) -> None:
        self._base_url = base_url
        self._cookies = cookies
        self._headers = headers or {}
        
        # Add custom authentication header
        if custom_auth:
            self._headers["X-Custom-Auth"] = custom_auth
            
        self._client = httpx.Client(
            base_url=base_url,
            cookies=cookies,
            headers=self._headers,
            timeout=timeout,
            verify=verify_ssl,
            follow_redirects=follow_redirects,
        )

    def get_headers(self) -> Dict[str, str]:
        """Get current headers."""
        return self._client.headers.copy()

    def close(self) -> None:
        """Close the underlying client."""
        self._client.close()
        
    def __enter__(self) -> "CustomClient":
        return self
        
    def __exit__(self, *args) -> None:
        self.close()

Template Development Tips

  1. Use template inheritance for common patterns:
{# base_model.jinja #}
{% macro model_docstring(model) %}
"""{{ model.description | default("Generated model class.") }}"""
{% endmacro %}

{# model.py.jinja #}
{% from "base_model.jinja" import model_docstring %}

class {{ model.class_info.name }}:
    {{ model_docstring(model) }}
    # ... rest of model
  1. Add custom filters programmatically:
from openapi_python_client import Project

def custom_filter(value):
    return value.upper().replace(" ", "_")

project = Project(openapi=data, config=config)
project.env.filters["custom"] = custom_filter
  1. Test templates with sample data:
from jinja2 import Environment, FileSystemLoader

env = Environment(loader=FileSystemLoader("./my-templates"))
template = env.get_template("model.py.jinja")
result = template.render(model=sample_model, config=sample_config)

Template Best Practices

Code Quality

  • Format generated code: Use post-hooks for consistent formatting
  • Type annotations: Always include complete type annotations
  • Documentation: Generate comprehensive docstrings
  • Error handling: Include proper error handling patterns

Maintainability

  • Modular templates: Break complex templates into smaller, reusable components
  • Version compatibility: Test templates with different OpenAPI versions
  • Configuration driven: Use configuration options rather than hardcoding values
  • Default fallbacks: Provide sensible defaults for missing data

Performance

  • Minimize template complexity: Keep template logic simple
  • Cache static content: Avoid regenerating static template parts
  • Efficient imports: Generate only necessary imports
  • Lazy loading: Use lazy imports where appropriate

Install with Tessl CLI

npx tessl i tessl/pypi-openapi-python-client

docs

cli-interface.md

configuration.md

index.md

programmatic-api.md

templates.md

tile.json