Дата: 2025-11-10
Версия: 1.0.0
Это цепочка генерации кода:
YAML описание → Jinja2 шаблон → Python код
(что нужно) (как создать) (готовый файл)
Пример за 30 секунд:
# user.yaml (ЧТО нужно)
entity: User
fields:
- name: email
type: string
- name: age
type: integer
↓ обрабатывается через
{# template.py.j2 (КАК создать) #}
class {{ entity }}(Base):
{% for field in fields %}
{{ field.name }} = Column({{ field.type }})
{% endfor %}
↓ генерируется
# user.py (ГОТОВЫЙ код)
class User(Base):
email = Column(String)
age = Column(Integer)
Это готовые форматы описания:
Каждый решает свою задачу, мы используем лучшее от каждого.
YAML - человекочитаемый формат данных.
# schemas/product.yaml
entity:
name: Product
table: products
description: "Товар в магазине"
fields:
id:
type: integer
primary_key: true
auto_increment: true
name:
type: string
max_length: 200
required: true
price:
type: integer
default: 0
min_value: 0
stock:
type: integer
default: 0
created_at:
type: datetime
auto_now_add: true
indexes:
- fields: [name]
- fields: [created_at]
api:
endpoints:
- method: GET
path: /products
- method: POST
path: /products
- method: GET
path: /products/{id}
Это декларативное описание:
- Вы говорите ЧТО нужно (entity Product с полями)
- НЕ говорите КАК это сделать (это работа генератора)
import yaml
# Загружаем YAML
with open('schemas/product.yaml') as f:
config = yaml.safe_load(f)
print(config)
# {
# 'entity': {'name': 'Product', 'table': 'products', ...},
# 'fields': {
# 'id': {'type': 'integer', 'primary_key': True, ...},
# 'name': {'type': 'string', 'max_length': 200, ...},
# ...
# },
# 'api': {...}
# }
Теперь у нас Python словарь с описанием.
Jinja2 - шаблонизатор (как f-strings, но мощнее).
{# templates/sqlalchemy_model.py.j2 #}
from sqlalchemy import Column, Integer, String, DateTime
from sqlalchemy.ext.declarative import declarative_base
from datetime import datetime
Base = declarative_base()
class {{ entity.name }}(Base):
"""{{ entity.description }}"""
__tablename__ = "{{ entity.table }}"
{% for field_name, field_config in fields.items() %}
{{ field_name }} = Column(
{% if field_config.type == 'integer' %}Integer{% endif %}
{% if field_config.type == 'string' %}String({{ field_config.max_length | default(255) }}){% endif %}
{% if field_config.type == 'datetime' %}DateTime{% endif %},
{% if field_config.primary_key %}primary_key=True,{% endif %}
{% if field_config.auto_increment %}autoincrement=True,{% endif %}
{% if field_config.required %}nullable=False,{% endif %}
{% if field_config.default is defined %}default={{ field_config.default }},{% endif %}
{% if field_config.auto_now_add %}default=datetime.utcnow,{% endif %}
)
{% endfor %}
def __repr__(self):
return f"<{{ entity.name }}(id={self.id})>"
Jinja2 синтаксис:
- {{ variable }} - вывод значения
- {% for item in items %} - цикл
- {% if condition %} - условие
- {{ value | default(100) }} - фильтры
from jinja2 import Environment, FileSystemLoader
# 1. Загружаем YAML
with open('schemas/product.yaml') as f:
config = yaml.safe_load(f)
# 2. Настраиваем Jinja2
env = Environment(loader=FileSystemLoader('templates'))
template = env.get_template('sqlalchemy_model.py.j2')
# 3. Рендерим шаблон с данными из YAML
generated_code = template.render(
entity=config['entity'],
fields=config['fields']
)
# 4. Сохраняем результат
with open('generated/models/product.py', 'w') as f:
f.write(generated_code)
print("✅ Модель сгенерирована!")
generated/models/product.py:
from sqlalchemy import Column, Integer, String, DateTime
from sqlalchemy.ext.declarative import declarative_base
from datetime import datetime
Base = declarative_base()
class Product(Base):
"""Товар в магазине"""
__tablename__ = "products"
id = Column(
Integer,
primary_key=True,
autoincrement=True,
)
name = Column(
String(200),
nullable=False,
)
price = Column(
Integer,
default=0,
)
stock = Column(
Integer,
default=0,
)
created_at = Column(
DateTime,
default=datetime.utcnow,
)
def __repr__(self):
return f"<Product(id={self.id})>"
Готово! Можно использовать:
from generated.models.product import Product
# Создание
product = Product(name="iPhone", price=50000, stock=10)
session.add(product)
session.commit()
# Запрос
products = session.query(Product).filter(Product.price > 10000).all()
Альтернативы:
# Без Jinja2 - сложнее читать
code = f"class {name}(Base):\n"
for field in fields:
code += f" {field['name']} = Column({field['type']})\n"
Проблемы:
- ❌ Трудно читать
- ❌ Сложно изменять
- ❌ Перемешан код и логика
class {{ name }}(Base):
{% for field in fields %}
{{ field.name }} = Column({{ field.type }})
{% endfor %}
Преимущества:
- ✅ Читается как обычный код
- ✅ Легко менять структуру
- ✅ Разделение логики и шаблона
Проблема: Каждый описывает API/модели по-своему.
Решение: Использовать общепринятые стандарты.
Что это: Формат описания REST API (JSON/YAML).
Кто использует: AWS, Google, Microsoft, Stripe, GitHub
Пример:
# openapi.yaml
openapi: 3.1.0
info:
title: Product API
version: 1.0.0
paths:
/products:
get:
summary: Список продуктов
parameters:
- name: limit
in: query
schema:
type: integer
default: 100
responses:
'200':
description: OK
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Product'
post:
summary: Создать продукт
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/ProductCreate'
responses:
'201':
description: Created
components:
schemas:
Product:
type: object
properties:
id:
type: integer
name:
type: string
price:
type: integer
required:
- id
- name
ProductCreate:
type: object
properties:
name:
type: string
minLength: 1
maxLength: 200
price:
type: integer
minimum: 0
required:
- name
Что можно сделать с OpenAPI:
# 1. Генерация кода
openapi-generator generate -i openapi.yaml -g python-fastapi
# Создаётся:
# - FastAPI endpoints
# - Pydantic models
# - Tests
# 2. Автодокументация
# Swagger UI автоматически создаёт интерактивную документацию
Для нашей платформы:
Мы можем генерировать OpenAPI из CIDL:
# product.cidl
entity: Product
api:
endpoints:
- method: GET
path: /products
↓ генератор
# openapi.yaml (автогенерируется)
openapi: 3.1.0
paths:
/products:
get: ...
Что это: DSL (Domain Specific Language) для описания БД моделей.
Кто использует: Node.js/TypeScript проекты
Пример:
// schema.prisma
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
model User {
id Int @id @default(autoincrement())
email String @unique
name String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
posts Post[]
@@index([email])
}
model Post {
id Int @id @default(autoincrement())
title String
content String?
published Boolean @default(false)
authorId Int
author User @relation(fields: [authorId], references: [id])
@@index([authorId])
}
Что генерируется:
$ prisma generate
# Создаётся TypeScript клиент:
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()
// Type-safe запросы!
const user = await prisma.user.create({
data: {
email: 'test@example.com',
name: 'John',
posts: {
create: [
{ title: 'Hello World', published: true }
]
}
},
include: {
posts: true // ← IDE автодополнение!
}
})
Синтаксис Prisma - САМЫЙ ЛАКОНИЧНЫЙ:
Сравните:
// Prisma (лаконично!)
model User {
id Int @id @default(autoincrement())
email String @unique
posts Post[]
}
vs
# SQLAlchemy (многословно)
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True, autoincrement=True)
email = Column(String, unique=True)
posts = relationship("Post", back_populates="author")
Для нашей платформы:
Мы берём синтаксис Prisma для CIDL:
# Вдохновлено Prisma, но в YAML
entity:
name: User
fields:
id:
type: integer
primary_key: true
auto_increment: true
email:
type: string
unique: true
relationships:
posts:
type: one_to_many
entity: Post
Что это: Язык описания типов данных + API.
Кто использует: Facebook, GitHub, Shopify, Airbnb
Пример:
# schema.graphql
type User {
id: ID!
email: String!
name: String!
posts: [Post!]!
createdAt: DateTime!
}
type Post {
id: ID!
title: String!
content: String
published: Boolean!
author: User!
}
type Query {
users(limit: Int = 100): [User!]!
user(id: ID!): User
posts(authorId: ID): [Post!]!
}
type Mutation {
createUser(email: String!, name: String!): User!
updateUser(id: ID!, email: String, name: String): User!
deleteUser(id: ID!): Boolean!
}
Что генерируется:
$ graphql-codegen
# Создаётся:
# - TypeScript типы
# - React hooks
# - Resolvers
// Автогенерированные типы
type User = {
id: string
email: string
name: string
posts: Post[]
createdAt: Date
}
// Автогенерированные hooks
const { data, loading } = useUsersQuery({
variables: { limit: 10 }
})
Для нашей платформы:
GraphQL схема может генерироваться из CIDL:
# product.cidl
entity: Product
fields:
id: integer
name: string
↓ генератор
# schema.graphql (автогенерируется)
type Product {
id: Int!
name: String!
}
# product.cidl - ЕДИНЫЙ источник правды
entity: Product
fields:
id: {type: integer, primary_key: true}
name: {type: string, max_length: 200}
price: {type: integer, min_value: 0}
api:
rest: true
graphql: true
CIDL (product.yaml)
│
┌──────────────────┼──────────────────┐
│ │ │
↓ ↓ ↓
SQLAlchemy Pydantic FastAPI
models.py schemas.py api.py
│ │ │
└──────────────────┼──────────────────┘
│
┌──────────────────┼──────────────────┐
│ │ │
↓ ↓ ↓
OpenAPI GraphQL Alembic
spec.yaml schema.graphql migration.py
# generator.py
class CIDLGenerator:
def __init__(self, cidl_file):
self.config = yaml.safe_load(open(cidl_file))
def generate_sqlalchemy(self):
"""CIDL → SQLAlchemy"""
template = jinja_env.get_template('sqlalchemy.py.j2')
return template.render(self.config)
def generate_pydantic(self):
"""CIDL → Pydantic"""
template = jinja_env.get_template('pydantic.py.j2')
return template.render(self.config)
def generate_fastapi(self):
"""CIDL → FastAPI"""
template = jinja_env.get_template('fastapi.py.j2')
return template.render(self.config)
def generate_openapi(self):
"""CIDL → OpenAPI"""
# Преобразуем CIDL в формат OpenAPI
openapi_spec = {
'openapi': '3.1.0',
'paths': self._convert_to_openapi_paths(),
'components': self._convert_to_openapi_schemas()
}
return yaml.dump(openapi_spec)
def generate_graphql(self):
"""CIDL → GraphQL"""
template = jinja_env.get_template('graphql.j2')
return template.render(self.config)
# Использование
generator = CIDLGenerator('product.cidl')
generator.generate_sqlalchemy() # → models/product.py
generator.generate_pydantic() # → schemas/product.py
generator.generate_fastapi() # → api/products.py
generator.generate_openapi() # → openapi.yaml
generator.generate_graphql() # → schema.graphql
# schemas/order.cidl
entity:
name: Order
table: orders
description: "Заказ пользователя"
fields:
id:
type: integer
primary_key: true
auto_increment: true
user_id:
type: integer
required: true
total:
type: decimal
precision: 10
scale: 2
default: 0.00
status:
type: enum
values: [pending, paid, shipped, delivered, cancelled]
default: pending
created_at:
type: datetime
auto_now_add: true
relationships:
user:
type: many_to_one
entity: User
foreign_key: user_id
items:
type: one_to_many
entity: OrderItem
foreign_key: order_id
api:
rest:
endpoints:
- method: GET
path: /orders
auth: required
- method: POST
path: /orders
auth: required
- method: GET
path: /orders/{id}
auth: required
graphql:
enabled: true
$ cifra-codegen generate schemas/order.cidl --output generated/
Generating...
✅ generated/models/order.py (SQLAlchemy)
✅ generated/schemas/order.py (Pydantic)
✅ generated/api/orders.py (FastAPI)
✅ generated/migrations/001_create_orders.py (Alembic)
✅ generated/graphql/order.graphql (GraphQL schema)
✅ generated/openapi/order.yaml (OpenAPI spec)
✅ generated/tests/test_order.py (pytest)
Done in 1.2s!
generated/models/order.py:
from sqlalchemy import Column, Integer, String, Numeric, DateTime, Enum
from sqlalchemy.orm import relationship
from datetime import datetime
class Order(Base):
"""Заказ пользователя"""
__tablename__ = "orders"
id = Column(Integer, primary_key=True, autoincrement=True)
user_id = Column(Integer, nullable=False)
total = Column(Numeric(10, 2), default=0.00)
status = Column(Enum('pending', 'paid', 'shipped', 'delivered', 'cancelled'), default='pending')
created_at = Column(DateTime, default=datetime.utcnow)
# Relationships
user = relationship("User", back_populates="orders")
items = relationship("OrderItem", back_populates="order")
generated/schemas/order.py:
from pydantic import BaseModel, Field
from decimal import Decimal
from datetime import datetime
from enum import Enum
class OrderStatus(str, Enum):
pending = "pending"
paid = "paid"
shipped = "shipped"
delivered = "delivered"
cancelled = "cancelled"
class OrderCreate(BaseModel):
user_id: int
total: Decimal = Field(default=Decimal("0.00"), decimal_places=2)
status: OrderStatus = OrderStatus.pending
class OrderResponse(BaseModel):
id: int
user_id: int
total: Decimal
status: OrderStatus
created_at: datetime
class Config:
orm_mode = True
generated/api/orders.py:
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from typing import List
router = APIRouter(prefix="/orders", tags=["orders"])
@router.get("", response_model=List[OrderResponse])
async def list_orders(
skip: int = 0,
limit: int = 100,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user)
):
"""Список заказов"""
orders = db.query(Order).offset(skip).limit(limit).all()
return orders
@router.post("", response_model=OrderResponse, status_code=201)
async def create_order(
data: OrderCreate,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user)
):
"""Создать заказ"""
order = Order(**data.dict())
db.add(order)
db.commit()
db.refresh(order)
return order
@router.get("/{id}", response_model=OrderResponse)
async def get_order(
id: int,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user)
):
"""Получить заказ"""
order = db.query(Order).filter(Order.id == id).first()
if not order:
raise HTTPException(status_code=404, detail="Order not found")
return order
generated/openapi/order.yaml:
openapi: 3.1.0
paths:
/orders:
get:
summary: Список заказов
security:
- bearerAuth: []
responses:
'200':
description: OK
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Order'
post:
summary: Создать заказ
security:
- bearerAuth: []
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/OrderCreate'
generated/graphql/order.graphql:
type Order {
id: Int!
userId: Int!
total: Float!
status: OrderStatus!
createdAt: DateTime!
user: User!
items: [OrderItem!]!
}
enum OrderStatus {
PENDING
PAID
SHIPPED
DELIVERED
CANCELLED
}
type Query {
orders(limit: Int = 100, offset: Int = 0): [Order!]!
order(id: Int!): Order
}
type Mutation {
createOrder(userId: Int!, total: Float, status: OrderStatus): Order!
}
# main.py
from fastapi import FastAPI
from generated.api.orders import router as orders_router
app = FastAPI()
app.include_router(orders_router)
# Готово! API работает:
# GET /orders
# POST /orders
# GET /orders/1
$ uvicorn main:app
# Автодокументация доступна:
# http://localhost:8000/docs (Swagger UI)
# http://localhost:8000/redoc
1. YAML - описание (ЧТО нужно)
↓
2. Python - парсинг (читаем YAML)
↓
3. Jinja2 - шаблоны (КАК создать)
↓
4. Generator - рендеринг (объединяем)
↓
5. Python код - результат (ГОТОВЫЙ файл)
CIDL (наш формат)
↓
Генераторы (YAML → Jinja2)
↓
Всё сразу:
- SQLAlchemy models
- Pydantic schemas
- FastAPI endpoints
- OpenAPI spec
- GraphQL schema
- Alembic migrations
- Tests
Один файл CIDL → 7 готовых файлов!
Версия: 1.0.0
Статус: Объяснение завершено