architect/_archive/2025-11-cleanup/platform-v2-cifra/05_DATA_LAYER.md

CIFRA — Слой данных

Версия: 2.0.0
Дата: 2025-11-10


Entity System

Entity — бизнес-объект (Contact, Deal, Order).

entities:
  Contact:
    fields:
      name: {type: string}
      email: {type: email}

Генерируется:

class Contact(Base):
    __tablename__ = 'contacts'

    id = Column(UUID, primary_key=True)
    name = Column(String(255))
    email = Column(String(255))

40+ типов полей

Базовые

string: {type: string, max_length: 255}
text: {type: text}
integer: {type: integer}
decimal: {type: decimal, precision: 10, scale: 2}
boolean: {type: boolean}

Специализированные

email: {type: email}
phone: {type: phone}
url: {type: url}
ip: {type: ip}
uuid: {type: uuid}
slug: {type: slug}

Даты

date: {type: date}
datetime: {type: datetime}
time: {type: time}
timestamp: {type: timestamp}

Деньги

money: {type: money, currency: USD}

Выбор

choice: {type: choice, choices: [option1, option2]}

Связи

reference: {type: reference, entity: Company}
many_to_many: {type: many_to_many, entity: Tag}

Файлы

file: {type: file}
image: {type: image}
video: {type: video}
document: {type: document}

JSON

json: {type: json}
json_schema: {type: json_schema, schema: {...}}

Database models (SQLAlchemy)

from sqlalchemy import Column, String, Integer
from sqlalchemy.dialects.postgresql import UUID
from cifra.db import Base

class Contact(Base):
    __tablename__ = 'contacts'

    id = Column(UUID(as_uuid=True), primary_key=True, default=uuid4)
    name = Column(String(255), nullable=False)
    email = Column(String(255), unique=True)
    age = Column(Integer)

    # Indexes
    __table_args__ = (
        Index('idx_contact_email', 'email'),
    )

Validation

fields:
  email:
    type: email
    required: true
    validators:
      - email_format
      - unique

  age:
    type: integer
    min: 0
    max: 150

  password:
    type: string
    min_length: 8
    validators:
      - strong_password

Генерируется:

from pydantic import BaseModel, EmailStr, validator

class ContactCreate(BaseModel):
    email: EmailStr
    age: int

    @validator('age')
    def validate_age(cls, v):
        if not 0 <= v <= 150:
            raise ValueError('Age must be 0-150')
        return v

Relationships

OneToOne

Contact:
  fields:
    profile: {type: one_to_one, entity: Profile}

OneToMany

Company:
  fields:
    contacts: {type: one_to_many, entity: Contact}

ManyToMany

Contact:
  fields:
    tags: {type: many_to_many, entity: Tag}

Генерируется:

class Contact(Base):
    # OneToOne
    profile = relationship('Profile', uselist=False, back_populates='contact')

    # ManyToOne
    company_id = Column(UUID, ForeignKey('companies.id'))
    company = relationship('Company', back_populates='contacts')

    # ManyToMany
    tags = relationship('Tag', secondary=contact_tags, back_populates='contacts')

Migrations

# Создать миграцию
cifra migrate create "add contact table"

# Применить
cifra migrate up

# Откатить
cifra migrate down

Следующий документ: SECURITY.md →