Source code for fmn.api.api_models

# SPDX-FileCopyrightText: Contributors to the Fedora Project
#
# SPDX-License-Identifier: MIT

import logging
import re
from typing import Annotated, Generic, Literal, TypeVar

from pydantic import BaseModel as PydanticBaseModel
from pydantic import ConfigDict, Field, field_validator, model_validator
from pydantic_core.core_schema import FieldValidationInfo

from ..core.constants import ArtifactType

log = logging.getLogger(__name__)


[docs] class BaseModel(PydanticBaseModel): model_config = ConfigDict(from_attributes=True)
# Tracking rules with specific types for params
[docs] class ListParamTrackingRule(BaseModel): name: Literal["artifacts-owned", "artifacts-group-owned", "users-followed"] params: list[str]
[docs] class NoParamTrackingRule(BaseModel): name: Literal["related-events"] params: str | None = None
[docs] class ArtifactsFollowedTrackingRule(BaseModel): name: Literal["artifacts-followed"] params: list[dict[Literal["name", "type"], str]]
TrackingRule = Annotated[ ListParamTrackingRule | NoParamTrackingRule | ArtifactsFollowedTrackingRule, Field(discriminator="name"), ] # DB models EMAIL_ADDRESS_RE = re.compile(r"[\w_.+-]+@[\w.-]+") MATRIX_ADDRESS_RE = re.compile(r"@[\w_-]+:[\w.-]+")
[docs] class Destination(BaseModel): protocol: str address: str
[docs] @field_validator("address") def address_format(cls, v, info: FieldValidationInfo): if info.data["protocol"] == "email" and not EMAIL_ADDRESS_RE.match(v): message = f"The email address {v!r} does not look right" log.warning(message) raise ValueError(message) elif info.data["protocol"] == "matrix" and not MATRIX_ADDRESS_RE.match(v): message = f"The Matrix address {v!r} should be in the form @username:server.tld" log.warning(message) raise ValueError(message) return v
[docs] class Filters(BaseModel): applications: list[str] = [] excluded_applications: list[str] = [] severities: list[str] = [] topic: str | None = None my_actions: bool = False
[docs] @model_validator(mode="before") def convert_from_orm(cls, data): if isinstance(data, list): return {f.name: f.params for f in data} return data
[docs] class GenerationRule(BaseModel): id: int | None = None destinations: list[Destination] filters: Filters
[docs] class User(BaseModel): id: int | None = None name: str is_admin: bool = False
[docs] class NewRule(BaseModel): name: str | None = None disabled: bool = False tracking_rule: TrackingRule generation_rules: list[GenerationRule]
[docs] class Rule(NewRule): id: int user: User generated_last_week: int = 0
[docs] class RulePatch(BaseModel): disabled: bool | None = None
# Dropdown options T = TypeVar("T")
[docs] class Option(BaseModel, Generic[T]): label: str value: T
[docs] class Artifact(BaseModel): type: ArtifactType name: str
[docs] class ArtifactOptionsGroup(BaseModel): label: str options: list[Option[Artifact]]