Source code for litestar_flags.models.flag

"""Feature flag model."""

from __future__ import annotations

from datetime import datetime
from typing import TYPE_CHECKING, Any
from uuid import UUID

from litestar_flags.models.base import HAS_ADVANCED_ALCHEMY
from litestar_flags.types import FlagStatus, FlagType

if TYPE_CHECKING:
    from litestar_flags.models.override import FlagOverride
    from litestar_flags.models.rule import FlagRule
    from litestar_flags.models.schedule import RolloutPhase, ScheduledFlagChange, TimeSchedule
    from litestar_flags.models.variant import FlagVariant

__all__ = ["FeatureFlag"]


if HAS_ADVANCED_ALCHEMY:
    from advanced_alchemy.base import UUIDv7AuditBase
    from sqlalchemy import JSON, Index
    from sqlalchemy.orm import Mapped, mapped_column, relationship

    class FeatureFlag(UUIDv7AuditBase):
        """Core feature flag model.

        Uses UUIDv7 for time-sortable IDs and automatic audit timestamps.

        Attributes:
            key: Unique identifier for the flag (used in code).
            name: Human-readable name for the flag.
            description: Optional description of the flag's purpose.
            flag_type: The type of value the flag returns.
            status: Current lifecycle status of the flag.
            default_enabled: Default boolean value when flag type is BOOLEAN.
            default_value: Default value for non-boolean flags (stored as JSON).
            tags: List of tags for organizing flags.
            metadata: Additional metadata stored as JSON.
            rules: Targeting rules for conditional evaluation.
            overrides: Entity-specific overrides.
            variants: Variants for A/B testing.

        """

        __tablename__ = "feature_flags"

        # Identifiers
        key: Mapped[str] = mapped_column(unique=True, index=True)
        name: Mapped[str]
        description: Mapped[str | None] = mapped_column(default=None)

        # Flag configuration
        flag_type: Mapped[FlagType] = mapped_column(default=FlagType.BOOLEAN)
        status: Mapped[FlagStatus] = mapped_column(default=FlagStatus.ACTIVE, index=True)

        # Default values
        default_enabled: Mapped[bool] = mapped_column(default=False)
        default_value: Mapped[dict[str, Any] | None] = mapped_column(JSON, default=None)

        # Metadata
        tags: Mapped[list[str]] = mapped_column(JSON, default=list)
        metadata_: Mapped[dict[str, Any]] = mapped_column("metadata", JSON, default=dict)

        # Relationships
        rules: Mapped[list[FlagRule]] = relationship(
            "FlagRule",
            back_populates="flag",
            cascade="all, delete-orphan",
            order_by="FlagRule.priority",
            lazy="selectin",
        )
        overrides: Mapped[list[FlagOverride]] = relationship(
            "FlagOverride",
            back_populates="flag",
            cascade="all, delete-orphan",
            lazy="selectin",
        )
        variants: Mapped[list[FlagVariant]] = relationship(
            "FlagVariant",
            back_populates="flag",
            cascade="all, delete-orphan",
            lazy="selectin",
        )
        scheduled_changes: Mapped[list[ScheduledFlagChange]] = relationship(
            "ScheduledFlagChange",
            back_populates="flag",
            cascade="all, delete-orphan",
            order_by="ScheduledFlagChange.scheduled_at",
            lazy="selectin",
        )
        time_schedules: Mapped[list[TimeSchedule]] = relationship(
            "TimeSchedule",
            back_populates="flag",
            cascade="all, delete-orphan",
            lazy="selectin",
        )
        rollout_phases: Mapped[list[RolloutPhase]] = relationship(
            "RolloutPhase",
            back_populates="flag",
            cascade="all, delete-orphan",
            order_by="RolloutPhase.phase_number",
            lazy="selectin",
        )

        __table_args__ = (Index("ix_feature_flags_status_key", "status", "key"),)

        def __repr__(self) -> str:
            return f"<FeatureFlag(key={self.key!r}, status={self.status.value!r})>"

else:
    # Fallback for when advanced-alchemy is not installed
    from dataclasses import dataclass, field
    from uuid import uuid4

[docs] @dataclass(slots=True) class FeatureFlag: # type: ignore[no-redef] """Feature flag model (dataclass fallback when advanced-alchemy not installed).""" key: str name: str id: UUID = field(default_factory=uuid4) description: str | None = None flag_type: FlagType = FlagType.BOOLEAN status: FlagStatus = FlagStatus.ACTIVE default_enabled: bool = False default_value: dict[str, Any] | None = None tags: list[str] = field(default_factory=list) metadata_: dict[str, Any] = field(default_factory=dict) rules: list[FlagRule] = field(default_factory=list) overrides: list[FlagOverride] = field(default_factory=list) variants: list[FlagVariant] = field(default_factory=list) scheduled_changes: list[ScheduledFlagChange] = field(default_factory=list) time_schedules: list[TimeSchedule] = field(default_factory=list) rollout_phases: list[RolloutPhase] = field(default_factory=list) created_at: datetime | None = None updated_at: datetime | None = None def __repr__(self) -> str: return f"<FeatureFlag(key={self.key!r}, status={self.status.value!r})>"