Исходный код amocrm.models.common

from __future__ import annotations

import dataclasses
from dataclasses import dataclass, field
from typing import Any, ClassVar, TypeVar


[документация] class CustomFieldsMixin: """Mixin для типизированного доступа к кастомным полям AmoCRM. Предоставляет методы для чтения значений кастомных полей по их ID. Должен использоваться совместно с датаклассами, имеющими поле ``custom_fields_values: list[CustomFieldValue] | None``. """ custom_fields_values: list[CustomFieldValue] | None
[документация] def get_cf_raw(self, field_id: int) -> list[dict[str, Any]] | None: """Вернуть список значений кастомного поля по его ID или ``None``.""" if self.custom_fields_values is None: return None for cf in self.custom_fields_values: if cf.field_id == field_id: return cf.values return None
[документация] def get_cf_value(self, field_id: int) -> Any: """Вернуть первое значение поля (``values[0]["value"]``) или ``None``.""" values = self.get_cf_raw(field_id) if not values: return None return values[0].get("value")
[документация] def get_cf_values(self, field_id: int) -> list[Any]: """Вернуть все значения поля (``values[i]["value"]``).""" values = self.get_cf_raw(field_id) if not values: return [] return [v.get("value") for v in values]
[документация] def get_cf_enum_id(self, field_id: int) -> int | None: """Вернуть ``enum_id`` первого значения поля или ``None``.""" values = self.get_cf_raw(field_id) if not values: return None return values[0].get("enum_id")
[документация] def set_cf_value(self, field_id: int, value: Any) -> None: """Установить единственное значение кастомного поля.""" if self.custom_fields_values is None: self.custom_fields_values = [] for cf in self.custom_fields_values: if cf.field_id == field_id: cf.values = [] if value is None else [{"value": value}] return if value is not None: self.custom_fields_values.append( CustomFieldValue(field_id=field_id, values=[{"value": value}]) )
[документация] def set_cf_values(self, field_id: int, values: list[Any]) -> None: """Установить несколько значений кастомного поля (multiselect).""" if self.custom_fields_values is None: self.custom_fields_values = [] new_values = [{"value": v} for v in values] for cf in self.custom_fields_values: if cf.field_id == field_id: cf.values = new_values return self.custom_fields_values.append( CustomFieldValue(field_id=field_id, values=new_values) )
[документация] def set_cf_raw(self, field_id: int, values: list[dict[str, Any]] | None) -> None: """Установить raw-значения кастомного поля (например, smart_address).""" if self.custom_fields_values is None: self.custom_fields_values = [] for cf in self.custom_fields_values: if cf.field_id == field_id: cf.values = values if values is not None else [] return if values is not None: self.custom_fields_values.append( CustomFieldValue(field_id=field_id, values=values) )
[документация] @dataclass(kw_only=True) class Tag: """Тег сущности AmoCRM. Attributes: id: Идентификатор тега. name: Название тега. """ id: int | None = None name: str | None = None
[документация] @classmethod def from_dict(cls, data: dict[str, Any]) -> Tag: """Создать экземпляр из словаря API.""" return cls(id=data.get("id"), name=data.get("name"))
[документация] def to_dict(self) -> dict[str, Any]: """Сериализовать в словарь, исключая поля со значением ``None``.""" return {k: v for k, v in dataclasses.asdict(self).items() if v is not None}
[документация] @dataclass(kw_only=True) class CustomFieldValue: """Значение кастомного поля AmoCRM. Attributes: field_id: Идентификатор кастомного поля. values: Список значений поля (каждое — словарь с ключами ``value`` и опционально ``enum_id`` / ``enum_code``). """ field_id: int values: list[dict[str, Any]] = field(default_factory=list)
[документация] @classmethod def from_dict(cls, data: dict[str, Any]) -> CustomFieldValue: """Создать экземпляр из словаря API.""" return cls(field_id=data["field_id"], values=data.get("values", []))
[документация] def to_dict(self) -> dict[str, Any]: """Сериализовать в словарь для отправки в API.""" return {"field_id": self.field_id, "values": self.values}
_M = TypeVar("_M", bound="BaseModel")
[документация] class BaseModel(CustomFieldsMixin): """Базовый класс DTO-моделей AmoCRM с общей логикой ``from_dict``/``to_dict``. Подклассы должны быть датаклассами и определять ``_scalar_fields`` — кортеж имён полей, которые берутся напрямую из словаря API. """ _scalar_fields: ClassVar[tuple[str, ...]] = () tags: list[Tag] | None custom_fields_values: list[CustomFieldValue] | None
[документация] @classmethod def from_dict(cls: type[_M], data: dict[str, Any]) -> _M: """Создать экземпляр из словаря API AmoCRM.""" kwargs: dict[str, Any] = {k: data.get(k) for k in cls._scalar_fields} tags_raw = data.get("_embedded", {}).get("tags") cf_raw = data.get("custom_fields_values") if tags_raw is not None: kwargs["tags"] = [Tag.from_dict(t) for t in tags_raw] if cf_raw is not None: kwargs["custom_fields_values"] = [ CustomFieldValue.from_dict(cf) for cf in cf_raw ] return cls(**kwargs)
[документация] def to_dict(self) -> dict[str, Any]: """Сериализовать в словарь для API, исключая поля со значением ``None``.""" result: dict[str, Any] = { k: getattr(self, k) for k in self._scalar_fields if getattr(self, k) is not None } if self.tags is not None: result["tags"] = [t.to_dict() for t in self.tags] if self.custom_fields_values is not None: result["custom_fields_values"] = [ cf.to_dict() for cf in self.custom_fields_values ] return result