from pydantic import (
BaseModel, model_validator, HttpUrl, computed_field, field_validator, ConfigDict, Field
)
from typing import List, Union, Optional, Literal, Annotated
RussianStr = Annotated[str, Field(pattern=r'^[А-Яа-яЁё\s\-0-9]+$')]
[документация]
class UserSpec(BaseModel):
"""
Модель профиля пользователя.
Атрибуты:
- user_id (int): Идентификатор пользователя.
- username (str): Имя пользователя, состоящее из русских букв, пробелов, дефисов и цифр.
- surname (str): Фамилия пользователя, состоящая из русских букв, пробелов, дефисов и цифр.
- second_name (str, optional): Отчество пользователя, опционально.
- email (str): Электронная почта пользователя, должна содержать '@' и '.'.
- status (Literal['active', 'non-active']): Статус пользователя, может быть 'active' или 'non-active'.
Конфигурация модели:
- `extra='forbid'`: Запрещает наличие дополнительных полей, не указанных в модели.
Валидаторы:
- `validate_email`: Проверяет, что email содержит символы '@' и '.'.
- username и surname должны состоять только из русских букв, пробелов, дефисов и цифр.
- second_name может быть None, если не указано.
- status должен быть либо 'active', либо 'non-active'.
Пример использования:
>>> user = UserSpec(
... user_id=1,
... username='Андрей',
... surname='Савватеев',
... second_name='Эдуардович',
... email='deez@nu.ts',
... status='active'
... )
>>> print(user)
user_id=1 username='Андрей' surname='Савватеев' second_name='Эдуардович' email='deez@nu.ts' status='active'
"""
model_config = ConfigDict(extra='forbid')
user_id: int
username: RussianStr
surname: RussianStr
second_name: Optional[str] = None
email: str
status: Literal['active', 'non-active']
[документация]
@field_validator('email', mode='after')
@classmethod
def validate_email(cls, value):
if '@' not in value or '.' not in value:
raise ValueError("Email must contain '@' and '.'")
return value
[документация]
class ProfileSpec(UserSpec):
"""Модель профиля пользователя с дополнительными полями.
Наследуется от UserSpec(user_id, username, surname, second_name, email, status) и добавляет поля `bio` и `url`.
Атрибуты:
- bio (RussianStr): Краткая биография пользователя, состоящая из русских букв, пробелов, дефисов и цифр.
- url (HttpUrl): URL пользователя, должен содержать '://'.
Конфигурация модели:
- `extra='forbid'`: Запрещает наличие дополнительных полей, не указанных в модели.
Валидаторы:
- `validate_url`: Проверяет, что URL содержит '://'.
- `validate_bio`: Проверяет, что биография состоит только из русских букв, пробелов, дефисов и цифр.
Пример использования:
>>> profile = ProfileSpec(
... user_id=1,
... username='Андрей',
... surname='Савватеев',
... second_name='Эдуардович',
... email='deez@nu.ts',
... status='active',
... bio='Программист и разработчик',
... url='https://example.com/profile'
... )
>>> print(profile)
user_id=1 username='Андрей' surname='Савватеев' second_name='Эдуардович' email='deez@nu.ts' status='active' bio='Программист и разработчик' url='https://example.com/profile'
"""
bio: RussianStr
url: HttpUrl
[документация]
@field_validator('url', mode='after')
@classmethod
def validate_url(cls, value):
if '://' not in str(value):
raise ValueError("URL must contain '://'")
return value
[документация]
class ItemSpec(BaseModel):
"""
Модель спецификации товара.
Атрибуты:
- item_id (int): Идентификатор товара.
- name (str): Название товара, состоящее из русских букв, пробелов, дефисов и цифр.
- desc (str): Описание товара, состоящее из русских букв, пробелов, дефисов и цифр.
- price (float): Цена товара, должна быть больше 0.
Конфигурация модели:
- `extra='forbid'`: Запрещает наличие дополнительных полей, не указанных в модели.
Валидаторы:
- `validate_price`: Проверяет, что цена товара больше 0.
- name и desc должны состоять только из русских букв, пробелов, дефисов и цифр.
Пример использования:
>>> item = ItemSpec(
... item_id=1,
... name='Товар 1',
... desc='Описание товара 1',
... price=100.0
... )
>>> print(item)
item_id=1 name='Товар 1' desc='Описание товара 1' price=100.0
"""
model_config = {
"extra": "forbid"
}
item_id: int
name: RussianStr
desc: RussianStr
price: float
[документация]
@field_validator("price", mode="after")
@classmethod
def validate_price(cls, value: float) -> float:
if value <= 0:
raise ValueError("Price must be greater than 0")
return value
[документация]
class ServiceSpec(BaseModel):
"""
Модель услуги, которую предоставляет магазин.
Атрибуты:
- service_id (int): Идентификатор услуги.
- name (str): Название услуги, состоящее из русских букв, пробелов, дефисов и цифр.
- desc (str): Описание услуги, состоящее из русских букв, пробелов, дефисов и цифр.
- price (float): Цена услуги, должна быть больше 0.
Конфигурация модели:
- `extra='forbid'`: Запрещает наличие дополнительных полей, не указанных в модели.
Валидаторы:
- `validate_price`: Проверяет, что цена услуги больше 0.
- name и desc должны состоять только из русских букв, пробелов, дефисов и цифр.
Пример использования:
>>> service = ServiceSpec(
... service_id=1,
... name='Услуга 1',
... desc='Описание услуги 1',
... price=100.0
... )
>>> print(service)
service_id=1 name='Услуга 1' desc='Описание услуги 1' price=100.0
"""
model_config = {
"extra": "forbid",
"populate_by_name": True
}
service_id: int
name: RussianStr
desc: RussianStr
price: float
[документация]
@field_validator('price', mode='after')
@classmethod
def validate_price(cls, value):
if value <= 0:
raise ValueError("Price must be greater than 0")
return value
[документация]
class OrderLineSpec(BaseModel):
"""
Модель строки заказа, которая содержит информацию о товаре или услуге в заказе.
Атрибуты:
- order_id (int): Идентификатор заказа.
- order_line_id (int): Идентификатор строки заказа, должен быть больше 0 и меньше или равен order_id.
- item_line (Union[ServiceSpec, ItemSpec]): Товар или услуга, которая входит в строку заказа.
- quantity (float): Количество товара или услуги в строке заказа, должно быть больше 0.
Конфигурация модели:
- `extra='forbid'`: Запрещает наличие дополнительных полей, не указанных в модели.
Валидаторы:
- `validate_quantity`: Проверяет, что количество больше 0.
- `validate_order_line_id`: Проверяет, что order_line_id больше 0 и меньше или равен order_id.
- `line_price`: Вычисляет цену строки заказа как произведение quantity и цены товара или услуги.
Пример использования:
>>> item = ItemSpec(item_id=1, name='Товар 1', desc='Описание товара 1', price=100.0)
>>> order_line = OrderLineSpec(
... order_id=1,
... order_line_id=1,
... item_line=item,
... quantity=2.0
... )
>>> print(order_line)
order_id=1 order_line_id=1 item_line=ItemSpec(item_id=1, name='Товар 1', desc='Описание товара 1', price=100.0) quantity=2.0 line_price=200.0
"""
model_config = {
"extra": "forbid"
}
order_id: int
order_line_id: int
item_line: Union[ServiceSpec, ItemSpec]
quantity: float
[документация]
@field_validator('quantity', mode='after')
@classmethod
def validate_quantity(cls, value):
if value <= 0:
raise ValueError("Quantity must be greater than 0")
return value
[документация]
@model_validator(mode='after')
def validate_order_line_id(self):
if self.order_line_id <= 0 or self.order_line_id > self.order_id:
raise ValueError("Wrong order line ID")
return self
@computed_field
def line_price(self) -> float:
return self.quantity * self.item_line.price
[документация]
class OrderSpec(BaseModel):
""""
Модель заказа, которая содержит информацию о заказе пользователя.
Атрибуты:
- order_id (int): Идентификатор заказа.
- user_info (ProfileSpec): Профиль пользователя, который сделал заказ.
- items_line (List[OrderLineSpec]): Список строк заказа, каждая из которых содержит информацию о товаре или услуге.
Конфигурация модели:
- `extra='forbid'`: Запрещает наличие дополнительных полей, не указанных в модели.
Валидаторы:
- `validate_order_id`: Проверяет, что order_id больше 0.
- `validate_items_line`: Проверяет, что items_line не пустой и содержит только строки заказа с корректными order_id и order_line_id.
Пример использования:
>>> user_profile = ProfileSpec(
... user_id=1,
... username='Андрей',
... surname='Савватеев',
... second_name='Эдуардович',
... email='andrey.savvateev@example.com',
... status='active',
... bio='Программист и разработчик',
... url='https://example.com/profile'
... )
>>> item = ItemSpec(item_id=1, name='Товар 1', desc='Описание товара 1', price=100.0)
>>> order_line = OrderLineSpec(
... order_id=1,
... order_line_id=1,
... item_line=item,
... quantity=2.0
... )
>>> order = OrderSpec(
... order_id=1,
... user_info=user_profile,
... items_line=[order_line]
... )
>>> print(order)
order_id=1 user_info=ProfileSpec(user_id=1, username='Андрей', surname='Савватеев', second_name='Эдуардович', email='andrey.savvateev@example.com', status='active', bio='Программист и разработчик', url='https://example.com/profile') items_line=[OrderLineSpec(order_id=1, order_line_id=1, item_line=ItemSpec(item_id=1, name='Товар 1', desc='Описание товара 1', price=100.0), quantity=2.0)]
"""
model_config = {
"extra": "forbid"
}
order_id: int
user_info: ProfileSpec
items_line: List[OrderLineSpec]