Source code for pypresscart.models._common

"""Shared enums and generic pagination envelope."""

from __future__ import annotations

from collections.abc import Sequence
from enum import Enum
from typing import Any, Generic, TypeVar

from pydantic import BaseModel, ConfigDict, Field, model_validator

T = TypeVar("T")


[docs] class ChannelType(str, Enum): WEBSITE = "WEBSITE" NEWSLETTER = "NEWSLETTER" INSTAGRAM = "INSTAGRAM" LINKEDIN = "LINKEDIN" YOUTUBE = "YOUTUBE" TIKTOK = "TIKTOK" TWITTER_X = "TWITTER_X" PODCAST = "PODCAST" OTHER = "OTHER"
[docs] class PlacementType(str, Enum): FULL_FEATURE = "FULL_FEATURE" PRESS_RELEASE = "PRESS_RELEASE" MENTION = "MENTION" QUOTE = "QUOTE" LISTICLE = "LISTICLE"
[docs] class OutletStatus(str, Enum): ACTIVE = "ACTIVE" INACTIVE = "INACTIVE" DRAFT = "DRAFT" PENDING_REVIEW = "PENDING_REVIEW" PENDING_AGREEMENT = "PENDING_AGREEMENT" REJECTED = "REJECTED" ARCHIVED = "ARCHIVED" SUSPENDED = "SUSPENDED"
[docs] class TokenType(str, Enum): FULL_ACCESS = "full_access" CUSTOM = "custom" READ_ONLY = "read_only"
[docs] class SortOrder(str, Enum): ASC = "asc" DESC = "desc"
[docs] class PresscartModel(BaseModel): """Base for all models in this package. Permissive on unknown fields for forward compat.""" model_config = ConfigDict(extra="allow", populate_by_name=True) @model_validator(mode="before") @classmethod def _coerce_empty_strings(cls, data: Any) -> Any: """Normalize empty strings to ``None`` before type coercion. The Presscart API occasionally returns ``""`` instead of ``null`` for fields that are semantically optional bools, numbers, or datetimes. Pydantic's strict parsers reject that; treating it as ``None`` is the pragmatic choice since no endpoint documents ``""`` as meaningful. """ if not isinstance(data, dict): return data return {k: (None if isinstance(v, str) and v == "" else v) for k, v in data.items()}
[docs] class Paginated(PresscartModel, Generic[T]): """Standard Presscart paginated envelope.""" records: Sequence[T] = Field(default_factory=list) total_records: int | None = None total_pages: int | None = None current_page: int | None = None next_page: int | None = None previous_page: int | None = None
[docs] class Tag(PresscartModel): name: str
[docs] class Price(PresscartModel): unit_amount: float currency: str | None = None pricing_tier: str | None = None
[docs] class IncludeItem(PresscartModel): channel_type: ChannelType | str | None = None placement_type: PlacementType | str | None = None
[docs] class Disclaimer(PresscartModel): id: str | None = None name: str | None = None description: str | None = None
def serialize_filters(prefix: str, values: dict[str, Any] | None) -> dict[str, Any]: """Turn a dict into ``prefix[key]=value`` query params (server's expected shape). List values are emitted as ``prefix[key][0]=v1&prefix[key][1]=v2`` (qs indexed-bracket notation) — the Presscart API requires that form for array filters like ``channel_types``, ``tags``, ``product_ids``; the bare ``[]`` form is silently ignored by the server. """ if not values: return {} out: dict[str, Any] = {} for key, val in values.items(): if val is None: continue if isinstance(val, (list, tuple)): for i, item in enumerate(val): out[f"{prefix}[{key}][{i}]"] = item else: out[f"{prefix}[{key}]"] = val return out __all__ = [ "ChannelType", "Disclaimer", "IncludeItem", "OutletStatus", "Paginated", "PlacementType", "PresscartModel", "Price", "SortOrder", "Tag", "TokenType", "serialize_filters", ]