Source code for litestar_flags.models.rule

"""Flag rule model for targeting conditions."""

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

if TYPE_CHECKING:
    from litestar_flags.models.flag import FeatureFlag

__all__ = ["FlagRule"]


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

    class FlagRule(UUIDv7AuditBase):
        """Targeting rule for conditional flag evaluation.

        Rules are evaluated in priority order (lower number = higher priority).
        The first matching rule determines the flag value.

        Attributes:
            flag_id: Reference to the parent flag.
            name: Name of the rule for identification.
            description: Optional description of what this rule targets.
            priority: Evaluation order (lower = evaluated first).
            enabled: Whether this rule is active.
            conditions: JSON array of condition objects.
            serve_enabled: Boolean value to serve when rule matches (for boolean flags).
            serve_value: Value to serve when rule matches (for non-boolean flags).
            rollout_percentage: Optional percentage rollout (0-100).
            flag: Reference to the parent FeatureFlag.

        Example conditions format::

            [
                {"attribute": "country", "operator": "in", "value": ["US", "CA"]},
                {"attribute": "plan", "operator": "eq", "value": "premium"}
            ]

        """

        __tablename__ = "flag_rules"

        flag_id: Mapped[UUID] = mapped_column(ForeignKey("feature_flags.id", ondelete="CASCADE"))

        # Rule identification
        name: Mapped[str]
        description: Mapped[str | None] = mapped_column(default=None)
        priority: Mapped[int] = mapped_column(default=0, index=True)
        enabled: Mapped[bool] = mapped_column(default=True)

        # Conditions (JSON array of condition objects)
        conditions: Mapped[list[dict[str, Any]]] = mapped_column(JSON, default=list)

        # Rule outcome
        serve_enabled: Mapped[bool] = mapped_column(default=True)
        serve_value: Mapped[dict[str, Any] | None] = mapped_column(JSON, default=None)

        # Percentage rollout (0-100, None = 100%)
        rollout_percentage: Mapped[int | None] = mapped_column(default=None)

        # Relationships
        flag: Mapped[FeatureFlag] = relationship("FeatureFlag", back_populates="rules")

        __table_args__ = (Index("ix_flag_rules_flag_priority", "flag_id", "priority"),)

        def __repr__(self) -> str:
            return f"<FlagRule(name={self.name!r}, priority={self.priority})>"

else:
    from dataclasses import dataclass, field
    from uuid import uuid4

[docs] @dataclass(slots=True) class FlagRule: # type: ignore[no-redef] """Flag rule model (dataclass fallback when advanced-alchemy not installed).""" name: str flag_id: UUID | None = None id: UUID = field(default_factory=uuid4) description: str | None = None priority: int = 0 enabled: bool = True conditions: list[dict[str, Any]] = field(default_factory=list) serve_enabled: bool = True serve_value: dict[str, Any] | None = None rollout_percentage: int | None = None flag: FeatureFlag | None = None created_at: datetime | None = None updated_at: datetime | None = None def __repr__(self) -> str: return f"<FlagRule(name={self.name!r}, priority={self.priority})>"