# generated by datamodel-codegen:
#   filename:  openapi.filtered.yaml

from __future__ import annotations

from datetime import date
from datetime import datetime as datetime_aliased
from enum import Enum
from typing import Any, Dict, List, Literal, Optional, Union
from uuid import UUID

from pydantic import AnyUrl, BaseModel, ConfigDict, EmailStr, Field, RootModel


class PingResponse(BaseModel):
    message: str
    success: bool


class VersionResponse(BaseModel):
    hash: str


class ActionTriggerSource(BaseModel):
    triggerType: Literal["ActionTriggerSource"] = Field(
        ..., description="The type of trigger"
    )
    triggerValues: List[str] = Field(
        ...,
        description="List of data asset sources, any of which need to match an event to trigger an action",
    )


class SourceType(str, Enum):
    postgres = "postgres"
    mysql = "mysql"
    mssql = "mssql"
    json_schema = "json_schema"
    avro = "avro"
    protobuf = "protobuf"
    python = "python"
    pyspark = "pyspark"
    typescript = "typescript"
    s3 = "s3"
    dataframe = "dataframe"
    kotlin = "kotlin"
    swift = "swift"
    php = "php"


class ActionTriggerSourceType(BaseModel):
    triggerType: Literal["ActionTriggerSourceType"] = Field(
        ..., description="The type of trigger"
    )
    triggerValues: List[SourceType] = Field(
        ...,
        description="List of data asset sourceTypes, any of which need to match an event to trigger an action",
    )


class TriggerValue(str, Enum):
    data_asset_created = "data_asset_created"
    data_asset_modified_field_added = "data_asset_modified_field_added"
    data_asset_modified_field_changed = "data_asset_modified_field_changed"
    data_asset_modified_field_removed = "data_asset_modified_field_removed"
    data_asset_deleted = "data_asset_deleted"
    data_contract_created = "data_contract_created"
    data_contract_modified = "data_contract_modified"
    data_contract_violation = "data_contract_violation"
    data_contract_deleted = "data_contract_deleted"
    data_action_created = "data_action_created"
    data_action_deleted = "data_action_deleted"
    data_action_enabled = "data_action_enabled"
    data_action_disabled = "data_action_disabled"
    data_action_modified = "data_action_modified"
    data_asset_pii_detected = "data_asset_pii_detected"


class ActionTriggerEventName(BaseModel):
    triggerType: Literal["ActionTriggerEventName"] = Field(
        ..., description="The type of trigger"
    )
    triggerValues: List[TriggerValue] = Field(
        ...,
        description="List of data asset event names, any of which need to match an event to trigger an action",
    )


class ActionTriggerNamespace(BaseModel):
    triggerType: Literal["ActionTriggerNamespace"] = Field(
        ..., description="The type of trigger"
    )
    triggerValues: List[str] = Field(
        ...,
        description="List of contract namespaces, any of which need to match an event to trigger an action",
    )


class ActionDestinationSlack(BaseModel):
    destinationType: Literal["ActionDestinationSlack"] = Field(
        ..., description="The type of destination"
    )
    slackChannelIds: List[str] = Field(
        ...,
        description="List of slack channels IDs to notify when the action is triggered",
    )


class ActionDestinationEmail(BaseModel):
    destinationType: Literal["ActionDestinationEmail"] = Field(
        ..., description="The type of destination"
    )
    emailAddresses: List[EmailStr] = Field(
        ...,
        description="List of email addresses to notify when the action is triggered",
    )


class ActionDestinationWebhook(BaseModel):
    destinationType: Literal["ActionDestinationWebhook"] = Field(
        ..., description="The type of destination"
    )
    webhookIds: List[str] = Field(
        ...,
        description="List of webhook identifiers to notify when the action is triggered",
    )


class Triggers(
    RootModel[
        "Union[ActionTriggerSource, ActionTriggerSourceType, ActionTriggerEventName, ActionTriggerNamespace]"
    ]
):
    root: Union[
        ActionTriggerSource,
        ActionTriggerSourceType,
        ActionTriggerEventName,
        ActionTriggerNamespace,
    ] = Field(..., discriminator="triggerType")

    @classmethod
    def construct_error(cls, e):
        from pydantic import ValidationError
        from pydantic.error_wrappers import ErrorWrapper

        class ExceptionProxy(Exception):
            def __init__(self, name, msg):
                self.__class__.__name__ = name
                self.msg = msg

            def __str__(self):
                res = self.msg
                del self.__dict__[
                    "msg"
                ]  # don't render msg twice in pydantic's error reporting
                return res

        filtered_errors = []
        for error in e.errors():
            new_error = {
                **error,
                "loc": tuple(f for f in error["loc"] if f != "root"),
            }
            if new_error not in filtered_errors:
                filtered_errors.append(new_error)
        error_wrappers = []
        for error in filtered_errors:
            msg = error["msg"]
            name = ".".join(error["type"].split(".")[1:])
            exc = ExceptionProxy(name, msg)
            error_wrapper = ErrorWrapper(
                exc=exc,
                loc=tuple(error["loc"]),
            )
            error_wrappers.append(error_wrapper)
        return ValidationError(error_wrappers, cls)

    @classmethod
    def validate(cls, value):
        from pydantic import ValidationError

        try:
            return super().validate(value)
        except ValidationError as e:
            raise cls.construct_error(e)


class Destinations(
    RootModel[
        "Union[ActionDestinationSlack, ActionDestinationEmail, ActionDestinationWebhook]"
    ]
):
    root: Union[
        ActionDestinationSlack, ActionDestinationEmail, ActionDestinationWebhook
    ] = Field(..., discriminator="destinationType")

    @classmethod
    def construct_error(cls, e):
        from pydantic import ValidationError
        from pydantic.error_wrappers import ErrorWrapper

        class ExceptionProxy(Exception):
            def __init__(self, name, msg):
                self.__class__.__name__ = name
                self.msg = msg

            def __str__(self):
                res = self.msg
                del self.__dict__[
                    "msg"
                ]  # don't render msg twice in pydantic's error reporting
                return res

        filtered_errors = []
        for error in e.errors():
            new_error = {
                **error,
                "loc": tuple(f for f in error["loc"] if f != "root"),
            }
            if new_error not in filtered_errors:
                filtered_errors.append(new_error)
        error_wrappers = []
        for error in filtered_errors:
            msg = error["msg"]
            name = ".".join(error["type"].split(".")[1:])
            exc = ExceptionProxy(name, msg)
            error_wrapper = ErrorWrapper(
                exc=exc,
                loc=tuple(error["loc"]),
            )
            error_wrappers.append(error_wrapper)
        return ValidationError(error_wrappers, cls)

    @classmethod
    def validate(cls, value):
        from pydantic import ValidationError

        try:
            return super().validate(value)
        except ValidationError as e:
            raise cls.construct_error(e)


class PutActionRequest(BaseModel):
    actionId: str = Field(..., description="The unique identifier of the action")
    name: str = Field(
        ...,
        description="The name of the action",
        examples=["email on pricing asset change"],
    )
    triggers: List[Triggers] = Field(..., min_length=1)
    destinations: List[Destinations]
    isActive: bool = Field(
        ..., description="Whether the action is active or not", examples=[True]
    )


class PutActionResponse(BaseModel):
    actionId: str = Field(
        ..., description="The unique identifier of the updated action"
    )


class ErrorResponse(BaseModel):
    id: Optional[float] = None
    title: Optional[str] = None
    message: str


class PostActionRequest(BaseModel):
    name: str = Field(
        ...,
        description="The name of the action",
        examples=["email on pricing asset change"],
    )
    triggers: List[Triggers] = Field(..., min_length=1)
    destinations: List[Destinations] = Field(..., min_length=1)


class PostActionResponse(BaseModel):
    actionId: str = Field(
        ..., description="The unique identifier of the created action"
    )


class DeleteActionRequest(BaseModel):
    actionId: str = Field(..., description="The action id to delete")


class DeleteActionResponse(BaseModel):
    actionId: str = Field(..., description="The id of the action which was deleted")


class Action(BaseModel):
    actionId: str = Field(
        ..., description="The unique identifier of the created action"
    )
    name: str = Field(
        ...,
        description="The name of the action",
        examples=["email on pricing asset change"],
    )
    isActive: bool = Field(
        ..., description="Whether the action is active", examples=[True]
    )
    ownerName: Optional[str] = Field(
        default=None, description="The name of the user who created the action"
    )
    triggers: List[Triggers] = Field(..., min_length=1)
    destinations: List[Destinations] = Field(..., min_length=1)


class GetActionsResponse(BaseModel):
    actions: List[Action]


class GetNpmCredentialsResponse(BaseModel):
    authToken: str = Field(..., description="Temporary auth token")
    repositoryEndpoint: str = Field(..., description="The npm repository endpoint")


class GetPipCredentialsResponse(BaseModel):
    authToken: str = Field(..., description="Temporary auth token")
    repositoryEndpoint: str = Field(..., description="The pip repository endpoint")


class GetScaPrimeArtifactBucketResponse(BaseModel):
    signedS3Url: str = Field(..., description="The signed S3 URL")


class DeleteContractResponse(BaseModel):
    message: Optional[str] = Field(default=None, description="Success message")


class ContractStatus(str, Enum):
    ACTIVE = "ACTIVE"
    DEPRECATED = "DEPRECATED"
    DRAFT = "DRAFT"
    ARCHIVED = "ARCHIVED"


class DataAssetResourceName(RootModel["str"]):
    model_config = ConfigDict(
        regex_engine="python-re",
    )
    root: str = Field(
        ...,
        description="The unique identifier of the data asset. It follows the pattern '{data_asset_type}://{data_asset_source}:{data_asset_name}'",
        pattern="^(protobuf|avro|json_schema|postgres|mysql|mssql|snowflake|bigquery|python|pyspark|typescript|s3|dataframe|kotlin|swift|php)://(?=.*[a-zA-Z0-9])[a-zA-Z0-9_@\\.:/-]+[:/](?=.*[a-zA-Z0-9])[a-zA-Z0-9 _\\./-\\{\\}\\-]+$",
    )

    @classmethod
    def construct_error(cls, e):
        from pydantic import ValidationError
        from pydantic.error_wrappers import ErrorWrapper

        class ExceptionProxy(Exception):
            def __init__(self, name, msg):
                self.__class__.__name__ = name
                self.msg = msg

            def __str__(self):
                res = self.msg
                del self.__dict__[
                    "msg"
                ]  # don't render msg twice in pydantic's error reporting
                return res

        filtered_errors = []
        for error in e.errors():
            new_error = {
                **error,
                "loc": tuple(f for f in error["loc"] if f != "root"),
            }
            if new_error not in filtered_errors:
                filtered_errors.append(new_error)
        error_wrappers = []
        for error in filtered_errors:
            msg = error["msg"]
            name = ".".join(error["type"].split(".")[1:])
            exc = ExceptionProxy(name, msg)
            error_wrapper = ErrorWrapper(
                exc=exc,
                loc=tuple(error["loc"]),
            )
            error_wrappers.append(error_wrapper)
        return ValidationError(error_wrappers, cls)

    @classmethod
    def validate(cls, value):
        from pydantic import ValidationError

        try:
            return super().validate(value)
        except ValidationError as e:
            raise cls.construct_error(e)


class GableSchemaFieldNull(BaseModel):
    type: Literal["null"]
    alias: Optional[str] = Field(
        default=None, pattern="^[a-zA-Z_][a-zA-Z0-9_]*(?:\\.[a-zA-Z_][a-zA-Z0-9_]*)+$"
    )
    doc: Optional[str] = None
    logical: Optional[str] = None
    name: Optional[str] = None
    optional: Optional[bool] = None


class GableSchemaFieldBool(BaseModel):
    type: Literal["bool"]
    alias: Optional[str] = Field(
        default=None, pattern="^[a-zA-Z_][a-zA-Z0-9_]*(?:\\.[a-zA-Z_][a-zA-Z0-9_]*)+$"
    )
    doc: Optional[str] = None
    logical: Optional[str] = None
    name: Optional[str] = None
    optional: Optional[bool] = None


class LogicalEnumTemporal(str, Enum):
    Date = "Date"
    Time = "Time"
    Duration = "Duration"
    Timestamp = "Timestamp"


class Unit(str, Enum):
    year = "year"
    month = "month"
    day = "day"
    hour = "hour"
    minute = "minute"
    second = "second"
    millisecond = "millisecond"
    microsecond = "microsecond"
    nanosecond = "nanosecond"
    picosecond = "picosecond"


class GableSchemaFieldInt(BaseModel):
    type: Literal["int"]
    bits: int = Field(..., ge=1, le=2147483647)
    signed: Optional[bool] = True
    alias: Optional[str] = Field(
        default=None, pattern="^[a-zA-Z_][a-zA-Z0-9_]*(?:\\.[a-zA-Z_][a-zA-Z0-9_]*)+$"
    )
    doc: Optional[str] = None
    logical: Optional[LogicalEnumTemporal] = None
    unit: Optional[Unit] = None
    timezone: Optional[str] = None
    name: Optional[str] = None
    optional: Optional[bool] = None


class GableSchemaFieldFloat(BaseModel):
    type: Literal["float"]
    bits: int = Field(..., ge=1, le=2147483647)
    alias: Optional[str] = Field(
        default=None, pattern="^[a-zA-Z_][a-zA-Z0-9_]*(?:\\.[a-zA-Z_][a-zA-Z0-9_]*)+$"
    )
    doc: Optional[str] = None
    logical: Optional[str] = None
    name: Optional[str] = None
    optional: Optional[bool] = None


class LogicalEnumText(str, Enum):
    UUID = "UUID"
    org_iso_8601_Date = "org.iso.8601.Date"
    org_iso_8601_DateTime = "org.iso.8601.DateTime"
    org_iso_8601_Time = "org.iso.8601.Time"


class GableSchemaFieldString(BaseModel):
    type: Literal["string"]
    bytes: Optional[int] = Field(default=None, ge=1, le=9223372036854776000)
    variable: Optional[bool] = True
    alias: Optional[str] = Field(
        default=None, pattern="^[a-zA-Z_][a-zA-Z0-9_]*(?:\\.[a-zA-Z_][a-zA-Z0-9_]*)+$"
    )
    doc: Optional[str] = None
    logical: Optional[LogicalEnumText] = None
    name: Optional[str] = None
    optional: Optional[bool] = None


class LogicalEnumNumeric(str, Enum):
    Decimal = "Decimal"
    Interval = "Interval"


class GableSchemaFieldBytes(BaseModel):
    type: Literal["bytes"]
    bytes: Optional[int] = Field(default=None, ge=1, le=9223372036854776000)
    variable: Optional[bool] = None
    alias: Optional[str] = Field(
        default=None, pattern="^[a-zA-Z_][a-zA-Z0-9_]*(?:\\.[a-zA-Z_][a-zA-Z0-9_]*)+$"
    )
    doc: Optional[str] = None
    logical: Optional[LogicalEnumNumeric] = None
    precision: Optional[int] = Field(default=None, ge=1, le=2147483647)
    scale: Optional[int] = Field(default=None, ge=0, le=2147483647)
    unit: Optional[Unit] = None
    name: Optional[str] = None
    optional: Optional[bool] = None


class GableSchemaTypeName(str, Enum):
    null = "null"
    bool = "bool"
    int = "int"
    float = "float"
    string = "string"
    bytes = "bytes"
    list = "list"
    map = "map"
    struct = "struct"
    enum = "enum"
    union = "union"


class GableSchemaNull(BaseModel):
    type: Literal["null"]
    alias: Optional[str] = Field(
        default=None, pattern="^[a-zA-Z_][a-zA-Z0-9_]*(?:\\.[a-zA-Z_][a-zA-Z0-9_]*)+$"
    )
    doc: Optional[str] = None
    logical: Optional[str] = None


class GableSchemaBool(BaseModel):
    type: Literal["bool"]
    alias: Optional[str] = Field(
        default=None, pattern="^[a-zA-Z_][a-zA-Z0-9_]*(?:\\.[a-zA-Z_][a-zA-Z0-9_]*)+$"
    )
    doc: Optional[str] = None
    logical: Optional[str] = None


class GableSchemaInt(BaseModel):
    type: Literal["int"]
    bits: int = Field(..., ge=1, le=2147483647)
    signed: Optional[bool] = True
    alias: Optional[str] = Field(
        default=None, pattern="^[a-zA-Z_][a-zA-Z0-9_]*(?:\\.[a-zA-Z_][a-zA-Z0-9_]*)+$"
    )
    doc: Optional[str] = None
    logical: Optional[LogicalEnumTemporal] = None
    unit: Optional[Unit] = None
    timezone: Optional[str] = None


class GableSchemaFloat(BaseModel):
    type: Literal["float"]
    bits: int = Field(..., ge=1, le=2147483647)
    alias: Optional[str] = Field(
        default=None, pattern="^[a-zA-Z_][a-zA-Z0-9_]*(?:\\.[a-zA-Z_][a-zA-Z0-9_]*)+$"
    )
    doc: Optional[str] = None
    logical: Optional[str] = None


class GableSchemaString(BaseModel):
    type: Literal["string"]
    bytes: Optional[int] = Field(default=None, ge=1, le=9223372036854776000)
    variable: Optional[bool] = True
    alias: Optional[str] = Field(
        default=None, pattern="^[a-zA-Z_][a-zA-Z0-9_]*(?:\\.[a-zA-Z_][a-zA-Z0-9_]*)+$"
    )
    doc: Optional[str] = None
    logical: Optional[LogicalEnumText] = None


class GableSchemaBytes(BaseModel):
    type: Literal["bytes"]
    bytes: Optional[int] = Field(default=None, ge=1, le=9223372036854776000)
    variable: Optional[bool] = None
    alias: Optional[str] = Field(
        default=None, pattern="^[a-zA-Z_][a-zA-Z0-9_]*(?:\\.[a-zA-Z_][a-zA-Z0-9_]*)+$"
    )
    doc: Optional[str] = None
    logical: Optional[LogicalEnumNumeric] = None
    precision: Optional[int] = Field(default=None, ge=1, le=2147483647)
    scale: Optional[int] = Field(default=None, ge=0, le=2147483647)
    unit: Optional[Unit] = None


class GableSchemaEnum(BaseModel):
    type: Literal["enum"]
    symbols: List[str]
    alias: Optional[str] = Field(
        default=None, pattern="^[a-zA-Z_][a-zA-Z0-9_]*(?:\\.[a-zA-Z_][a-zA-Z0-9_]*)+$"
    )
    doc: Optional[str] = None
    logical: Optional[str] = None


class GableSchemaAliasReference(BaseModel):
    model_config = ConfigDict(
        extra="allow",
    )
    type: str
    doc: Optional[str] = None
    logical: Optional[str] = None


class GableSchemaUnknown(BaseModel):
    type: Literal["unknown"]
    alias: Optional[str] = Field(
        default=None, pattern="^[a-zA-Z_][a-zA-Z0-9_]*(?:\\.[a-zA-Z_][a-zA-Z0-9_]*)+$"
    )
    doc: Optional[str] = None
    logical: Optional[str] = None


class GableSchemaFieldEnum(BaseModel):
    type: Literal["enum"]
    symbols: List[str]
    alias: Optional[str] = Field(
        default=None, pattern="^[a-zA-Z_][a-zA-Z0-9_]*(?:\\.[a-zA-Z_][a-zA-Z0-9_]*)+$"
    )
    doc: Optional[str] = None
    logical: Optional[str] = None
    name: Optional[str] = None
    optional: Optional[bool] = None


class GableSchemaFieldAliasReference(BaseModel):
    model_config = ConfigDict(
        extra="allow",
    )
    type: str
    doc: Optional[str] = None
    logical: Optional[str] = None
    name: Optional[str] = None
    optional: Optional[bool] = None


class GableSchemaFieldUnknown(BaseModel):
    type: Literal["unknown"]
    alias: Optional[str] = Field(
        default=None, pattern="^[a-zA-Z_][a-zA-Z0-9_]*(?:\\.[a-zA-Z_][a-zA-Z0-9_]*)+$"
    )
    doc: Optional[str] = None
    logical: Optional[str] = None
    name: Optional[str] = None
    optional: Optional[bool] = None


class GableSchemaContractField1(BaseModel):
    name: Optional[str] = None
    optional: Optional[bool] = None
    constraints: Optional[Dict[str, Any]] = None


class GableSchemaContractField3(GableSchemaNull, GableSchemaContractField1):
    pass


class GableSchemaContractField4(GableSchemaBool, GableSchemaContractField1):
    pass


class GableSchemaContractField5(GableSchemaInt, GableSchemaContractField1):
    pass


class GableSchemaContractField6(GableSchemaFloat, GableSchemaContractField1):
    pass


class GableSchemaContractField7(GableSchemaString, GableSchemaContractField1):
    pass


class GableSchemaContractField8(GableSchemaBytes, GableSchemaContractField1):
    pass


class GableSchemaContractField11(GableSchemaEnum, GableSchemaContractField1):
    pass


class GableSchemaContractField13(GableSchemaAliasReference, GableSchemaContractField1):
    pass


class GableSchemaContractField14(GableSchemaUnknown, GableSchemaContractField1):
    pass


class EnforcementLevel(str, Enum):
    RECORD = "RECORD"
    NOTIFY = "NOTIFY"
    ALERT = "ALERT"
    BLOCK = "BLOCK"
    INACTIVE = "INACTIVE"


class CheckResponse(BaseModel):
    message: str
    success: bool


class ChangelogEventBase(BaseModel):
    id: UUID = Field(..., description="The unique identifier for the changelog event.")
    eventTitle: Optional[str] = Field(
        default=None, description="The LLM-generated text of the changelog event."
    )
    timestamp: datetime_aliased = Field(
        ..., description="The timestamp of the changelog event."
    )
    userId: Optional[UUID] = Field(
        default=None,
        description="The unique identifier for the User who made a changelog event.",
    )


class ContractViolationType(str, Enum):
    MISSING_REQUIRED_PROPERTY = "MISSING_REQUIRED_PROPERTY"
    INCOMPATIBLE_TYPE = "INCOMPATIBLE_TYPE"
    GREATER_THAN = "GREATER_THAN"
    GREATER_THAN_OR_EQUAL_TO = "GREATER_THAN_OR_EQUAL_TO"
    LESS_THAN = "LESS_THAN"
    LESS_THAN_OR_EQUAL_TO = "LESS_THAN_OR_EQUAL_TO"
    IS_NULL = "IS_NULL"
    IS_NULL_THRESHOLD = "IS_NULL_THRESHOLD"
    IS_NOT_EMPTY = "IS_NOT_EMPTY"
    LENGTH = "LENGTH"
    LENGTH_GREATER_THAN = "LENGTH_GREATER_THAN"
    LENGTH_GREATER_THAN_OR_EQUAL_TO = "LENGTH_GREATER_THAN_OR_EQUAL_TO"
    LENGTH_LESS_THAN = "LENGTH_LESS_THAN"
    LENGTH_LESS_THAN_OR_EQUAL_TO = "LENGTH_LESS_THAN_OR_EQUAL_TO"
    MISSING_DATA_ASSET = "MISSING_DATA_ASSET"
    PII_DETECTED = "PII_DETECTED"


class ContractViolation(BaseModel):
    contractId: UUID = Field(
        ..., description="Unique identifier for the contract in UUID format"
    )
    contractVersion: str = Field(..., description="Version of the contract")
    contractFieldName: str = Field(
        ..., description="Field of the contract where violation occured"
    )
    contractName: Optional[str] = Field(
        default=None, description="Name of the contract"
    )
    contractDomain: Optional[str] = Field(
        default=None, description="Domain of the contract"
    )
    dataAssetFieldProfileId: Optional[UUID] = Field(
        default=None,
        description="Unique identifier for the data asset field in UUID format",
    )
    entityType: Literal["DATA_CONTRACT"] = Field(
        ...,
        description="The type of entity that the changelog event is associated with.",
    )
    eventType: Optional[Literal["VIOLATION"]] = Field(
        default=None, description="The type of event that occurred"
    )
    violationType: ContractViolationType = Field(..., description="Type of violation")
    dataAssetResourceName: DataAssetResourceName
    expectedValue: str = Field(..., description="Expected value of the field")
    actualValue: str = Field(..., description="actual value of the field")
    userId: Optional[UUID] = Field(
        default=None, description="Unique identifier for the user in UUID format"
    )
    prLink: Optional[str] = Field(
        default=None,
        description="Link to the pull request associated with the violation",
    )


class ContractViolationEvent(ChangelogEventBase, ContractViolation):
    pass


class ContractStage(str, Enum):
    ACTIVE = "ACTIVE"
    DEPRECATED = "DEPRECATED"
    DRAFT = "DRAFT"
    ARCHIVED = "ARCHIVED"


class PutContractStageRequest(BaseModel):
    contractStage: ContractStage = Field(
        ..., description="The new contract stage for the contract"
    )


class PutContractStageResponse(BaseModel):
    message: str = Field(
        ..., description="Success message or information about the operation"
    )
    contractId: UUID = Field(..., description="The ID of the contract that was updated")


class ViolationState(str, Enum):
    violation = "violation"
    no_violation = "no_violation"


class Violation(BaseModel):
    message: str
    field: str
    fieldType: str
    violationType: ContractViolationType
    expected: str
    actual: Optional[str] = None


class ContractViolationCheckedResponse(BaseModel):
    violation_state: ViolationState
    violations: Optional[List[Violation]] = None
    checkedAt: Optional[datetime_aliased] = Field(
        default=None, description="The datetime the violation check was performed"
    )


class ContractViolationUncheckedResponse(BaseModel):
    violation_state: Literal["unchecked"]


class GetContractViolationStatusResponse(
    RootModel[
        "Union[ContractViolationCheckedResponse, ContractViolationUncheckedResponse]"
    ]
):
    root: Union[ContractViolationCheckedResponse, ContractViolationUncheckedResponse]

    @classmethod
    def construct_error(cls, e):
        from pydantic import ValidationError
        from pydantic.error_wrappers import ErrorWrapper

        class ExceptionProxy(Exception):
            def __init__(self, name, msg):
                self.__class__.__name__ = name
                self.msg = msg

            def __str__(self):
                res = self.msg
                del self.__dict__[
                    "msg"
                ]  # don't render msg twice in pydantic's error reporting
                return res

        filtered_errors = []
        for error in e.errors():
            new_error = {
                **error,
                "loc": tuple(f for f in error["loc"] if f != "root"),
            }
            if new_error not in filtered_errors:
                filtered_errors.append(new_error)
        error_wrappers = []
        for error in filtered_errors:
            msg = error["msg"]
            name = ".".join(error["type"].split(".")[1:])
            exc = ExceptionProxy(name, msg)
            error_wrapper = ErrorWrapper(
                exc=exc,
                loc=tuple(error["loc"]),
            )
            error_wrappers.append(error_wrapper)
        return ValidationError(error_wrappers, cls)

    @classmethod
    def validate(cls, value):
        from pydantic import ValidationError

        try:
            return super().validate(value)
        except ValidationError as e:
            raise cls.construct_error(e)


class DataCount(BaseModel):
    count: float = Field(..., description="Total number of rows")


class PostContractResponse(BaseModel):
    message: str
    contractIds: List[UUID] = Field(
        ...,
        description="List of contract IDs that were updated, if no contracts were updated this will be an empty list",
    )


class ContractActivityUpdateEvent(BaseModel):
    type: Literal["CONTRACT_UPDATED"] = Field(..., description="type of the event")
    datetime: datetime_aliased = Field(
        ..., description="date time at which the contract was updated"
    )
    fileUri: Optional[AnyUrl] = Field(
        default=None,
        description="full link to the file and commit in the repo that contains this updated contract",
    )
    gitUser: Optional[str] = Field(
        default=None, description="the git user who edited the contract"
    )


class ContractActivityCreateEvent(BaseModel):
    type: Literal["CONTRACT_CREATED"] = Field(..., description="type of the event")
    datetime: datetime_aliased = Field(
        ..., description="date time at which the contract was created"
    )
    fileUri: Optional[AnyUrl] = Field(
        default=None,
        description="full link to the file and commit in the repo that contains this created contract",
    )
    gitUser: Optional[str] = Field(
        default=None, description="the git user who created the contract"
    )


class ContractActivityResponse(
    RootModel["List[Union[ContractActivityUpdateEvent, ContractActivityCreateEvent]]"]
):
    root: List[Union[ContractActivityUpdateEvent, ContractActivityCreateEvent]] = Field(
        ...,
        description="List of contract activity events in chronological order (oldest first)",
        min_length=1,
    )

    @classmethod
    def construct_error(cls, e):
        from pydantic import ValidationError
        from pydantic.error_wrappers import ErrorWrapper

        class ExceptionProxy(Exception):
            def __init__(self, name, msg):
                self.__class__.__name__ = name
                self.msg = msg

            def __str__(self):
                res = self.msg
                del self.__dict__[
                    "msg"
                ]  # don't render msg twice in pydantic's error reporting
                return res

        filtered_errors = []
        for error in e.errors():
            new_error = {
                **error,
                "loc": tuple(f for f in error["loc"] if f != "root"),
            }
            if new_error not in filtered_errors:
                filtered_errors.append(new_error)
        error_wrappers = []
        for error in filtered_errors:
            msg = error["msg"]
            name = ".".join(error["type"].split(".")[1:])
            exc = ExceptionProxy(name, msg)
            error_wrapper = ErrorWrapper(
                exc=exc,
                loc=tuple(error["loc"]),
            )
            error_wrappers.append(error_wrapper)
        return ValidationError(error_wrappers, cls)

    @classmethod
    def validate(cls, value):
        from pydantic import ValidationError

        try:
            return super().validate(value)
        except ValidationError as e:
            raise cls.construct_error(e)


class ContractSubscription(BaseModel):
    id: UUID = Field(..., description="The unique identifier of the subscription")
    dataContractId: UUID = Field(..., description="Unique identifier of the contract")
    userId: Optional[UUID] = Field(
        default=None, description="The unique identifier of the user"
    )
    email: Optional[str] = Field(
        default=None,
        description="The email address of the subscriber if provided",
        pattern="^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$",
    )
    githubHandle: Optional[str] = Field(
        default=None, description="The GitHub handle of the subscriber if provided"
    )
    slackChannel: Optional[str] = Field(
        default=None,
        description="The Slack channel where contract violations will be sent",
    )


class UpdateContractSubscriptionRequest(BaseModel):
    dataContractId: UUID = Field(..., description="Unique identifier of the contract")
    userId: Optional[UUID] = Field(
        default=None, description="The unique identifier of the user"
    )
    email: Optional[str] = Field(
        default=None,
        description="The email address of the subscriber if provided",
        pattern="^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$",
    )
    githubHandle: Optional[str] = Field(
        default=None, description="The GitHub handle for the subscriber if provided"
    )
    slackChannel: Optional[str] = Field(
        default=None,
        description="The Slack channel where contract violations will be sent",
    )


class CreateContractSubscriptionRequest(BaseModel):
    dataContractId: UUID = Field(..., description="Unique identifier of the contract")
    userId: Optional[UUID] = Field(
        default=None, description="The unique identifier of the user"
    )
    email: Optional[str] = Field(
        default=None,
        description="The email address of the subscriber if provided",
        pattern="^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$",
    )
    githubHandle: Optional[str] = Field(
        default=None, description="The GitHub handle of the subscriber"
    )
    slackChannel: Optional[str] = Field(
        default=None,
        description="The Slack channel where contract violations will be sent",
    )


class GetContractSubscriptionsResponse(RootModel["List[ContractSubscription]"]):
    root: List[ContractSubscription]

    @classmethod
    def construct_error(cls, e):
        from pydantic import ValidationError
        from pydantic.error_wrappers import ErrorWrapper

        class ExceptionProxy(Exception):
            def __init__(self, name, msg):
                self.__class__.__name__ = name
                self.msg = msg

            def __str__(self):
                res = self.msg
                del self.__dict__[
                    "msg"
                ]  # don't render msg twice in pydantic's error reporting
                return res

        filtered_errors = []
        for error in e.errors():
            new_error = {
                **error,
                "loc": tuple(f for f in error["loc"] if f != "root"),
            }
            if new_error not in filtered_errors:
                filtered_errors.append(new_error)
        error_wrappers = []
        for error in filtered_errors:
            msg = error["msg"]
            name = ".".join(error["type"].split(".")[1:])
            exc = ExceptionProxy(name, msg)
            error_wrapper = ErrorWrapper(
                exc=exc,
                loc=tuple(error["loc"]),
            )
            error_wrappers.append(error_wrapper)
        return ValidationError(error_wrappers, cls)

    @classmethod
    def validate(cls, value):
        from pydantic import ValidationError

        try:
            return super().validate(value)
        except ValidationError as e:
            raise cls.construct_error(e)


class ContractCheckComplianceResponse(BaseModel):
    violations: List[Violation]


class ContractEnforcementLevel(BaseModel):
    id: UUID = Field(
        ...,
        description="Unique identifier for the contract enforcement level row in UUID format",
    )
    contractId: UUID = Field(
        ..., description="Unique identifier for the associated contract"
    )
    createdAt: datetime_aliased = Field(
        ..., description="Date and time at which the enforcement level was created"
    )
    updatedAt: datetime_aliased = Field(
        ..., description="Date and time at which the enforcement level was last updated"
    )
    enforcementLevel: EnforcementLevel = Field(
        ..., description="alert level for contract"
    )


class UpdateContractEnforcementLevelRequest(BaseModel):
    enforcementLevel: EnforcementLevel = Field(
        ..., description="The enforcement level for the contract"
    )


class CreateContractEnforcementLevelRequest(BaseModel):
    contractId: UUID = Field(
        ..., description="Unique identifier for the associated contract"
    )
    enforcementLevel: EnforcementLevel = Field(
        ..., description="The enforcement level for the contract"
    )


class DataAssetSearchResult(BaseModel):
    id: UUID = Field(..., description="The unique identifier for the data asset.")
    dataAssetResourceName: DataAssetResourceName
    domain: str = Field(
        ...,
        description='The domain to which the data asset belongs. Previously referred to as "namespace."',
    )
    path: str = Field(
        ...,
        description='The name or path identifying the data asset. Previously referred to as "name."',
    )
    type: SourceType
    contractId: Optional[str] = Field(
        default=None, description="The contract ID associated with the data asset."
    )
    updatedAt: datetime_aliased = Field(
        ..., description="The timestamp of the most recent update to the data asset."
    )


class DataAssetSearchResultPaginated(BaseModel):
    data: Optional[List[DataAssetSearchResult]] = None
    totalCount: Optional[float] = None


class CreateOrUpdateDataAssetFieldRequest(BaseModel):
    name: str = Field(..., description="The name of the field.")
    description: Optional[str] = Field(
        default=None, description="A brief description of the field."
    )
    order: Optional[int] = Field(
        default=None,
        description="The order or position of the field in the data asset.",
    )
    nativeDataType: str = Field(
        ..., description="The native data type of the field in the source system."
    )
    type: Dict[str, Any] = Field(
        ...,
        description="A custom or specific attribute to represent the Gable type of the field.",
    )
    parentFieldId: Optional[UUID] = Field(
        default=None,
        description="For nested fields, this is the ID of the parent field.",
    )
    changeSummary: Optional[str] = Field(
        default=None,
        description="A very brief LLM-generated summary of the changes made to the data asset field.",
    )
    changeDescription: Optional[str] = Field(
        default=None,
        description="A detailed LLM-generated description of the changes made to the data asset field.",
    )


class DataAssetInput(BaseModel):
    dataAssetResourceName: DataAssetResourceName
    domain: str = Field(
        ..., description="The domain or category to which the data asset belongs."
    )
    path: str = Field(..., description="The name or path identifying the data asset.")
    type: SourceType
    description: Optional[str] = Field(
        default=None,
        description="A brief description of this particular version of the data asset.",
    )
    rawSchema: Optional[str] = Field(
        default=None,
        description="The raw schema of the data asset from the source system. This can be the contents of a schema file or data from the information  schema of a database. This is used to regenerate the schema of the data asset if needed.",
    )
    fields: List[CreateOrUpdateDataAssetFieldRequest] = Field(
        ..., description="The fields of the data asset."
    )
    prLink: Optional[str] = Field(
        default=None,
        description="Link to the PR that may have added or edited the data assets",
        examples=["https://github.com/fakeorg/fakerepo/pull/123"],
    )


class CreateOrUpdateDataAssetsRequest(RootModel["List[DataAssetInput]"]):
    root: List[DataAssetInput]

    @classmethod
    def construct_error(cls, e):
        from pydantic import ValidationError
        from pydantic.error_wrappers import ErrorWrapper

        class ExceptionProxy(Exception):
            def __init__(self, name, msg):
                self.__class__.__name__ = name
                self.msg = msg

            def __str__(self):
                res = self.msg
                del self.__dict__[
                    "msg"
                ]  # don't render msg twice in pydantic's error reporting
                return res

        filtered_errors = []
        for error in e.errors():
            new_error = {
                **error,
                "loc": tuple(f for f in error["loc"] if f != "root"),
            }
            if new_error not in filtered_errors:
                filtered_errors.append(new_error)
        error_wrappers = []
        for error in filtered_errors:
            msg = error["msg"]
            name = ".".join(error["type"].split(".")[1:])
            exc = ExceptionProxy(name, msg)
            error_wrapper = ErrorWrapper(
                exc=exc,
                loc=tuple(error["loc"]),
            )
            error_wrappers.append(error_wrapper)
        return ValidationError(error_wrappers, cls)

    @classmethod
    def validate(cls, value):
        from pydantic import ValidationError

        try:
            return super().validate(value)
        except ValidationError as e:
            raise cls.construct_error(e)


class DataAssetOutput(BaseModel):
    id: UUID = Field(..., description="ID of the created or updated data asset")
    versionId: UUID = Field(..., description="Version ID of the now current data asset")
    dataAssetResourceName: DataAssetResourceName
    newVersionCreated: bool = Field(
        ..., description="Indicates whether a new version was created"
    )


class CreateOrUpdateDataAssetsResponse(RootModel["List[DataAssetOutput]"]):
    root: List[DataAssetOutput]

    @classmethod
    def construct_error(cls, e):
        from pydantic import ValidationError
        from pydantic.error_wrappers import ErrorWrapper

        class ExceptionProxy(Exception):
            def __init__(self, name, msg):
                self.__class__.__name__ = name
                self.msg = msg

            def __str__(self):
                res = self.msg
                del self.__dict__[
                    "msg"
                ]  # don't render msg twice in pydantic's error reporting
                return res

        filtered_errors = []
        for error in e.errors():
            new_error = {
                **error,
                "loc": tuple(f for f in error["loc"] if f != "root"),
            }
            if new_error not in filtered_errors:
                filtered_errors.append(new_error)
        error_wrappers = []
        for error in filtered_errors:
            msg = error["msg"]
            name = ".".join(error["type"].split(".")[1:])
            exc = ExceptionProxy(name, msg)
            error_wrapper = ErrorWrapper(
                exc=exc,
                loc=tuple(error["loc"]),
            )
            error_wrappers.append(error_wrapper)
        return ValidationError(error_wrappers, cls)

    @classmethod
    def validate(cls, value):
        from pydantic import ValidationError

        try:
            return super().validate(value)
        except ValidationError as e:
            raise cls.construct_error(e)


class DeleteDataAssetsRequest(BaseModel):
    dataAssetsResourceNames: List[str] = Field(
        ..., description="The unique identifiers of the data asset"
    )


class DeleteDataAssetsResponse(BaseModel):
    message: Optional[str] = Field(default=None, description="Success message")


CreateOrUpdateDataAssetRequest = DataAssetInput


CreateOrUpdateDataAssetResponse = DataAssetOutput


class Input(BaseModel):
    sourceName: str
    sourceType: SourceType
    schemaContents: str
    realDbName: Optional[str] = None
    realDbSchema: Optional[str] = None


class ResponseType(str, Enum):
    DETAILED = "DETAILED"
    COMMENT_MARKDOWN = "COMMENT_MARKDOWN"


class CheckDataAssetsRequest(BaseModel):
    inputs: List[Input]
    includeUnchangedAssets: Optional[bool] = Field(
        default=False,
        description="If true, the data assets sent that have not changed compared with the stored assets will be checked for contract violations. If false, the only data assets that have changed compared with the stored assets will be checked for contract violations.",
    )
    responseType: ResponseType = Field(
        ...,
        description="Determines the format of the response from the API. Specifying 'DETAILED' will return a detailed JSON object for each data asset checked. If 'COMMENT_MARKDOWN' is specified, the response will be a markdown string intended to be used as a comment in a pull request.",
    )
    prLink: Optional[str] = Field(
        default=None,
        description="Link to the pull request the proposed changes to the data asset are part of",
    )


class CheckDataAssetNoContractResponse(BaseModel):
    dataAssetNamespace: str = Field(
        ...,
        description="The namespace of the data asset",
        examples=[
            "postgres://service-one.aaa.eu-west-1.rds.amazonaws.com:5432",
            "protobuf://github.com/org/repo/path/to/file.proto",
        ],
    )
    dataAssetResourceName: DataAssetResourceName = Field(
        ...,
        description="The full resource name of the data asset, see [Data Assets](https://docs.gable.ai/data_assets_and_lineage/data_assets)",
        examples=[
            "postgres://service-one.aaa.eu-west-1.rds.amazonaws.com:5432:serviceone.public.sales",
            "protobuf://git@github.com/org/repo/path/to/file.proto:company.serviceone.Sales",
        ],
    )
    dataAssetPath: str = Field(
        ...,
        description="The relative path of the data asset within its data store",
        examples=["serviceone.public.sales", "company.serviceone.Sales"],
    )
    responseType: Literal["NO_CONTRACT"]


class CheckDataAssetNoChangeResponse(BaseModel):
    dataAssetNamespace: str = Field(
        ...,
        description="The namespace of the data asset",
        examples=[
            "postgres://service-one.aaa.eu-west-1.rds.amazonaws.com:5432",
            "protobuf://github.com/org/repo/path/to/file.proto",
        ],
    )
    dataAssetResourceName: DataAssetResourceName = Field(
        ...,
        description="The full resource name of the data asset, see [Data Assets](https://docs.gable.ai/data_assets_and_lineage/data_assets)",
        examples=[
            "postgres://service-one.aaa.eu-west-1.rds.amazonaws.com:5432:serviceone.public.sales",
            "protobuf://git@github.com/org/repo/path/to/file.proto:company.serviceone.Sales",
        ],
    )
    dataAssetPath: str = Field(
        ...,
        description="The relative path of the data asset within its data store",
        examples=["serviceone.public.sales", "company.serviceone.Sales"],
    )
    responseType: Literal["NO_CHANGE"]


class Subscriber(BaseModel):
    email: Optional[str] = None
    githubHandle: Optional[str] = None
    slackChannel: Optional[str] = None


class CheckDataAssetDetailedResponse(BaseModel):
    dataAssetNamespace: str = Field(
        ...,
        description="The namespace of the data asset",
        examples=[
            "postgres://service-one.aaa.eu-west-1.rds.amazonaws.com:5432",
            "protobuf://github.com/org/repo/path/to/file.proto",
        ],
    )
    dataAssetResourceName: DataAssetResourceName = Field(
        ...,
        description="The full resource name of the data asset, see [Data Assets](https://docs.gable.ai/data_assets_and_lineage/data_assets)",
        examples=[
            "postgres://service-one.aaa.eu-west-1.rds.amazonaws.com:5432:serviceone.public.sales",
            "protobuf://git@github.com/org/repo/path/to/file.proto:company.serviceone.Sales",
        ],
    )
    dataAssetPath: str = Field(
        ...,
        description="The relative path of the data asset within its data store",
        examples=["serviceone.public.sales", "company.serviceone.Sales"],
    )
    contractId: UUID
    contractUrl: str = Field(..., description="Link to the contract in the Gable UI")
    contractNamespace: str
    contractName: str
    contractOwner: str
    contractOwnerGithubHandle: Optional[str] = None
    violations: Optional[List[Violation]] = None
    subscribers: List[Subscriber]
    responseType: Literal["DETAILED"]
    enforcementLevel: Optional[EnforcementLevel] = Field(
        default="INACTIVE", description="alert level for contract"
    )


class CheckDataAssetErrorResponse(BaseModel):
    dataAssetNamespace: str = Field(
        ...,
        description="The namespace of the data asset",
        examples=[
            "postgres://service-one.aaa.eu-west-1.rds.amazonaws.com:5432",
            "protobuf://github.com/org/repo/path/to/file.proto",
        ],
    )
    dataAssetResourceName: Optional[DataAssetResourceName] = Field(
        default=None,
        description="The full resource name of the data asset, see [Data Assets](https://docs.gable.ai/data_assets_and_lineage/data_assets)",
        examples=[
            "postgres://service-one.aaa.eu-west-1.rds.amazonaws.com:5432:serviceone.public.sales",
            "protobuf://git@github.com/org/repo/path/to/file.proto:company.serviceone.Sales",
        ],
    )
    dataAssetPath: Optional[str] = Field(
        default=None,
        description="The relative path of the data asset within its data store",
        examples=["serviceone.public.sales", "company.serviceone.Sales"],
    )
    message: str = Field(..., description="The error message")
    responseType: Literal["ERROR"]
    enforcementLevel: Optional[EnforcementLevel] = Field(
        default=None, description="notification tier of error response"
    )


class CheckDataAssetCommentMarkdownResponse(BaseModel):
    markdown: Optional[str] = None
    shouldAlert: bool = Field(
        ...,
        description="Whether or not to comment on the PR - true if at least one contract with a contract violation has its enforcement level set to ALERT or BLOCK.",
    )
    shouldBlock: bool = Field(
        ...,
        description="Whether or not to block the PR - true if at least one contract with a contract violation has its enforcement level set to BLOCK.",
    )
    errors: Optional[List[CheckDataAssetErrorResponse]] = None
    responseType: Literal["COMMENT_MARKDOWN"]


class StructuredDataAssetResourceName(BaseModel):
    source_type: SourceType
    data_source: str
    path: str


class S3SamplingParameters(BaseModel):
    rowSampleCount: int = Field(..., description="Number of rows sampled per file")
    recentFileCount: Optional[int] = Field(
        default=None,
        description="Number of most recent files whose schemas are sampled per data asset",
    )


class DataAssetFieldProfileBase(BaseModel):
    id: Optional[UUID] = Field(
        default=None,
        description="Unique identifier for the data asset field profile in UUID format",
    )
    sampledRecordsCount: int = Field(..., description="Number of samples")
    nullable: bool = Field(..., description="Whether the data is nullable")
    nullCount: Optional[int] = Field(
        default=None, description="Number of nulls (only defined if nullable is true)"
    )
    sampledFiles: List[str] = Field(
        ..., description="List of sampled files", min_length=1
    )
    sampledDate: Optional[datetime_aliased] = Field(
        default=None, description="The date the sample was taken"
    )
    samplingParameters: S3SamplingParameters


class DataAssetFieldProfileBoolean(DataAssetFieldProfileBase):
    profileType: Literal["boolean"]
    trueCount: int = Field(..., description="Number of true values")
    falseCount: int = Field(..., description="Number of false values")


class DataAssetFieldProfileNumber(DataAssetFieldProfileBase):
    profileType: Literal["number"]
    uniqueCount: int = Field(..., description="Number of unique values")
    min: float = Field(..., description="Minimum value")
    max: float = Field(..., description="Maximum value")


class DataAssetFieldProfileOther(DataAssetFieldProfileBase):
    profileType: Literal["other"]


class DataAssetFieldProfileString(DataAssetFieldProfileBase):
    profileType: Literal["string"]
    uniqueCount: int = Field(..., description="Number of unique values")
    minLength: int = Field(..., description="Minimum length")
    maxLength: int = Field(..., description="Maximum length")
    emptyCount: int = Field(..., description="Number of empty values")


class DataAssetFieldProfileUUID(DataAssetFieldProfileBase):
    profileType: Literal["uuid"]
    uniqueCount: int = Field(..., description="Number of unique values")
    minLength: int = Field(..., description="Minimum length")
    maxLength: int = Field(..., description="Maximum length")
    emptyCount: int = Field(..., description="Number of empty values")
    format: Optional[str] = Field(default=None, description="UUID format")
    uuidVersion: Optional[int] = Field(default=None, description="UUID version")


class DataAssetFieldProfileTemporal(DataAssetFieldProfileBase):
    profileType: Literal["temporal"]
    min: datetime_aliased = Field(..., description="Minimum value")
    max: datetime_aliased = Field(..., description="Maximum value")
    format: str = Field(..., description="Temporal format")


class DataAssetFieldProfileList(DataAssetFieldProfileBase):
    profileType: Literal["list"]
    minLength: int = Field(..., description="Minimum length")
    maxLength: int = Field(..., description="Maximum length")


class PySparkAsset(BaseModel):
    schema_: Dict[str, Any] = Field(
        ..., alias="schema", description="The schema of the data asset"
    )
    git_host: str = Field(
        ...,
        description="The git host where the schema is stored (must match the format of <domain>:<org>/<repo> or <domain>:<org>/<group(s)/<repo>)",
        examples=["github.com:gable/repo"],
        pattern="^[a-zA-Z0-9-\\.]+\\.[a-zA-Z]{2,3}:[a-zA-Z0-9-]+\\/[a-zA-Z0-9-_\\/]+$",
    )
    spark_entrypoint: str = Field(
        ..., description="The entrypoint of the spark job which produced this asset"
    )
    spark_table: str = Field(..., description="The table name for this asset")
    project_name: str = Field(
        ..., description="The project name in which the asset is defined"
    )


class CheckComplianceDataAssetsPySparkRequest(BaseModel):
    assets: List[PySparkAsset] = Field(
        ..., description="Array of data assets to check for compliance"
    )
    includeUnchangedAssets: Optional[bool] = Field(
        default=False,
        description="If true, the data assets sent that have not changed compared with the stored assets will be checked for contract violations. If false, the only data assets that have changed compared with the stored assets will be checked for contract violations.",
    )
    responseType: ResponseType = Field(
        ...,
        description="Determines the format of the response from the API. Specifying 'DETAILED' will return a detailed JSON object for each data asset checked. If 'COMMENT_MARKDOWN' is specified, the response will be a markdown string intended to be used as a comment in a pull request.",
    )
    prLink: Optional[str] = Field(
        default=None,
        description="Link to the pull request the proposed changes to the data asset are part of",
    )


class IngestDataAssetRequest(BaseModel):
    sourceType: SourceType
    sourceNames: List[str] = Field(..., description="The names of the sources")
    databaseSchema: str = Field(..., description="The name of the database schema")
    schema_: List[str] = Field(
        ...,
        alias="schema",
        description="Array of schemas. Each schema could be from a db information schema or the contents of a schema file.",
    )
    dryRun: Optional[bool] = Field(
        default=False, description="If true, no data asset will be registered"
    )
    prLink: Optional[str] = Field(
        default=None,
        description="Link to the PR that may have added or edited the data assets",
        examples=["https://github.com/fakeorg/fakerepo/pull/123"],
    )


class IngestDataAssetResponse(BaseModel):
    message: str = Field(..., description="Response message")
    registered: List[str] = Field(
        ..., description="List of the registered data asset ids"
    )
    success: bool = Field(..., description="Whether the request was successful")


class ErrorResponseDeprecated(BaseModel):
    id: Optional[float] = None
    title: Optional[str] = None
    message: str
    success: Optional[bool] = None


class AssetRegistrationOutcome(BaseModel):
    data_asset_resource_name: StructuredDataAssetResourceName
    error: Optional[str] = Field(
        default=None, description="Error message if registration of this asset failed"
    )


class RegisterDataAssetsResponse(BaseModel):
    asset_registration_outcomes: List[AssetRegistrationOutcome]


class RegisterDataAssetPySparkRequest(BaseModel):
    assets: List[PySparkAsset] = Field(
        ..., description="Array of data assets to register"
    )
    dry_run: Optional[bool] = Field(
        default=False, description="If true, no data asset will be registered"
    )
    prLink: Optional[str] = Field(
        default=None,
        description="Link to the PR that may have added or edited the data assets",
        examples=["https://github.com/fakeorg/fakerepo/pull/123"],
    )


class PiiCategoryEnum(str, Enum):
    phone = "phone"
    email = "email"
    credit_card = "credit_card"
    address = "address"
    person = "person"
    birth_date = "birth_date"
    gender = "gender"
    nationality = "nationality"
    ssn = "ssn"
    zip_code = "zip_code"
    po_box = "po_box"
    user_name = "user_name"
    password = "password"
    religion = "religion"
    sexual_orientation = "sexual_orientation"
    drivers_license = "drivers_license"
    passport = "passport"
    birth_certificate = "birth_certificate"
    medicare = "medicare"
    concession_card = "concession_card"
    fingerprint = "fingerprint"
    face_scan = "face_scan"
    bank_details = "bank_details"
    contact_details = "contact_details"
    tax_file_number = "tax_file_number"


class Status(str, Enum):
    ACTIVE = "ACTIVE"
    DEPRECATED = "DEPRECATED"


class GetApiKeysResponseItem(BaseModel):
    id: Optional[str] = Field(default=None, description="The identifier of the API key")
    name: Optional[str] = Field(default=None, description="The name of the API key")
    value: Optional[str] = Field(default=None, description="The value of the API key")


class GetApiKeysResponse(RootModel["List[GetApiKeysResponseItem]"]):
    root: List[GetApiKeysResponseItem]

    @classmethod
    def construct_error(cls, e):
        from pydantic import ValidationError
        from pydantic.error_wrappers import ErrorWrapper

        class ExceptionProxy(Exception):
            def __init__(self, name, msg):
                self.__class__.__name__ = name
                self.msg = msg

            def __str__(self):
                res = self.msg
                del self.__dict__[
                    "msg"
                ]  # don't render msg twice in pydantic's error reporting
                return res

        filtered_errors = []
        for error in e.errors():
            new_error = {
                **error,
                "loc": tuple(f for f in error["loc"] if f != "root"),
            }
            if new_error not in filtered_errors:
                filtered_errors.append(new_error)
        error_wrappers = []
        for error in filtered_errors:
            msg = error["msg"]
            name = ".".join(error["type"].split(".")[1:])
            exc = ExceptionProxy(name, msg)
            error_wrapper = ErrorWrapper(
                exc=exc,
                loc=tuple(error["loc"]),
            )
            error_wrappers.append(error_wrapper)
        return ValidationError(error_wrappers, cls)

    @classmethod
    def validate(cls, value):
        from pydantic import ValidationError

        try:
            return super().validate(value)
        except ValidationError as e:
            raise cls.construct_error(e)


class GetSsoSamlSetupDetailsResponse(BaseModel):
    ssoUrl: str = Field(
        ...,
        description="The location where the SAML assertion is sent with a HTTP POST, also referred to as the SAML Assertion Consumer Service (ACS) URL.",
    )
    audienceUri: str = Field(
        ...,
        description="(SP Entity Id) The application-defined unique identifier that is the intended audience of the SAML assertion. This is most often the SP Entity ID of your application.",
    )


class SsoSamlUrlConfig(BaseModel):
    model_config = ConfigDict(
        regex_engine="python-re",
    )
    type: Literal["SAML"] = Field(
        ...,
        description="The type of SSO integration, currently only SAML is supported.",
    )
    identityProvider: str = Field(
        ...,
        description='The name of your identity provider. This value will be displayed to users when they log in. \n\nNote: This value cannot be the strings "Google" or "SAML" as they\'re reserved words in our identity management platform.\n',
        examples=["Okta", "GoogleWorkspace", "OneLogin", "JumpCloud"],
        pattern="^(?=[a-zA-Z0-9\\(\\)\\.\\-!@]+$)(?!Google|SAML$).*$",
    )
    metadataDocumentEndpointUrl: str = Field(
        ...,
        description="The URL of the SAML metadata document. Either this or metadataFileContents must be provided.",
        examples=["https://company.okta.com/app/123456789/sso/saml/metadata"],
    )


class SsoSamlFileConfig(BaseModel):
    model_config = ConfigDict(
        regex_engine="python-re",
    )
    type: Literal["SAML"] = Field(
        ...,
        description="The type of SSO integration, currently only SAML is supported.",
    )
    identityProvider: str = Field(
        ...,
        description='The name of your identity provider. This value will be displayed to users when they log in. \n\nNote: This value cannot be the strings "Google" or "SAML" as they\'re reserved words in our identity management platform.\n',
        examples=["Okta", "GoogleWorkspace", "OneLogin", "JumpCloud"],
        pattern="^(?=[a-zA-Z0-9\\(\\)\\.\\-!@]+$)(?!Google|SAML$).*$",
    )
    metadataFileContents: str = Field(
        ...,
        description="The contents of the metadata document. Either this or metadataDocumentEndpointUrl must be provided.",
    )


class SsoConfig(RootModel["Union[SsoSamlUrlConfig, SsoSamlFileConfig]"]):
    root: Union[SsoSamlUrlConfig, SsoSamlFileConfig]

    @classmethod
    def construct_error(cls, e):
        from pydantic import ValidationError
        from pydantic.error_wrappers import ErrorWrapper

        class ExceptionProxy(Exception):
            def __init__(self, name, msg):
                self.__class__.__name__ = name
                self.msg = msg

            def __str__(self):
                res = self.msg
                del self.__dict__[
                    "msg"
                ]  # don't render msg twice in pydantic's error reporting
                return res

        filtered_errors = []
        for error in e.errors():
            new_error = {
                **error,
                "loc": tuple(f for f in error["loc"] if f != "root"),
            }
            if new_error not in filtered_errors:
                filtered_errors.append(new_error)
        error_wrappers = []
        for error in filtered_errors:
            msg = error["msg"]
            name = ".".join(error["type"].split(".")[1:])
            exc = ExceptionProxy(name, msg)
            error_wrapper = ErrorWrapper(
                exc=exc,
                loc=tuple(error["loc"]),
            )
            error_wrappers.append(error_wrapper)
        return ValidationError(error_wrappers, cls)

    @classmethod
    def validate(cls, value):
        from pydantic import ValidationError

        try:
            return super().validate(value)
        except ValidationError as e:
            raise cls.construct_error(e)


class GetUserRequest(BaseModel):
    email: str = Field(
        ...,
        description="The email address of the user, which is the primary ID in Gable",
        pattern="^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$",
    )


class UserStatus(str, Enum):
    ACTIVE = "ACTIVE"
    INVITED = "INVITED"
    DELETED = "DELETED"


class UserRole(str, Enum):
    ADMIN = "ADMIN"
    EDITOR = "EDITOR"
    VIEWER = "VIEWER"


class User(BaseModel):
    email: str = Field(
        ...,
        description="The email address of the user",
        pattern="^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$",
    )
    firstName: Optional[str] = Field(
        default=None, description="The first name of the user, if available"
    )
    lastName: Optional[str] = Field(
        default=None, description="The last name of the user, if available"
    )
    githubHandle: Optional[str] = Field(
        default=None, description="The GitHub handle of the user or team, if available"
    )
    status: UserStatus = Field(..., description="The status of the user")
    role: UserRole = Field(..., description="The role of the user")


class UpdateUserRequest(BaseModel):
    email: str = Field(
        ...,
        description="The email address of the user, which is the primary ID in Gable",
        pattern="^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$",
    )
    firstName: Optional[str] = Field(
        default=None, description="The first name of the user"
    )
    lastName: Optional[str] = Field(
        default=None, description="The last name of the user"
    )
    githubHandle: Optional[str] = Field(
        default=None,
        description="The GitHub handle of the user",
        pattern="^[a-zA-Z0-9-]*$",
    )
    role: Optional[UserRole] = Field(default=None, description="The role of the user")


class UpdateUserResponse(BaseModel):
    email: str = Field(
        ...,
        description="The email address of the user, which is the primary ID in Gable",
        pattern="^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$",
    )
    firstName: Optional[str] = Field(
        default=None, description="The first name of the user"
    )
    lastName: Optional[str] = Field(
        default=None, description="The last name of the user"
    )
    githubHandle: Optional[str] = Field(
        default=None, description="The GitHub handle of the user"
    )
    role: UserRole = Field(..., description="The role of the user")


class GetUsersResponse(RootModel["List[User]"]):
    root: List[User]

    @classmethod
    def construct_error(cls, e):
        from pydantic import ValidationError
        from pydantic.error_wrappers import ErrorWrapper

        class ExceptionProxy(Exception):
            def __init__(self, name, msg):
                self.__class__.__name__ = name
                self.msg = msg

            def __str__(self):
                res = self.msg
                del self.__dict__[
                    "msg"
                ]  # don't render msg twice in pydantic's error reporting
                return res

        filtered_errors = []
        for error in e.errors():
            new_error = {
                **error,
                "loc": tuple(f for f in error["loc"] if f != "root"),
            }
            if new_error not in filtered_errors:
                filtered_errors.append(new_error)
        error_wrappers = []
        for error in filtered_errors:
            msg = error["msg"]
            name = ".".join(error["type"].split(".")[1:])
            exc = ExceptionProxy(name, msg)
            error_wrapper = ErrorWrapper(
                exc=exc,
                loc=tuple(error["loc"]),
            )
            error_wrappers.append(error_wrapper)
        return ValidationError(error_wrappers, cls)

    @classmethod
    def validate(cls, value):
        from pydantic import ValidationError

        try:
            return super().validate(value)
        except ValidationError as e:
            raise cls.construct_error(e)


class InviteUserRequest(BaseModel):
    email: str = Field(
        ...,
        description="The email address of the user, which is the primary ID in Gable",
        pattern="^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$",
    )
    firstName: Optional[str] = Field(
        default=None, description="The first name of the user"
    )
    lastName: Optional[str] = Field(
        default=None, description="The last name of the user"
    )
    resendInvite: Optional[bool] = Field(
        default=None,
        description="The flag which determines whether the invitation should be resent",
    )
    role: Optional[UserRole] = Field(default=None, description="The role of the user")


class DeleteUserRequest(BaseModel):
    email: str = Field(
        ...,
        description="The email address of the user, which is the primary ID in Gable",
        pattern="^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$",
    )


class ChangelogEventType(str, Enum):
    CREATED = "CREATED"
    MODIFIED = "MODIFIED"
    DELETED = "DELETED"
    VIOLATION = "VIOLATION"
    ENABLED = "ENABLED"
    DISABLED = "DISABLED"


class AssetCreatedEvent(ChangelogEventBase):
    entityType: Literal["DATA_ASSET"] = Field(
        ...,
        description="The type of entity that the changelog event is associated with.",
    )
    dataAssetResourceName: DataAssetResourceName
    eventType: ChangelogEventType
    prLink: Optional[str] = Field(
        default=None,
        description="Link to the pull request associated with the asset modification.",
        examples=["https://github.com/fakeorg/fakerepo/pull/1"],
    )
    piiDetected: Optional[Dict[str, Any]] = Field(
        default=None, description="A mapping of the fieldname to the pii_category."
    )


AssetDeletedEvent = AssetCreatedEvent


class Diff(BaseModel):
    fieldName: str = Field(..., description="The name of the field being added.")
    type: str = Field(..., description="The type of the field being added.")


class FieldAddedEvent(BaseModel):
    eventType: Literal["FIELD_ADDED"] = Field(
        ..., description="The type of event that occurred."
    )
    diff: Diff = Field(..., description="Details of the modified field.")


class AttributeName(str, Enum):
    PRECISION_BITS = "PRECISION_BITS"
    SIGNED = "SIGNED"
    TYPE = "TYPE"
    LOGICAL_TYPE = "LOGICAL_TYPE"
    ENUM_VALUES = "ENUM_VALUES"
    NULLABLE = "NULLABLE"
    STRUCTURAL = "STRUCTURAL"
    BYTES = "BYTES"
    VARIABLE = "VARIABLE"
    SCALE = "SCALE"
    PRECISION = "PRECISION"
    UNIT = "UNIT"
    TIMEZONE = "TIMEZONE"
    VALUE = "VALUE"
    SEMANTIC = "SEMANTIC"
    DOC = "DOC"


class Diff1(BaseModel):
    fieldName: str = Field(..., description="The name of the field being modified.")
    attributeName: AttributeName = Field(
        ..., description="The name of the attribute being modified."
    )
    previousValue: str = Field(
        ..., description="The type of the field before the change."
    )
    newValue: str = Field(..., description="The type of the field after the change.")
    summary: Optional[str] = Field(
        default=None, description="A very brief summary of the change for the field."
    )
    description: Optional[str] = Field(
        default=None, description="A detailed description of the change."
    )


class FieldModifiedEvent(BaseModel):
    eventType: Literal["FIELD_MODIFIED"] = Field(
        ..., description="The type of event that occurred."
    )
    diff: Diff1 = Field(..., description="Details of the modified field.")


class Diff2(BaseModel):
    fieldName: str = Field(..., description="The name of the field being deleted.")
    type: str = Field(..., description="The type of the field being deleted.")


class FieldDeletedEvent(BaseModel):
    eventType: Literal["FIELD_DELETED"] = Field(
        ..., description="The type of event that occurred."
    )
    diff: Diff2 = Field(..., description="Details of the modified field.")


class AssetModifiedEvent(AssetCreatedEvent):
    modifications: List[
        Union[FieldAddedEvent, FieldModifiedEvent, FieldDeletedEvent]
    ] = Field(
        ...,
        description="List of changelog modifications in chronological order (newest first)",
    )
    piiDetected: Optional[Dict[str, Any]] = Field(
        default=None, description="A mapping of the fieldname to the pii_category."
    )


class ContractCreatedEvent(ChangelogEventBase):
    entityType: Literal["DATA_CONTRACT"] = Field(
        ...,
        description="The type of entity that the changelog event is associated with.",
    )
    eventType: ChangelogEventType
    contractId: UUID = Field(..., description="The unique identifier for the contract.")
    contractName: Optional[str] = Field(
        default=None, description="The name of the contract."
    )
    contractDomain: Optional[str] = Field(
        default=None, description="The domain of the contract."
    )


class ContractModifiedEvent(ContractCreatedEvent):
    modifications: List[
        Union[FieldAddedEvent, FieldModifiedEvent, FieldDeletedEvent]
    ] = Field(
        ...,
        description="List of changelog modifications in chronological order (newest first)",
    )


class ContractDeleted(BaseModel):
    contractId: UUID = Field(
        ..., description="Unique identifier for the contract in UUID format"
    )
    entityType: Literal["DATA_CONTRACT"] = Field(
        ...,
        description="The type of entity that the changelog event is associated with.",
    )
    eventType: Literal["DELETED"] = Field(
        ..., description="The type of event that occurred"
    )
    userId: Optional[UUID] = Field(
        default=None, description="Unique identifier for the user in UUID format"
    )
    contractName: str = Field(..., description="Name of the contract")
    contractDomain: str = Field(..., description="Domain/namespace of the contract")


class ContractDeletedEvent(ChangelogEventBase, ContractDeleted):
    pass


class ActionCreatedEvent(ChangelogEventBase):
    entityType: Literal["ACTION"] = Field(
        ...,
        description="The type of entity that the changelog event is associated with.",
    )
    eventType: ChangelogEventType
    actionId: UUID = Field(..., description="The unique identifier for the action.")
    actionName: str = Field(..., description="The name of the action.")


ActionDeletedEvent = ActionCreatedEvent


ActionEnabledEvent = ActionCreatedEvent


ActionDisabledEvent = ActionCreatedEvent


class ActionTriggerType(str, Enum):
    source = "source"
    source_type = "source_type"
    event_name = "event_name"
    namespace = "namespace"


class Diff3(BaseModel):
    triggerName: str = Field(..., description="The name of the trigger being added.")
    type: ActionTriggerType = Field(
        ..., description="The type of the trigger being added."
    )


class TriggerAddedEvent(BaseModel):
    eventType: Literal["TRIGGER_ADDED"] = Field(
        ..., description="The type of event that occurred."
    )
    diff: Diff3 = Field(..., description="Details of the modified trigger.")


class Diff4(BaseModel):
    triggerName: str = Field(..., description="The name of the trigger being deleted.")
    type: ActionTriggerType = Field(
        ..., description="The type of the trigger being deleted."
    )


class TriggerDeletedEvent(BaseModel):
    eventType: Literal["TRIGGER_DELETED"] = Field(
        ..., description="The type of event that occurred."
    )
    diff: Diff4 = Field(..., description="Details of the modified trigger.")


class ActionDestinationType(str, Enum):
    slack = "slack"
    email = "email"
    webhook = "webhook"


class Diff5(BaseModel):
    destinationName: str = Field(
        ..., description="The name of the destination being added."
    )
    type: ActionDestinationType = Field(
        ..., description="The type of the destination being added."
    )


class DestinationAddedEvent(BaseModel):
    eventType: Literal["DESTINATION_ADDED"] = Field(
        ..., description="The type of event that occurred."
    )
    diff: Diff5 = Field(..., description="Details of the modified destination.")


class Diff6(BaseModel):
    destinationName: str = Field(
        ..., description="The name of the destination being deleted."
    )
    type: ActionDestinationType = Field(
        ..., description="The type of the destination being deleted."
    )


class DestinationDeletedEvent(BaseModel):
    eventType: Literal["DESTINATION_DELETED"] = Field(
        ..., description="The type of event that occurred."
    )
    diff: Diff6 = Field(..., description="Details of the modified destination.")


class Diff7(BaseModel):
    type: ActionDestinationType = Field(
        ..., description="The type of the destination being modified."
    )
    previousValues: Optional[List[str]] = Field(
        default=None, description="Names of the destination before the change."
    )
    newValues: Optional[List[str]] = Field(
        default=None, description="Names of the destination after the change."
    )
    summary: Optional[str] = Field(
        default=None,
        description="A very brief summary of the change for the destination.",
    )
    description: Optional[str] = Field(
        default=None, description="A detailed description of the change."
    )


class DestinationModifiedEvent(BaseModel):
    eventType: Literal["DESTINATION_MODIFIED"] = Field(
        ..., description="The type of event that occurred."
    )
    diff: Diff7 = Field(..., description="Details of the modified destination.")


class Diff8(BaseModel):
    previousValue: str = Field(
        ..., description="The name of the action before the change."
    )
    newValue: str = Field(..., description="The name of the action after the change.")


class ActionRenamedEvent(BaseModel):
    eventType: Literal["ACTION_RENAMED"] = Field(
        ..., description="The type of event that occurred."
    )
    diff: Diff8 = Field(..., description="Details of the modified action.")


class ActionModifiedEvent(ActionCreatedEvent):
    modifications: List[
        Union[
            TriggerAddedEvent,
            TriggerDeletedEvent,
            DestinationAddedEvent,
            DestinationDeletedEvent,
            DestinationModifiedEvent,
            ActionRenamedEvent,
        ]
    ] = Field(
        ...,
        description="List of changelog modifications in chronological order (newest first)",
    )


class GetChangelogResponse(BaseModel):
    changelogEvents: List[
        Union[
            AssetCreatedEvent,
            AssetDeletedEvent,
            AssetModifiedEvent,
            ContractCreatedEvent,
            ContractModifiedEvent,
            ContractViolationEvent,
            ContractDeletedEvent,
            ActionCreatedEvent,
            ActionDeletedEvent,
            ActionEnabledEvent,
            ActionDisabledEvent,
            ActionModifiedEvent,
        ]
    ] = Field(
        ...,
        description="List of changelog events in chronological order (oldest first)",
    )


class ChangelogResponsePaginated(BaseModel):
    data: Optional[GetChangelogResponse] = None
    totalCount: Optional[float] = None


class ChangelogEventInput(RootModel["ContractViolation"]):
    root: ContractViolation

    @classmethod
    def construct_error(cls, e):
        from pydantic import ValidationError
        from pydantic.error_wrappers import ErrorWrapper

        class ExceptionProxy(Exception):
            def __init__(self, name, msg):
                self.__class__.__name__ = name
                self.msg = msg

            def __str__(self):
                res = self.msg
                del self.__dict__[
                    "msg"
                ]  # don't render msg twice in pydantic's error reporting
                return res

        filtered_errors = []
        for error in e.errors():
            new_error = {
                **error,
                "loc": tuple(f for f in error["loc"] if f != "root"),
            }
            if new_error not in filtered_errors:
                filtered_errors.append(new_error)
        error_wrappers = []
        for error in filtered_errors:
            msg = error["msg"]
            name = ".".join(error["type"].split(".")[1:])
            exc = ExceptionProxy(name, msg)
            error_wrapper = ErrorWrapper(
                exc=exc,
                loc=tuple(error["loc"]),
            )
            error_wrappers.append(error_wrapper)
        return ValidationError(error_wrappers, cls)

    @classmethod
    def validate(cls, value):
        from pydantic import ValidationError

        try:
            return super().validate(value)
        except ValidationError as e:
            raise cls.construct_error(e)


class CreateChangelogEventRequest(
    RootModel["Union[ChangelogEventInput, List[ChangelogEventInput]]"]
):
    root: Union[ChangelogEventInput, List[ChangelogEventInput]]

    @classmethod
    def construct_error(cls, e):
        from pydantic import ValidationError
        from pydantic.error_wrappers import ErrorWrapper

        class ExceptionProxy(Exception):
            def __init__(self, name, msg):
                self.__class__.__name__ = name
                self.msg = msg

            def __str__(self):
                res = self.msg
                del self.__dict__[
                    "msg"
                ]  # don't render msg twice in pydantic's error reporting
                return res

        filtered_errors = []
        for error in e.errors():
            new_error = {
                **error,
                "loc": tuple(f for f in error["loc"] if f != "root"),
            }
            if new_error not in filtered_errors:
                filtered_errors.append(new_error)
        error_wrappers = []
        for error in filtered_errors:
            msg = error["msg"]
            name = ".".join(error["type"].split(".")[1:])
            exc = ExceptionProxy(name, msg)
            error_wrapper = ErrorWrapper(
                exc=exc,
                loc=tuple(error["loc"]),
            )
            error_wrappers.append(error_wrapper)
        return ValidationError(error_wrappers, cls)

    @classmethod
    def validate(cls, value):
        from pydantic import ValidationError

        try:
            return super().validate(value)
        except ValidationError as e:
            raise cls.construct_error(e)


class CreateChangelogEventResponse(BaseModel):
    changelogEventIds: List[UUID] = Field(
        ..., description="List of changelog event IDs that were create"
    )


class Domain(BaseModel):
    value: str
    type: SourceType


class GetChangelogFiltersResponse(BaseModel):
    domains: List[Domain] = Field(..., description="Domains list")
    types: List[SourceType] = Field(..., description="Types list")


class Channel(BaseModel):
    id: Optional[str] = Field(default=None, description="The channel id")
    name: Optional[str] = Field(default=None, description="The channel name")


class GetSlackChannelsResponse(BaseModel):
    channels: List[Channel]
    workspaceName: Optional[str] = Field(
        default=None,
        description="Slack workspace name; Slack Client not valid if missing",
    )


class GetSlackInstallResponse(BaseModel):
    slackInstallUrl: str = Field(..., description="URL to install Slack Client")


class PostTestSlackMessageRequest(BaseModel):
    channelId: str = Field(
        ...,
        description="The slack channel id name to which the test message will be sent",
    )


class PostTestSlackMessageResponse(BaseModel):
    message: Optional[str] = Field(
        default=None, description="Message that indicates successful action"
    )


class Webhook(BaseModel):
    id: str = Field(..., description="Endpoint id")
    url: str = Field(..., description="Endpoint url")
    name: str = Field(..., description="Endpoint name")
    secret: str = Field(..., description="Endpoint secret")
    headers: Optional[Dict[str, str]] = Field(
        default=None, description="A map of headers"
    )


class GetWebhooksResponse(BaseModel):
    webhooks: List[Webhook]


class CreateWebhookRequest(BaseModel):
    url: str = Field(..., description="The URL that the webhooks will be sent to")
    name: str = Field(..., description="The webhook name")
    headers: Optional[Dict[str, str]] = Field(
        default=None, description="A map of headers"
    )


class CreateWebhookResponse(BaseModel):
    id: str = Field(..., description="The id of a created webhook")
    url: str = Field(..., description="The url of a created webhook")
    name: str = Field(..., description="The name of a created webhook")
    headers: Optional[Dict[str, str]] = Field(
        default=None, description="A map of headers"
    )


class PostTestWebhookMessageRequest(BaseModel):
    eventType: str = Field(
        ..., description="The webhook event type to which the test message will be sent"
    )


class PostTestWebhookMessageResponse(BaseModel):
    messageId: str = Field(..., description="The message id")


class Status1(str, Enum):
    PENDING = "PENDING"
    SENT = "SENT"
    FAILED = "FAILED"


class Notification(BaseModel):
    id: str = Field(
        ..., description="The unique identifier of the created notification"
    )
    changeLogEventId: str = Field(
        ...,
        description="The unique identifier of the change log event associated with the notification",
    )
    actionId: str = Field(
        ...,
        description="The unique identifier of the action associated with the notification",
    )
    actionDetails: Dict[str, Any] = Field(
        ...,
        description="Additional details about the action associated with the notification",
    )
    status: Status1 = Field(
        ...,
        description='The current status of the notification (e.g., "PENDING", "SENT", "FAILED")',
    )
    createdAt: datetime_aliased = Field(
        ..., description="The timestamp when the notification was created"
    )
    updatedAt: Optional[datetime_aliased] = Field(
        default=None, description="The timestamp when the notification was last updated"
    )
    retryCount: int = Field(
        ..., description="The number of times the notification has been retried", ge=0
    )
    actionName: str = Field(
        ..., description="The name of the actions associated with the notification"
    )
    userName: Optional[str] = Field(
        default=None, description="The name of the user who created the action"
    )


class GetNotificationsResponse(BaseModel):
    notifications: List[Notification]


class NumberConstraintEnum(str, Enum):
    greaterThan = "greaterThan"
    greaterThanOrEqualTo = "greaterThanOrEqualTo"
    lessThan = "lessThan"
    lessThanOrEqualTo = "lessThanOrEqualTo"
    isNull = "isNull"
    isNullThreshold = "isNullThreshold"


class TimeConstraints(BaseModel):
    type: Literal["time"]
    constraints: List[NumberConstraintEnum]


class FloatConstraints(BaseModel):
    type: Literal["float"]
    constraints: List[NumberConstraintEnum]


class IntegerConstraints(BaseModel):
    type: Literal["integer"]
    constraints: List[NumberConstraintEnum]


class LengthConstraintEnum(str, Enum):
    length = "length"
    isNotEmpty = "isNotEmpty"
    isNull = "isNull"
    isNullThreshold = "isNullThreshold"


class ListConstraints(BaseModel):
    type: Literal["list"]
    constraints: List[LengthConstraintEnum]


class MapConstraints(BaseModel):
    type: Literal["map"]
    constraints: List[LengthConstraintEnum]


class StringConstraintEnum(str, Enum):
    charLength = "charLength"
    isNotEmpty = "isNotEmpty"
    isNull = "isNull"
    isNullThreshold = "isNullThreshold"


class StringConstraints(BaseModel):
    type: Literal["string"]
    constraints: List[StringConstraintEnum]


class UUIDConstraints(BaseModel):
    type: Literal["uuid"]
    constraints: List[StringConstraintEnum]


class OtherConstraintEnum(str, Enum):
    isNull = "isNull"
    isNullThreshold = "isNullThreshold"


class BooleanConstraints(BaseModel):
    type: Literal["boolean"]
    constraints: List[OtherConstraintEnum]


class OtherConstraints(BaseModel):
    type: Literal["other"]
    constraints: List[OtherConstraintEnum]


class RecapTypeConstraintCategory(str, Enum):
    NUMBER = "NUMBER"
    TIME = "TIME"
    STRING = "STRING"
    BYTES = "BYTES"
    DATA_STRUCTURE = "DATA_STRUCTURE"
    OTHER = "OTHER"


class RecapConstraints(BaseModel):
    type: Literal["recap"]
    constraints: List[RecapTypeConstraintCategory]


class ContractConstraintType(str, Enum):
    GREATER_THAN = "greaterThan"
    GREATER_THAN_OR_EQUAL_TO = "greaterThanOrEqualTo"
    LESS_THAN = "lessThan"
    LESS_THAN_OR_EQUAL_TO = "lessThanOrEqualTo"
    IS_NULL = "isNull"
    IS_NULL_THRESHOLD = "isNullThreshold"
    CHARACTER_LENGTH = "charLength"
    IS_NOT_EMPTY = "isNotEmpty"
    LENGTH = "length"


class SupportedContractConstraints(BaseModel):
    type: Literal["contract"]
    constraints: List[ContractConstraintType]


class ContractConstraint(
    RootModel[
        "Union[TimeConstraints, FloatConstraints, IntegerConstraints, ListConstraints, MapConstraints, StringConstraints, UUIDConstraints, BooleanConstraints, OtherConstraints, RecapConstraints, SupportedContractConstraints]"
    ]
):
    root: Union[
        TimeConstraints,
        FloatConstraints,
        IntegerConstraints,
        ListConstraints,
        MapConstraints,
        StringConstraints,
        UUIDConstraints,
        BooleanConstraints,
        OtherConstraints,
        RecapConstraints,
        SupportedContractConstraints,
    ] = Field(..., discriminator="type")

    @classmethod
    def construct_error(cls, e):
        from pydantic import ValidationError
        from pydantic.error_wrappers import ErrorWrapper

        class ExceptionProxy(Exception):
            def __init__(self, name, msg):
                self.__class__.__name__ = name
                self.msg = msg

            def __str__(self):
                res = self.msg
                del self.__dict__[
                    "msg"
                ]  # don't render msg twice in pydantic's error reporting
                return res

        filtered_errors = []
        for error in e.errors():
            new_error = {
                **error,
                "loc": tuple(f for f in error["loc"] if f != "root"),
            }
            if new_error not in filtered_errors:
                filtered_errors.append(new_error)
        error_wrappers = []
        for error in filtered_errors:
            msg = error["msg"]
            name = ".".join(error["type"].split(".")[1:])
            exc = ExceptionProxy(name, msg)
            error_wrapper = ErrorWrapper(
                exc=exc,
                loc=tuple(error["loc"]),
            )
            error_wrappers.append(error_wrapper)
        return ValidationError(error_wrappers, cls)

    @classmethod
    def validate(cls, value):
        from pydantic import ValidationError

        try:
            return super().validate(value)
        except ValidationError as e:
            raise cls.construct_error(e)


class ContractConstraintMapping(RootModel["Optional[Dict[str, ContractConstraint]]"]):
    root: Optional[Dict[str, ContractConstraint]] = None

    @classmethod
    def construct_error(cls, e):
        from pydantic import ValidationError
        from pydantic.error_wrappers import ErrorWrapper

        class ExceptionProxy(Exception):
            def __init__(self, name, msg):
                self.__class__.__name__ = name
                self.msg = msg

            def __str__(self):
                res = self.msg
                del self.__dict__[
                    "msg"
                ]  # don't render msg twice in pydantic's error reporting
                return res

        filtered_errors = []
        for error in e.errors():
            new_error = {
                **error,
                "loc": tuple(f for f in error["loc"] if f != "root"),
            }
            if new_error not in filtered_errors:
                filtered_errors.append(new_error)
        error_wrappers = []
        for error in filtered_errors:
            msg = error["msg"]
            name = ".".join(error["type"].split(".")[1:])
            exc = ExceptionProxy(name, msg)
            error_wrapper = ErrorWrapper(
                exc=exc,
                loc=tuple(error["loc"]),
            )
            error_wrappers.append(error_wrapper)
        return ValidationError(error_wrappers, cls)

    @classmethod
    def validate(cls, value):
        from pydantic import ValidationError

        try:
            return super().validate(value)
        except ValidationError as e:
            raise cls.construct_error(e)


class IngestScaMetadataRequest(BaseModel):
    metadata: Dict[str, Any]
    dataAssetResourceName: DataAssetResourceName


class IngestScaMetadataResponse(BaseModel):
    newVersionCreated: Optional[bool] = Field(
        default=None, description="Indicates whether a new version was created"
    )


class TelemetryType(RootModel["Literal['SCA_PRIME']"]):
    root: Literal["SCA_PRIME"] = Field(
        ..., description="The type of the telemetry event."
    )

    @classmethod
    def construct_error(cls, e):
        from pydantic import ValidationError
        from pydantic.error_wrappers import ErrorWrapper

        class ExceptionProxy(Exception):
            def __init__(self, name, msg):
                self.__class__.__name__ = name
                self.msg = msg

            def __str__(self):
                res = self.msg
                del self.__dict__[
                    "msg"
                ]  # don't render msg twice in pydantic's error reporting
                return res

        filtered_errors = []
        for error in e.errors():
            new_error = {
                **error,
                "loc": tuple(f for f in error["loc"] if f != "root"),
            }
            if new_error not in filtered_errors:
                filtered_errors.append(new_error)
        error_wrappers = []
        for error in filtered_errors:
            msg = error["msg"]
            name = ".".join(error["type"].split(".")[1:])
            exc = ExceptionProxy(name, msg)
            error_wrapper = ErrorWrapper(
                exc=exc,
                loc=tuple(error["loc"]),
            )
            error_wrappers.append(error_wrapper)
        return ValidationError(error_wrappers, cls)

    @classmethod
    def validate(cls, value):
        from pydantic import ValidationError

        try:
            return super().validate(value)
        except ValidationError as e:
            raise cls.construct_error(e)


class CreateTelemetryRequest(BaseModel):
    id: Optional[UUID] = Field(
        default=None,
        description="The unique identifier of the telemetry event. If not provided, a random UUID will be generated.",
    )
    data: Dict[str, Any] = Field(
        ..., description="The data payload of the telemetry event."
    )
    type: TelemetryType


class CreateTelemetryResponse(BaseModel):
    id: UUID = Field(..., description="The id of the created telemetry event")


class GetContractNamespacesResponse(BaseModel):
    namespaces: List[str] = Field(..., description="List of contract namespaces")


class ComponentMetrics(BaseModel):
    ingressCount: int = Field(..., description="Number of ingress records.")
    egressCount: int = Field(..., description="Number of egress records.")
    pathCount: int = Field(..., description="Number of paths.")


class ComponentCodeInfo(BaseModel):
    repoUri: AnyUrl = Field(..., description="URL of the Git repository.")
    projectRoot: str = Field(
        ..., description="Root directory of the project within the repository."
    )


class Component(BaseModel):
    id: UUID = Field(..., description="The unique identifier for the component.")
    name: str = Field(..., description="The name of the component.")
    type: str = Field(..., description="The type of the component.")
    repoName: str = Field(
        ..., description="The repository name or path associated with the component."
    )
    lastUpdated: date = Field(
        ..., description="The date when the component was last updated."
    )
    metrics: ComponentMetrics = Field(
        ...,
        description="Counts of ingresses, egresses and paths recorded for this component.",
    )
    codeInfo: ComponentCodeInfo = Field(
        ..., description="Repository metadata for this component."
    )


class CodeURI(BaseModel):
    file: str = Field(..., description="Relative file path in the repo.")
    lineStart: int
    lineEnd: Optional[int] = None
    columnStart: int
    columnEnd: Optional[int] = None


class CodeAnchor(BaseModel):
    anchorId: str = Field(..., description="Unique identifier for the anchor.")
    name: str = Field(..., description="Human readable name of the code anchor.")
    codeUri: CodeURI
    url: Optional[str] = Field(default=None, description="URL to the code anchor.")


class Ingress1(BaseModel):
    id: UUID = Field(
        ..., description="The unique identifier of the ingress where the path starts."
    )
    codeAnchorId: UUID = Field(
        ...,
        description="The unique identifier of the code anchor node where the path starts.",
    )


class Egress1(BaseModel):
    id: UUID = Field(
        ..., description="The unique identifier of the egress where the path ends."
    )
    codeAnchorId: UUID = Field(
        ...,
        description="The unique identifier of the code anchor node where the path starts.",
    )


class FieldMapping(BaseModel):
    ingressField: str = Field(
        ..., description="The name of the ingress field in the mapping."
    )
    egressField: str = Field(
        ..., description="The name of the egress field in the mapping."
    )
    notes: Optional[str] = Field(
        default=None, description="Additional notes about the field mapping."
    )


class Path(BaseModel):
    ingress: Ingress1
    egress: Egress1
    codeAnchors: List[CodeAnchor] = Field(
        ..., description="A list of node IDs that are part of this path."
    )
    fieldMappings: List[FieldMapping] = Field(
        ...,
        description="A list of field mappings that describe how fields are mapped along the path.",
    )


class StaticAnalysisCodeMetadata(BaseModel):
    repo_uri: str = Field(..., description="URI of the code repository")
    repo_branch: str = Field(..., description="Branch of the code repository")
    repo_commit: str = Field(..., description="Commit hash of the code")
    project_root: str = Field(
        ..., description="Root directory targeted by the static analysis tool"
    )


class StaticAnalysisSignature(BaseModel):
    class_names: List[str] = Field(..., description="The names of the classes")
    method_names: List[str] = Field(..., description="The names of the methods")
    kind: str = Field(
        ...,
        description='Unique name for the ingress/egress signature (e.g. "postgres", "gateway_response"). All the signatures with the same kind are grouped together.',
    )


class StaticAnalysisToolConfig(BaseModel):
    ingress_signatures: List[StaticAnalysisSignature]
    egress_signatures: List[StaticAnalysisSignature]


class StaticAnalysisToolMetadata(BaseModel):
    name: str = Field(..., description="Name of the SCA tool used")
    version: str = Field(..., description="Version of the SCA tool")
    config: Optional[StaticAnalysisToolConfig] = None


class PostScaStartRunRequest(BaseModel):
    code_info: StaticAnalysisCodeMetadata
    sca_info: StaticAnalysisToolMetadata


class S3PresignedUrl(BaseModel):
    url: str = Field(..., description="The signed S3 URL")
    fields: Dict[str, str] = Field(..., description="The fields for the presigned post")


class PostScaStartRunResponse(BaseModel):
    runId: str = Field(..., description="The unique run ID assigned to this SCA run")
    s3PresignedUrl: Optional[S3PresignedUrl] = Field(
        default=None, description="Details for uploading files to S3"
    )


class StaticAnalysisMachineMetadata(BaseModel):
    cpu_cores: int = Field(..., description="Number of CPU cores available")
    ram_gb: float = Field(..., description="Total RAM in gigabytes")


class StaticAnalysisStartRunRequest(BaseModel):
    code_info: StaticAnalysisCodeMetadata
    sca_tool: StaticAnalysisToolMetadata
    machine_info: StaticAnalysisMachineMetadata


class StaticAnalysisStartRunResponse(BaseModel):
    run_id: UUID = Field(..., description="Unique identifier assigned to the run")
    created_at: datetime_aliased = Field(
        ..., description="Timestamp indicating when the run was created"
    )


class StaticAnalysisCodeURI(BaseModel):
    file: str = Field(..., description="Relative file path in the repo")
    line_start: int
    line_end: Optional[int] = None
    column_start: int
    column_end: Optional[int] = None


class StaticAnalysisCodeAnchor(BaseModel):
    anchor_id: str = Field(
        ...,
        description="Unique identifier for the anchor. Analogous to a merge key. Conceptually, an anchor represents a point in code which is ideally stable to some set of changes to surrounding code. Generated by the tool.",
    )
    code_uri: StaticAnalysisCodeURI


class StaticAnalysisTabularLineageFieldMapping(BaseModel):
    ingress_field: str = Field(..., description="The field in the ingress schema")
    egress_field: str = Field(..., description="The field in the egress schema")
    notes: Optional[str] = Field(default=None, description="Notes about the mapping")


class GableSchemaField(
    RootModel[
        "Union[GableSchemaFieldStruct, GableSchemaFieldNull, GableSchemaFieldBool, GableSchemaFieldInt, GableSchemaFieldFloat, GableSchemaFieldString, GableSchemaFieldBytes, GableSchemaFieldList, GableSchemaFieldMap, GableSchemaFieldEnum, GableSchemaFieldUnion, GableSchemaFieldAliasReference, GableSchemaFieldUnknown]"
    ]
):
    root: Union[
        GableSchemaFieldStruct,
        GableSchemaFieldNull,
        GableSchemaFieldBool,
        GableSchemaFieldInt,
        GableSchemaFieldFloat,
        GableSchemaFieldString,
        GableSchemaFieldBytes,
        GableSchemaFieldList,
        GableSchemaFieldMap,
        GableSchemaFieldEnum,
        GableSchemaFieldUnion,
        GableSchemaFieldAliasReference,
        GableSchemaFieldUnknown,
    ]

    @classmethod
    def construct_error(cls, e):
        from pydantic import ValidationError
        from pydantic.error_wrappers import ErrorWrapper

        class ExceptionProxy(Exception):
            def __init__(self, name, msg):
                self.__class__.__name__ = name
                self.msg = msg

            def __str__(self):
                res = self.msg
                del self.__dict__[
                    "msg"
                ]  # don't render msg twice in pydantic's error reporting
                return res

        filtered_errors = []
        for error in e.errors():
            new_error = {
                **error,
                "loc": tuple(f for f in error["loc"] if f != "root"),
            }
            if new_error not in filtered_errors:
                filtered_errors.append(new_error)
        error_wrappers = []
        for error in filtered_errors:
            msg = error["msg"]
            name = ".".join(error["type"].split(".")[1:])
            exc = ExceptionProxy(name, msg)
            error_wrapper = ErrorWrapper(
                exc=exc,
                loc=tuple(error["loc"]),
            )
            error_wrappers.append(error_wrapper)
        return ValidationError(error_wrappers, cls)

    @classmethod
    def validate(cls, value):
        from pydantic import ValidationError

        try:
            return super().validate(value)
        except ValidationError as e:
            raise cls.construct_error(e)


class GableSchemaFieldStruct(BaseModel):
    type: Literal["struct"]
    name: Optional[str] = None
    fields: Optional[List[GableSchemaField]] = None
    alias: Optional[str] = Field(
        default=None, pattern="^[a-zA-Z_][a-zA-Z0-9_]*(?:\\.[a-zA-Z_][a-zA-Z0-9_]*)+$"
    )
    doc: Optional[str] = None
    logical: Optional[str] = None
    optional: Optional[bool] = None


class GableSchemaStruct(BaseModel):
    type: Literal["struct"]
    name: Optional[str] = None
    fields: Optional[List[GableSchemaField]] = None
    alias: Optional[str] = Field(
        default=None, pattern="^[a-zA-Z_][a-zA-Z0-9_]*(?:\\.[a-zA-Z_][a-zA-Z0-9_]*)+$"
    )
    doc: Optional[str] = None
    logical: Optional[str] = None


class GableSchemaType(
    RootModel[
        "Union[GableSchemaTypeName, Union[GableSchemaStruct, GableSchemaNull, GableSchemaBool, GableSchemaInt, GableSchemaFloat, GableSchemaString, GableSchemaBytes, GableSchemaList, GableSchemaMap, GableSchemaEnum, GableSchemaUnion, GableSchemaAliasReference, GableSchemaUnknown]]"
    ]
):
    root: Union[
        GableSchemaTypeName,
        Union[
            GableSchemaStruct,
            GableSchemaNull,
            GableSchemaBool,
            GableSchemaInt,
            GableSchemaFloat,
            GableSchemaString,
            GableSchemaBytes,
            GableSchemaList,
            GableSchemaMap,
            GableSchemaEnum,
            GableSchemaUnion,
            GableSchemaAliasReference,
            GableSchemaUnknown,
        ],
    ]

    @classmethod
    def construct_error(cls, e):
        from pydantic import ValidationError
        from pydantic.error_wrappers import ErrorWrapper

        class ExceptionProxy(Exception):
            def __init__(self, name, msg):
                self.__class__.__name__ = name
                self.msg = msg

            def __str__(self):
                res = self.msg
                del self.__dict__[
                    "msg"
                ]  # don't render msg twice in pydantic's error reporting
                return res

        filtered_errors = []
        for error in e.errors():
            new_error = {
                **error,
                "loc": tuple(f for f in error["loc"] if f != "root"),
            }
            if new_error not in filtered_errors:
                filtered_errors.append(new_error)
        error_wrappers = []
        for error in filtered_errors:
            msg = error["msg"]
            name = ".".join(error["type"].split(".")[1:])
            exc = ExceptionProxy(name, msg)
            error_wrapper = ErrorWrapper(
                exc=exc,
                loc=tuple(error["loc"]),
            )
            error_wrappers.append(error_wrapper)
        return ValidationError(error_wrappers, cls)

    @classmethod
    def validate(cls, value):
        from pydantic import ValidationError

        try:
            return super().validate(value)
        except ValidationError as e:
            raise cls.construct_error(e)


class GableSchemaList(BaseModel):
    type: Literal["list"]
    values: GableSchemaType
    length: Optional[int] = Field(default=None, ge=1, le=9223372036854776000)
    variable: Optional[bool] = True
    alias: Optional[str] = Field(
        default=None, pattern="^[a-zA-Z_][a-zA-Z0-9_]*(?:\\.[a-zA-Z_][a-zA-Z0-9_]*)+$"
    )
    doc: Optional[str] = None


class GableSchemaMap(BaseModel):
    type: Literal["map"]
    keys: GableSchemaType
    values: GableSchemaType
    alias: Optional[str] = Field(
        default=None, pattern="^[a-zA-Z_][a-zA-Z0-9_]*(?:\\.[a-zA-Z_][a-zA-Z0-9_]*)+$"
    )
    doc: Optional[str] = None
    logical: Optional[str] = None


class GableSchemaUnion(BaseModel):
    type: Literal["union"]
    types: List[GableSchemaType]
    alias: Optional[str] = Field(
        default=None, pattern="^[a-zA-Z_][a-zA-Z0-9_]*(?:\\.[a-zA-Z_][a-zA-Z0-9_]*)+$"
    )
    doc: Optional[str] = None
    logical: Optional[str] = None


class GableSchemaFieldList(BaseModel):
    type: Literal["list"]
    values: GableSchemaType
    length: Optional[int] = Field(default=None, ge=1, le=9223372036854776000)
    variable: Optional[bool] = True
    alias: Optional[str] = Field(
        default=None, pattern="^[a-zA-Z_][a-zA-Z0-9_]*(?:\\.[a-zA-Z_][a-zA-Z0-9_]*)+$"
    )
    doc: Optional[str] = None
    name: Optional[str] = None
    optional: Optional[bool] = None


class GableSchemaFieldMap(BaseModel):
    type: Literal["map"]
    keys: GableSchemaType
    values: GableSchemaType
    alias: Optional[str] = Field(
        default=None, pattern="^[a-zA-Z_][a-zA-Z0-9_]*(?:\\.[a-zA-Z_][a-zA-Z0-9_]*)+$"
    )
    doc: Optional[str] = None
    logical: Optional[str] = None
    name: Optional[str] = None
    optional: Optional[bool] = None


class GableSchemaFieldUnion(BaseModel):
    type: Literal["union"]
    types: List[GableSchemaType]
    alias: Optional[str] = Field(
        default=None, pattern="^[a-zA-Z_][a-zA-Z0-9_]*(?:\\.[a-zA-Z_][a-zA-Z0-9_]*)+$"
    )
    doc: Optional[str] = None
    logical: Optional[str] = None
    name: Optional[str] = None
    optional: Optional[bool] = None


class GableSchemaContractField(
    RootModel[
        "Union[GableSchemaContractField2, GableSchemaContractField3, GableSchemaContractField4, GableSchemaContractField5, GableSchemaContractField6, GableSchemaContractField7, GableSchemaContractField8, GableSchemaContractField9, GableSchemaContractField10, GableSchemaContractField11, GableSchemaContractField12, GableSchemaContractField13, GableSchemaContractField14]"
    ]
):
    root: Union[
        GableSchemaContractField2,
        GableSchemaContractField3,
        GableSchemaContractField4,
        GableSchemaContractField5,
        GableSchemaContractField6,
        GableSchemaContractField7,
        GableSchemaContractField8,
        GableSchemaContractField9,
        GableSchemaContractField10,
        GableSchemaContractField11,
        GableSchemaContractField12,
        GableSchemaContractField13,
        GableSchemaContractField14,
    ]

    @classmethod
    def construct_error(cls, e):
        from pydantic import ValidationError
        from pydantic.error_wrappers import ErrorWrapper

        class ExceptionProxy(Exception):
            def __init__(self, name, msg):
                self.__class__.__name__ = name
                self.msg = msg

            def __str__(self):
                res = self.msg
                del self.__dict__[
                    "msg"
                ]  # don't render msg twice in pydantic's error reporting
                return res

        filtered_errors = []
        for error in e.errors():
            new_error = {
                **error,
                "loc": tuple(f for f in error["loc"] if f != "root"),
            }
            if new_error not in filtered_errors:
                filtered_errors.append(new_error)
        error_wrappers = []
        for error in filtered_errors:
            msg = error["msg"]
            name = ".".join(error["type"].split(".")[1:])
            exc = ExceptionProxy(name, msg)
            error_wrapper = ErrorWrapper(
                exc=exc,
                loc=tuple(error["loc"]),
            )
            error_wrappers.append(error_wrapper)
        return ValidationError(error_wrappers, cls)

    @classmethod
    def validate(cls, value):
        from pydantic import ValidationError

        try:
            return super().validate(value)
        except ValidationError as e:
            raise cls.construct_error(e)


class ContractSpec(BaseModel):
    id: UUID = Field(
        ..., description="Unique identifier for the contract in UUID format"
    )
    dataAssetResourceName: Optional[DataAssetResourceName] = None
    dataAssetResourceNameList: Optional[List[DataAssetResourceName]] = Field(
        default=None,
        description="Optional array of data asset resource names for contracts that apply to multiple specific assets. When present, this takes precedence over dataAssetResourceName.",
    )
    doc: str = Field(..., description="Description of the contract")
    name: str = Field(
        ...,
        description="The name of the contract. When combined with the contract namespace, it represents a unique name in the Gable platform.  Only alphanumeric characters (upper and lowercase) and underscores are allowed",
    )
    namespace: str = Field(
        ...,
        description="The namespace of the contract. When combined with the contract name, it represents a unique name in the Gable platform.  Only alphanumeric characters (upper and lowercase) and underscores are allowed",
    )
    owner: EmailStr = Field(..., description="The owner of the contract")
    restrictPii: Optional[bool] = Field(
        default=None,
        description="If true, fields that look like PII will cause contract violations.",
    )
    schema_: List[GableSchemaContractField] = Field(..., alias="schema")


class ContractInput(BaseModel):
    id: UUID = Field(
        ..., description="Unique identifier for the contract in UUID format"
    )
    parentRowId: Optional[UUID] = Field(
        default=None,
        description="Unique identifier for a contract's parent row id in UUID format",
    )
    version: Optional[str] = Field(
        default=None, description="Version of the contract (semantic versioning)"
    )
    status: ContractStatus
    gitHash: Optional[str] = Field(
        default=None,
        description="full length git hash corresponding to the commit this contract was added/updated",
        max_length=40,
        min_length=40,
    )
    gitRepo: Optional[AnyUrl] = Field(
        default=None, description="full link to the git repo this contract lives in"
    )
    gitUser: Optional[str] = Field(
        default=None, description="git user who added this contract"
    )
    reviewers: Optional[List[str]] = Field(
        default=None,
        description="optional list of users who reviewed the merged PR that this contract added/updated in",
    )
    filePath: Optional[str] = Field(
        default=None,
        description="path to the contract file from the root of the git repository",
        pattern="^([^/]+\\/)*[^/]+$",
    )
    mergedAt: Optional[datetime_aliased] = Field(
        default=None,
        description="date time at which the PR that added/updated this contract was merged",
    )
    enforcementLevel: Optional[EnforcementLevel] = Field(
        default=None, description="alert level for contract"
    )
    contractSpec: ContractSpec = Field(
        ...,
        description="This is the possible contract specification. If it is valid, it will  match the contract specification schema. However, we have to allow  for invalid contracts to be passed to Gable even if we reject them.",
    )
    lastEditorUserId: Optional[UUID] = Field(
        default=None,
        description="Unique identifier for the user who last edited the contract through the UI",
    )
    lastEditorEmail: Optional[str] = Field(
        default=None,
        description="Email of the user who last edited the contract through the UI",
    )
    lastEditorFirstName: Optional[str] = Field(
        default=None,
        description="First name of the user who last edited the contract through the UI",
    )
    lastEditorLastName: Optional[str] = Field(
        default=None,
        description="Last name of the user who last edited the contract through the UI",
    )
    lastEditorGithubHandle: Optional[str] = Field(
        default=None,
        description="GitHub handle of the user who last edited the contract through the UI",
    )


class PostContractRequest(RootModel["Union[ContractInput, List[ContractInput]]"]):
    root: Union[ContractInput, List[ContractInput]]

    @classmethod
    def construct_error(cls, e):
        from pydantic import ValidationError
        from pydantic.error_wrappers import ErrorWrapper

        class ExceptionProxy(Exception):
            def __init__(self, name, msg):
                self.__class__.__name__ = name
                self.msg = msg

            def __str__(self):
                res = self.msg
                del self.__dict__[
                    "msg"
                ]  # don't render msg twice in pydantic's error reporting
                return res

        filtered_errors = []
        for error in e.errors():
            new_error = {
                **error,
                "loc": tuple(f for f in error["loc"] if f != "root"),
            }
            if new_error not in filtered_errors:
                filtered_errors.append(new_error)
        error_wrappers = []
        for error in filtered_errors:
            msg = error["msg"]
            name = ".".join(error["type"].split(".")[1:])
            exc = ExceptionProxy(name, msg)
            error_wrapper = ErrorWrapper(
                exc=exc,
                loc=tuple(error["loc"]),
            )
            error_wrappers.append(error_wrapper)
        return ValidationError(error_wrappers, cls)

    @classmethod
    def validate(cls, value):
        from pydantic import ValidationError

        try:
            return super().validate(value)
        except ValidationError as e:
            raise cls.construct_error(e)


class ContractOutput(BaseModel):
    id: UUID = Field(
        ..., description="Unique identifier for the contract in UUID format"
    )
    parentRowId: Optional[UUID] = Field(
        default=None,
        description="Unique identifier for a contract's parent row id in UUID format",
    )
    version: Optional[str] = Field(
        default=None, description="Version of the contract (semantic versioning)"
    )
    status: ContractStatus
    gitHash: Optional[str] = Field(
        default=None,
        description="full length git hash corresponding to the commit this contract was added/updated",
        max_length=40,
        min_length=40,
    )
    gitRepo: Optional[AnyUrl] = Field(
        default=None, description="full link to the git repo this contract lives in"
    )
    gitUser: Optional[str] = Field(
        default=None, description="git user who added/updated this contract"
    )
    fileUri: Optional[AnyUrl] = Field(
        default=None,
        description="full link to the file in the repo that contains this contract",
    )
    filePath: Optional[str] = Field(
        default=None,
        description="path to the contract file from the root of the git repository",
    )
    reviewers: Optional[List[str]] = Field(
        default=None,
        description="optional list of users who reviewed the merged PR that this contract added/updated in",
    )
    mergedAt: Optional[datetime_aliased] = Field(
        default=None,
        description="date time at which the PR that added/updated this contract was merged",
    )
    createdAt: datetime_aliased = Field(
        ..., description="date time at which the contract was created"
    )
    updatedAt: datetime_aliased = Field(
        ..., description="date time at which the contract was last updated"
    )
    contractSpec: ContractSpec = Field(..., description="contract spec")
    contractSpecRaw: str = Field(..., description="contract spec raw json")
    enforcementLevel: Optional[EnforcementLevel] = Field(
        default="INACTIVE", description="alert level for contract"
    )
    lastEditorUserId: Optional[UUID] = Field(
        default=None,
        description="Unique identifier for the user who last edited the contract through the UI",
    )
    lastEditorEmail: Optional[str] = Field(
        default=None,
        description="Email of the user who last edited the contract through the UI",
    )
    lastEditorFirstName: Optional[str] = Field(
        default=None,
        description="First name of the user who last edited the contract through the UI",
    )
    lastEditorLastName: Optional[str] = Field(
        default=None,
        description="Last name of the user who last edited the contract through the UI",
    )
    lastEditorGithubHandle: Optional[str] = Field(
        default=None,
        description="GitHub handle of the user who last edited the contract through the UI",
    )
    violations: List[ContractViolationEvent] = Field(
        ..., description="List of contract violations"
    )
    contractSpecFieldToViolationStatusMapping: Optional[
        Dict[str, List[ContractViolationType]]
    ] = Field(
        default=None,
        description="Mapping of contract spec fields to a list of the violations currently applicable to them",
    )


class ContractOutputPaginated(BaseModel):
    contracts: List[ContractOutput]
    totalCount: float = Field(
        ..., description="Total number of contracts matching the search criteria"
    )


class Contract(BaseModel):
    contractSchema: List[GableSchemaContractField]
    dataAssetResourceName: DataAssetResourceName


class ContractCheckComplianceRequest(BaseModel):
    contract: Contract


class CheckDataAssetMissingAssetResponse(BaseModel):
    dataAssetResourceName: DataAssetResourceName = Field(
        ...,
        description="The full resource name of the data asset, see [Data Assets](https://docs.gable.ai/data_assets_and_lineage/data_assets)",
        examples=[
            "postgres://service-one.aaa.eu-west-1.rds.amazonaws.com:5432:serviceone.public.sales",
            "protobuf://git@github.com/org/repo/path/to/file.proto:company.serviceone.Sales",
        ],
    )
    dataAssetPath: str = Field(
        ...,
        description="The relative path of the data asset within its data store",
        examples=["serviceone.public.sales", "company.serviceone.Sales"],
    )
    contract: ContractOutput
    contractOwner: str
    contractOwnerGithubHandle: Optional[str] = None
    subscribers: List[Subscriber]
    responseType: Literal["MISSING_DATA_ASSET"]


class CheckDataAssetResponse(
    RootModel[
        "Union[CheckDataAssetNoContractResponse, CheckDataAssetNoChangeResponse, CheckDataAssetDetailedResponse, CheckDataAssetErrorResponse, CheckDataAssetMissingAssetResponse]"
    ]
):
    root: Union[
        CheckDataAssetNoContractResponse,
        CheckDataAssetNoChangeResponse,
        CheckDataAssetDetailedResponse,
        CheckDataAssetErrorResponse,
        CheckDataAssetMissingAssetResponse,
    ] = Field(..., discriminator="responseType")

    @classmethod
    def construct_error(cls, e):
        from pydantic import ValidationError
        from pydantic.error_wrappers import ErrorWrapper

        class ExceptionProxy(Exception):
            def __init__(self, name, msg):
                self.__class__.__name__ = name
                self.msg = msg

            def __str__(self):
                res = self.msg
                del self.__dict__[
                    "msg"
                ]  # don't render msg twice in pydantic's error reporting
                return res

        filtered_errors = []
        for error in e.errors():
            new_error = {
                **error,
                "loc": tuple(f for f in error["loc"] if f != "root"),
            }
            if new_error not in filtered_errors:
                filtered_errors.append(new_error)
        error_wrappers = []
        for error in filtered_errors:
            msg = error["msg"]
            name = ".".join(error["type"].split(".")[1:])
            exc = ExceptionProxy(name, msg)
            error_wrapper = ErrorWrapper(
                exc=exc,
                loc=tuple(error["loc"]),
            )
            error_wrappers.append(error_wrapper)
        return ValidationError(error_wrappers, cls)

    @classmethod
    def validate(cls, value):
        from pydantic import ValidationError

        try:
            return super().validate(value)
        except ValidationError as e:
            raise cls.construct_error(e)


class CheckDataAssetsResponse(
    RootModel[
        "Union[List[CheckDataAssetResponse], CheckDataAssetCommentMarkdownResponse]"
    ]
):
    root: Union[List[CheckDataAssetResponse], CheckDataAssetCommentMarkdownResponse]

    @classmethod
    def construct_error(cls, e):
        from pydantic import ValidationError
        from pydantic.error_wrappers import ErrorWrapper

        class ExceptionProxy(Exception):
            def __init__(self, name, msg):
                self.__class__.__name__ = name
                self.msg = msg

            def __str__(self):
                res = self.msg
                del self.__dict__[
                    "msg"
                ]  # don't render msg twice in pydantic's error reporting
                return res

        filtered_errors = []
        for error in e.errors():
            new_error = {
                **error,
                "loc": tuple(f for f in error["loc"] if f != "root"),
            }
            if new_error not in filtered_errors:
                filtered_errors.append(new_error)
        error_wrappers = []
        for error in filtered_errors:
            msg = error["msg"]
            name = ".".join(error["type"].split(".")[1:])
            exc = ExceptionProxy(name, msg)
            error_wrapper = ErrorWrapper(
                exc=exc,
                loc=tuple(error["loc"]),
            )
            error_wrappers.append(error_wrapper)
        return ValidationError(error_wrappers, cls)

    @classmethod
    def validate(cls, value):
        from pydantic import ValidationError

        try:
            return super().validate(value)
        except ValidationError as e:
            raise cls.construct_error(e)


class DataAssetFieldProfile(
    RootModel[
        "Union[DataAssetFieldProfileBoolean, DataAssetFieldProfileNumber, DataAssetFieldProfileOther, DataAssetFieldProfileString, DataAssetFieldProfileUUID, DataAssetFieldProfileTemporal, DataAssetFieldProfileUnion, DataAssetFieldProfileList]"
    ]
):
    root: Union[
        DataAssetFieldProfileBoolean,
        DataAssetFieldProfileNumber,
        DataAssetFieldProfileOther,
        DataAssetFieldProfileString,
        DataAssetFieldProfileUUID,
        DataAssetFieldProfileTemporal,
        DataAssetFieldProfileUnion,
        DataAssetFieldProfileList,
    ] = Field(..., discriminator="profileType")

    @classmethod
    def construct_error(cls, e):
        from pydantic import ValidationError
        from pydantic.error_wrappers import ErrorWrapper

        class ExceptionProxy(Exception):
            def __init__(self, name, msg):
                self.__class__.__name__ = name
                self.msg = msg

            def __str__(self):
                res = self.msg
                del self.__dict__[
                    "msg"
                ]  # don't render msg twice in pydantic's error reporting
                return res

        filtered_errors = []
        for error in e.errors():
            new_error = {
                **error,
                "loc": tuple(f for f in error["loc"] if f != "root"),
            }
            if new_error not in filtered_errors:
                filtered_errors.append(new_error)
        error_wrappers = []
        for error in filtered_errors:
            msg = error["msg"]
            name = ".".join(error["type"].split(".")[1:])
            exc = ExceptionProxy(name, msg)
            error_wrapper = ErrorWrapper(
                exc=exc,
                loc=tuple(error["loc"]),
            )
            error_wrappers.append(error_wrapper)
        return ValidationError(error_wrappers, cls)

    @classmethod
    def validate(cls, value):
        from pydantic import ValidationError

        try:
            return super().validate(value)
        except ValidationError as e:
            raise cls.construct_error(e)


class DataAssetFieldProfileUnion(DataAssetFieldProfileBase):
    profileType: Literal["union"]
    profiles: List[DataAssetFieldProfile] = Field(
        ..., description="List of constituent data asset field profiles"
    )


class DataAssetFieldsToProfilesMapping(
    RootModel["Optional[Dict[str, DataAssetFieldProfile]]"]
):
    root: Optional[Dict[str, DataAssetFieldProfile]] = None

    @classmethod
    def construct_error(cls, e):
        from pydantic import ValidationError
        from pydantic.error_wrappers import ErrorWrapper

        class ExceptionProxy(Exception):
            def __init__(self, name, msg):
                self.__class__.__name__ = name
                self.msg = msg

            def __str__(self):
                res = self.msg
                del self.__dict__[
                    "msg"
                ]  # don't render msg twice in pydantic's error reporting
                return res

        filtered_errors = []
        for error in e.errors():
            new_error = {
                **error,
                "loc": tuple(f for f in error["loc"] if f != "root"),
            }
            if new_error not in filtered_errors:
                filtered_errors.append(new_error)
        error_wrappers = []
        for error in filtered_errors:
            msg = error["msg"]
            name = ".".join(error["type"].split(".")[1:])
            exc = ExceptionProxy(name, msg)
            error_wrapper = ErrorWrapper(
                exc=exc,
                loc=tuple(error["loc"]),
            )
            error_wrappers.append(error_wrapper)
        return ValidationError(error_wrappers, cls)

    @classmethod
    def validate(cls, value):
        from pydantic import ValidationError

        try:
            return super().validate(value)
        except ValidationError as e:
            raise cls.construct_error(e)


class ResolvedDataAsset(BaseModel):
    source_type: SourceType
    data_asset_resource_name: StructuredDataAssetResourceName
    schema_: Dict[str, Any] = Field(
        ..., alias="schema", description="The schema of the data asset"
    )
    fieldNameToDataAssetFieldProfileMap: Optional[DataAssetFieldsToProfilesMapping] = (
        Field(
            default=None,
            description="A mapping of the field name within the schema to its corresponding data profile",
        )
    )


class DataAssetsCheckComplianceRequest(BaseModel):
    assets: List[ResolvedDataAsset] = Field(
        ..., description="Array of data assets to check compliance for"
    )
    includeUnchangedAssets: Optional[bool] = Field(
        default=False,
        description="If true, the data assets sent that have not changed compared with the stored assets will be checked for contract violations. If false, the only data assets that have changed compared with the stored assets will be checked for contract violations.",
    )
    responseType: ResponseType = Field(
        ...,
        description="Determines the format of the response from the API. Specifying 'DETAILED' will return a detailed JSON object for each data asset checked. If 'COMMENT_MARKDOWN' is specified, the response will be a markdown string intended to be used as a comment in a pull request.",
    )
    prLink: Optional[str] = Field(
        default=None,
        description="(optional) Link to the pull request the proposed changes to the data asset are part of",
    )


class S3Asset(BaseModel):
    schema_: Dict[str, Any] = Field(
        ..., alias="schema", description="The schema of the data asset"
    )
    pattern: str = Field(..., description="The pattern of the data asset")
    bucket: str = Field(..., description="The bucket of the data asset")
    fieldNameToDataAssetFieldProfileMap: Optional[DataAssetFieldsToProfilesMapping] = (
        Field(
            default=None,
            description="A mapping of the field name within the recap struct schema to its corresponding data profile",
        )
    )


class CheckComplianceDataAssetsS3Request(BaseModel):
    assets: List[S3Asset] = Field(
        ..., description="Array of data assets to check for compliance"
    )
    includeUnchangedAssets: Optional[bool] = Field(
        default=False,
        description="If true, the data assets sent that have not changed compared with the stored assets will be checked for contract violations and responseType: NO_CHANGE will be returned for those assets. If false, the only data assets that have changed compared with the stored assets will be checked for contract violations.",
    )
    responseType: ResponseType = Field(
        ...,
        description="Determines the format of the response from the API. Specifying 'DETAILED' will return a detailed JSON object for each data asset checked. If 'COMMENT_MARKDOWN' is specified, the response will be a markdown string intended to be used as a comment in a pull request.",
    )
    prLink: Optional[str] = Field(
        default=None,
        description="Link to the pull request the proposed changes to the data asset are part of",
    )


class RegisterDataAssetsRequest(BaseModel):
    assets: List[ResolvedDataAsset] = Field(
        ..., description="Array of data assets to register"
    )
    prLink: Optional[str] = Field(
        default=None,
        description="Link to the PR that may have added or edited the data assets",
        examples=["https://github.com/fakeorg/fakerepo/pull/123"],
    )


class RegisterDataAssetS3Request(BaseModel):
    assets: List[S3Asset] = Field(..., description="Array of data assets to register")
    dry_run: Optional[bool] = Field(
        default=False, description="If true, no data asset will be registered"
    )
    prLink: Optional[str] = Field(
        default=None,
        description="Link to the PR that may have added or edited the data assets",
        examples=["https://github.com/fakeorg/fakerepo/pull/123"],
    )


class DataAssetField(BaseModel):
    id: UUID = Field(..., description="The unique identifier for the data asset field.")
    dataAssetVersionId: UUID = Field(
        ...,
        description="The identifier of the data asset version this field belongs to.",
    )
    name: str = Field(..., description="The name of the field.")
    description: Optional[str] = Field(
        default=None, description="A brief description of the field."
    )
    order: Optional[int] = Field(
        default=None,
        description="The order or position of the field in the data asset.",
    )
    nativeDataType: str = Field(
        ..., description="The native data type of the field in the source system."
    )
    type: Dict[str, Any] = Field(
        ...,
        description="A custom or specific attribute to represent the Gable type of the field.",
    )
    displayType: str = Field(..., description="The display name of the field.")
    parentFieldId: Optional[UUID] = Field(
        default=None,
        description="For nested fields, this is the ID of the parent field.",
    )
    createdAt: datetime_aliased = Field(
        ..., description="The timestamp when the field was created."
    )
    updatedAt: datetime_aliased = Field(
        ..., description="The timestamp when the field was last updated."
    )
    deletedAt: Optional[datetime_aliased] = Field(
        default=None, description="The timestamp when the field was marked as deleted."
    )
    piiCategory: Optional[PiiCategoryEnum] = None
    profile: Optional[DataAssetFieldProfile] = None


class DataAssetVersion(BaseModel):
    id: UUID = Field(
        ..., description="The unique identifier for the version of the data asset."
    )
    dataAssetId: UUID = Field(
        ...,
        description="The identifier of the parent data asset to which this version belongs.",
    )
    description: Optional[str] = Field(
        default=None,
        description="A brief description of this particular version of the data asset.",
    )
    rawSchema: Optional[str] = Field(
        default=None,
        description="The raw schema of the data asset from the source system. This can be the contents of a schema file or data from the information  schema of a database. This is used to regenerate the schema of the data asset if needed.",
    )
    fields: List[DataAssetField] = Field(
        ..., description="The fields of the data asset."
    )
    createdAt: datetime_aliased = Field(
        ...,
        description="The timestamp when this version of the data asset was created.",
    )
    updatedAt: datetime_aliased = Field(
        ...,
        description="The timestamp when this version of the data asset was last updated.",
    )
    deletedAt: Optional[datetime_aliased] = Field(
        default=None,
        description="The timestamp when this version of the data asset was marked as deleted, if applicable.",
    )
    violations: List[ContractViolation] = Field(
        ...,
        description="List of contract violations associated with the data asset version.",
    )


class DataAsset(BaseModel):
    id: UUID = Field(..., description="The unique identifier for the data asset.")
    dataAssetResourceName: DataAssetResourceName
    domain: str = Field(
        ...,
        description='The domain to which the data asset belongs. Previously referred to as "namespace."',
    )
    path: str = Field(
        ...,
        description='The name or path identifying the data asset. Previously referred to as "name."',
    )
    type: SourceType
    contractId: Optional[str] = Field(
        default=None, description="The contract ID associated with the data asset."
    )
    versionDetail: DataAssetVersion = Field(
        ..., description="The version details of the data asset."
    )
    createdAt: datetime_aliased = Field(
        ..., description="The timestamp of when the data asset was initially created."
    )
    updatedAt: datetime_aliased = Field(
        ..., description="The timestamp of the most recent update to the data asset."
    )
    deletedAt: Optional[datetime_aliased] = Field(
        default=None,
        description="The timestamp indicating when the data asset was deleted, if applicable.",
    )
    schema_: Optional[Dict[str, Any]] = Field(default=None, alias="schema")


class InferContractFromDataAssetResponse(BaseModel):
    contractId: UUID = Field(
        ..., description="Unique identifier for the contract in UUID format"
    )
    version: Optional[str] = Field(
        default=None, description="Version of the contract (semantic versioning)"
    )
    status: Optional[Status] = Field(default=None, description="status of the contract")
    gitHash: Optional[str] = Field(
        default=None,
        description="full length git hash corresponding to the commit this contract was added/updated",
        max_length=40,
        min_length=40,
    )
    gitRepo: Optional[AnyUrl] = Field(
        default=None, description="full link to the git repo this contract lives in"
    )
    gitUser: Optional[str] = Field(
        default=None, description="git user who added/updated this contract"
    )
    fileUri: Optional[AnyUrl] = Field(
        default=None,
        description="full link to the file in the repo that contains this contract",
    )
    reviewers: Optional[List[str]] = Field(
        default=None,
        description="optional list of users who reviewed the merged PR that this contract added/updated in",
    )
    mergedAt: Optional[datetime_aliased] = Field(
        default=None,
        description="date time at which the PR that added/updated this contract was merged",
    )
    createdAt: Optional[datetime_aliased] = Field(
        default=None, description="date time at which the contract was created"
    )
    updatedAt: Optional[datetime_aliased] = Field(
        default=None, description="date time at which the contract was last updated"
    )
    contractSpec: ContractSpec = Field(..., description="contract spec")
    contractSpecRaw: str = Field(..., description="contract spec raw json")


class CreateDataAssetProfileRequest(BaseModel):
    darn: str = Field(
        ..., description="the Data Asset Resource Name (DARN) of the data asset"
    )
    data_asset_id: UUID = Field(..., description="ID for the data asset")
    fieldsToProfilesMapping: DataAssetFieldsToProfilesMapping = Field(
        ...,
        description="The mapping of data asset fields to data asset field profiles associated with the data asset",
    )


class CreateDataAssetProfileResponse(BaseModel):
    fieldsToProfilesMapping: DataAssetFieldsToProfilesMapping


class DataFlowBoundary(BaseModel):
    id: UUID = Field(..., description="The unique identifier for the ingress/egress.")
    kind: str = Field(..., description="The kind of the ingress/egress.")
    schema_: GableSchemaStruct = Field(
        ..., alias="schema", description="The schema of the ingress/egress."
    )


class FullComponent(BaseModel):
    id: UUID = Field(..., description="The unique identifier for the component.")
    name: str = Field(..., description="The name of the component.")
    ingresses: List[Ingress] = Field(
        ..., description="The ingresses associated with the component."
    )
    egresses: List[Egress] = Field(
        ..., description="The egresses associated with the component."
    )
    paths: List[Path] = Field(
        ..., description="The paths associated with the component."
    )


class StaticAnalysisDataFlowPathEnds(BaseModel):
    kind: Optional[str] = Field(
        default=None,
        description='The kind of the ingress/egress (e.g. "postgres", "gateway_response")',
    )
    code_anchor: StaticAnalysisCodeAnchor = Field(
        ...,
        description="The code anchor that represents the ingress/egress point in the codebase. This should be the same as the first (for ingress) or last (for egress) code anchor in the flow path.",
    )
    schema_: GableSchemaStruct = Field(..., alias="schema")


class StaticAnalysisDataFlowPath(BaseModel):
    ingress: StaticAnalysisDataFlowPathEnds
    egress: StaticAnalysisDataFlowPathEnds
    code_anchors: List[StaticAnalysisCodeAnchor] = Field(
        ...,
        description="Ordered list of code anchors that represent the path from ingress to egress. The order of the anchors in this array represents the flow of data through the codebase.",
    )
    field_mappings: Optional[List[StaticAnalysisTabularLineageFieldMapping]] = Field(
        default=None,
        description="List of identified field mappings between schema at ingress and egress.",
    )


class StaticAnalysisPathsUploadRequest(BaseModel):
    paths: List[StaticAnalysisDataFlowPath]
    run_id: str = Field(..., description="The ID of the run that the paths belong to")


class FakeResponseForTypeGeneration(BaseModel):
    fakeProperty1: Optional[StaticAnalysisStartRunRequest] = None
    fakeProperty2: Optional[StaticAnalysisStartRunResponse] = None
    fakeProperty3: Optional[StaticAnalysisPathsUploadRequest] = None


class GableSchemaContractField2(GableSchemaStruct, GableSchemaContractField1):
    pass


class GableSchemaContractField9(GableSchemaList, GableSchemaContractField1):
    pass


class GableSchemaContractField10(GableSchemaMap, GableSchemaContractField1):
    pass


class GableSchemaContractField12(GableSchemaUnion, GableSchemaContractField1):
    pass


Ingress = DataFlowBoundary


Egress = DataFlowBoundary


GableSchemaField.model_rebuild()
GableSchemaType.model_rebuild()
GableSchemaContractField.model_rebuild()
DataAssetFieldProfile.model_rebuild()
FullComponent.model_rebuild()

# OpenAPI schema version used to generate this file
OPENAPI_SCHEMA_VERSION = "0.2.1"
