Schemas & Meta: FastAPI Ronin Schema Generation with Tortoise ORM¶
FastAPI Ronin offers a powerful schema generation system based on your Tortoise ORM models. The primary method for creating schemas is using the @decorators.schema decorator, which provides a simple and intuitive way to link Pydantic models to Tortoise models. For advanced use cases requiring fine-grained field control, you can use build_schema() with SchemaMeta classes as an additional method.
FastAPI Ronin extends the functionality of pydantic_model_creator from Tortoise ORM, providing more flexible and secure schema generation for FastAPI.
The config parameter uses PydanticMetaData, allowing you to apply all standard Tortoise options. For more details on available features and configurations, refer to the official Tortoise documentation.
Overview¶
FastAPI Ronin provides two main approaches for creating schemas:
- Decorator-based approach (
@decorators.schema) - Primary method for creating basic schemas. - Meta class approach (
build_schemawithSchemaMeta) - Additional method for advanced field control
The schema system consists of the following components:
@decorators.schema- Decorator for linking Pydantic models to Tortoise models (primary method)- SchemaMeta - Defines which fields to include/exclude (for advanced usage)
- build_schema() - Creates Pydantic models from Tortoise models with meta configuration (additional method)
- rebuild_schema() - Modifies existing schemas for different use cases
SchemaMeta Classes¶
SchemaMeta classes define the field configuration for your schemas:
from fastapi_ronin.schemas import SchemaMeta
class ProjectMeta(SchemaMeta):
include = (
'id',
'name',
'description',
'created_at',
'updated_at',
)
optional = ('description',)
SchemaMeta Attributes¶
| Attribute | Type | Description |
|---|---|---|
include |
Tuple[str, ...] |
Fields to include in the schema |
exclude |
Tuple[str, ...] |
Fields to exclude from the schema |
optional |
Tuple[str, ...] |
Fields that should be optional |
computed |
Tuple[str, ...] |
Computed/derived fields |
Best Practice: Use include instead of exclude
Always prefer include over exclude for better control when adding new fields to your models. This prevents accidentally exposing sensitive data when new fields are added.
Basic Schema Generation with Decorator (Primary Method)¶
The primary and recommended way to create schemas in FastAPI Ronin is using the @decorators.schema decorator. This approach is simple, straightforward, and works well for most use cases.
Simple Schema with Decorator¶
from tortoise import fields
from app.core.models import BaseModel
class Project(BaseModel):
name = fields.CharField(max_length=255)
company_id = fields.IntField()
from datetime import datetime
from tortoise.contrib.pydantic import PydanticModel
from fastapi_ronin import decorators
from app.domains.project.models import Project
class BaseModelSchema(PydanticModel):
id: int
created_at: datetime
updated_at: datetime
@decorators.schema(model=Project)
class ProjectCreateSchema(PydanticModel):
name: str
company_id: int
@decorators.schema(model=Project)
class ProjectReadSchema(BaseModelSchema, ProjectCreateSchema):
pass
Handling Relationships with Decorator¶
The decorator approach works seamlessly with relationships:
from tortoise import fields
from app.core.models import BaseModel
class Company(BaseModel):
name = fields.CharField(max_length=255)
class Project(BaseModel):
name = fields.CharField(max_length=255)
company_id = fields.IntField()
from datetime import datetime
from tortoise.contrib.pydantic import PydanticModel
from fastapi_ronin import decorators
from app.domains.company.models import Company
from app.domains.project.models import Project
class BaseModelSchema(PydanticModel):
id: int
created_at: datetime
updated_at: datetime
@decorators.schema(model=Company)
class CompanySchema(BaseModelSchema):
name: str
@decorators.schema(model=Project)
class ProjectCreateSchema(PydanticModel):
name: str
company_id: int
@decorators.schema(model=Project)
class ProjectReadSchema(BaseModelSchema, ProjectCreateSchema):
company: CompanySchema
Benefits of Decorator Approach¶
- ✅ Simple and intuitive syntax
- ✅ Direct control over schema fields
- ✅ Easy to understand and maintain
- ✅ Works well with inheritance
Advanced Schema Generation with Meta Classes (Additional Method)¶
When to Use Decorator vs Meta Classes
Use the decorator approach (@decorators.schema) for most cases. Use build_schema with SchemaMeta only for simple implementations with minimal nesting, as schema typing is not supported due to dynamic assembly.
Simple Schema with Meta Classes¶
from tortoise import fields
from app.core.models import BaseModel
class Project(BaseModel):
name = fields.CharField(max_length=255)
description = fields.TextField(null=True)
active = fields.BooleanField(default=True)
from app.core.models import BASE_FIELDS
from fastapi_ronin.schemas import SchemaMeta
class ProjectMeta(SchemaMeta):
include = (
*BASE_FIELDS, # id, created_at, updated_at
'name',
'description',
'active',
)
from fastapi_ronin.schemas import build_schema, rebuild_schema
from app.domains.project.models import Project
from app.domains.project.meta import ProjectMeta
# Generate read schema (includes all fields)
ProjectReadSchema = build_schema(Project, meta=ProjectMeta)
# Generate create schema (excludes readonly fields)
ProjectCreateSchema = rebuild_schema(
ProjectReadSchema,
exclude_readonly=True
)
Handling Relationships¶
Models with ForeignKey¶
class Project(BaseModel):
name = fields.CharField(max_length=255)
description = fields.TextField(null=True)
class Task(BaseModel):
name = fields.CharField(max_length=255)
completed = fields.BooleanField(default=False)
project = fields.ForeignKeyField('models.Project', related_name='tasks')
Meta Classes for Relationships¶
from fastapi_ronin.schemas import SchemaMeta, build_schema_meta
class ProjectMeta(SchemaMeta):
include = (
*BASE_FIELDS,
'name',
'description',
)
class TaskMeta(SchemaMeta):
include = (
*BASE_FIELDS,
'name',
'completed',
'project_id', # Include foreign key ID
)
# Create meta for nested schemas with relationships
def get_project_with_tasks_meta():
"""Project schema with embedded tasks"""
return build_schema_meta(
ProjectMeta,
('tasks', get_task_with_project_meta()),
)
def get_task_with_project_meta():
"""Task schema with embedded project data"""
return build_schema_meta(TaskMeta, ('project', ProjectMeta))
Schema Generation with Relationships¶
from fastapi_ronin.schemas import ConfigSchemaMeta, build_schema, rebuild_schema
# Simple schemas
ProjectReadSchema = build_schema(Project, meta=ProjectMeta)
TaskReadSchema = build_schema(Task, meta=get_task_with_project_meta())
# Detailed project schema with tasks (handles circular references)
ProjectDetailSchema = build_schema(
Project,
meta=get_project_with_tasks_meta(),
config=ConfigSchemaMeta(allow_cycles=True), # Handle circular references
)
# Create schemas (exclude readonly fields)
ProjectCreateSchema = rebuild_schema(ProjectReadSchema, exclude_readonly=True)
TaskCreateSchema = rebuild_schema(TaskReadSchema, exclude_readonly=True)
Schema Rebuilding¶
The rebuild_schema() function allows you to create variations of existing schemas:
# Original schema includes all fields
ProjectReadSchema = build_schema(Project, meta=ProjectMeta)
# Create schema excludes readonly fields like id, created_at, updated_at
ProjectCreateSchema = rebuild_schema(
ProjectReadSchema,
exclude_readonly=True
)
# Create schema with different meta
ProjectMinimalSchema = rebuild_schema(
ProjectReadSchema,
meta=MinimalProjectMeta,
name="ProjectMinimalSchema"
)
Configuration Options¶
ConfigSchemaMeta¶
Use ConfigSchemaMeta for advanced configuration:
from fastapi_ronin.schemas import ConfigSchemaMeta
# Allow circular references in relationships
config = ConfigSchemaMeta(allow_cycles=True)
# Custom configuration
config = ConfigSchemaMeta(
allow_cycles=True,
exclude=('internal_field',),
include=('custom_field',),
)
schema = build_schema(
Project,
meta=ProjectMeta,
config=config
)
Type Hints for IDE Support¶
Improve IDE support with type hints:
from typing import TYPE_CHECKING
from tortoise.contrib.pydantic import PydanticModel
# Runtime schema generation
ProjectSchema = build_schema(Project, meta=ProjectMeta)
# Type hints for IDE
if TYPE_CHECKING:
ProjectSchema = type('ProjectSchema', (Project, PydanticModel), {})
Best Practices¶
1. Use Base Field Sets¶
# Define common field sets
BASE_FIELDS = ('id', 'created_at', 'updated_at')
class ProjectMeta(SchemaMeta):
include = (
*BASE_FIELDS,
'name',
'description',
)
2. Prefer include over exclude¶
# ✅ Good: Explicit control over exposed fields
class ProjectMeta(SchemaMeta):
include = (
'id',
'name',
'description',
'created_at',
)
# ❌ Avoid: May accidentally expose new fields
class ProjectMeta(SchemaMeta):
exclude = ('password', 'secret_key')
3. Organize by Use Case¶
# Different schemas for different API responses
class ProjectMetas:
class List(SchemaMeta):
include = ('id', 'name', 'created_at')
class Detail(SchemaMeta):
include = ('id', 'name', 'description', 'created_at', 'updated_at')
class Create(SchemaMeta):
include = ('name', 'description')
# Use specific meta for different contexts
ProjectListSchema = build_schema(Project, meta=ProjectMetas.List)
ProjectDetailSchema = build_schema(Project, meta=ProjectMetas.Detail)
Common Patterns¶
API Response Schemas¶
# Different schemas for different API responses
ProjectListSchema = build_schema(Project, meta=ProjectListMeta) # Minimal fields
ProjectDetailSchema = build_schema(Project, meta=ProjectDetailMeta) # Full fields
ProjectCreateSchema = rebuild_schema(ProjectDetailSchema, exclude_readonly=True)
Schema Naming¶
# Explicit naming for better OpenAPI documentation
ProjectReadSchema = build_schema(
Project,
meta=ProjectMeta,
name="ProjectResponse"
)
ProjectCreateSchema = rebuild_schema(
ProjectReadSchema,
exclude_readonly=True,
name="ProjectCreateRequest"
)
The schema system in FastAPI Ronin provides the flexibility to create exactly the API schemas you need while maintaining clean separation between your data models and API contracts.