"""
----------------------------------------------------------------------------
METADATA:
File: const.py
Project: paperap
Created: 2025-03-04
Version: 0.0.10
Author: Jess Mann
Email: jess@jmann.me
Copyright (c) 2025 Jess Mann
----------------------------------------------------------------------------
LAST MODIFIED:
2025-03-04 By Jess Mann
"""
from __future__ import annotations
import logging
from datetime import datetime
from enum import Enum, IntEnum, StrEnum
from string import Template
from typing import (
Any,
Iterator,
Literal,
NotRequired,
Protocol,
Required,
Self,
TypeAlias,
TypedDict,
override,
runtime_checkable,
)
import pydantic
from pydantic import ConfigDict, Field
logger = logging.getLogger(__name__)
[docs]
class StrEnumWithUnknown(StrEnum):
@override
@classmethod
def _missing_(cls, value: object) -> str:
logger.debug("Handling unknown enum value", extra={"enum_class": cls.__name__, "value": value})
return cls.UNKNOWN # type: ignore # subclasses will define unknown
[docs]
class IntEnumWithUnknown(IntEnum):
@override
@classmethod
def _missing_(cls, value: object) -> int:
logger.debug("Handling unknown enum value", extra={"enum_class": cls.__name__, "value": value})
return cls.UNKNOWN # type: ignore # subclasses will define unknown
[docs]
class ConstModel(pydantic.BaseModel):
model_config = ConfigDict(
from_attributes=True,
extra="forbid",
use_enum_values=True,
validate_default=True,
validate_assignment=True,
)
@override
def __eq__(self, other: Any) -> bool:
if isinstance(other, dict):
# Ensure the dictionary keys match the model fields
expected_keys = set(self.model_fields.keys())
if set(other.keys()) != expected_keys:
return False
return all(getattr(self, key) == other.get(key) for key in expected_keys)
# This check probably isn't necessary before calling super (TODO?)
if isinstance(other, self.__class__):
# Compare all fields of the model
return self.model_dump() == other.model_dump()
return super().__eq__(other)
[docs]
class URLS:
# May be deprecated in the future. Used for reference currently.
index: Template = Template("/api/")
token: Template = Template("/api/token/")
list: Template = Template("/api/${resource}/")
detail: Template = Template("/api/${resource}/${pk}/")
create: Template = Template("/api/${resource}/")
update: Template = Template("/api/${resource}/${pk}/")
delete: Template = Template("/api/${resource}/${pk}/")
meta: Template = Template("/api/document/${pk}/metadata/")
next_asn: Template = Template("/api/document/next_asn/")
notes: Template = Template("/api/document/${pk}/notes/")
post: Template = Template("/api/documents/post_document/")
single: Template = Template("/api/document/${pk}/")
suggestions: Template = Template("/api/${resource}/${pk}/suggestions/")
preview: Template = Template("/api/${resource}/${pk}/preview/")
thumbnail: Template = Template("/api/${resource}/${pk}/thumb/")
download: Template = Template("/api/${resource}/${pk}/download/")
CommonEndpoints: TypeAlias = Literal["list", "detail", "create", "update", "delete"]
Endpoints: TypeAlias = dict[CommonEndpoints | str, Template]
type ClientResponse = dict[str, Any] | list[dict[str, Any]] | None
[docs]
class FilteringStrategies(StrEnum):
WHITELIST = "whitelist"
BLACKLIST = "blacklist"
ALLOW_ALL = "allow_all"
ALLOW_NONE = "allow_none"
[docs]
class ModelStatus(StrEnum):
INITIALIZING = "initializing"
UPDATING = "updating"
SAVING = "saving"
READY = "ready"
ERROR = "error"
[docs]
class CustomFieldTypes(StrEnumWithUnknown):
STRING = "string"
BOOLEAN = "boolean"
INTEGER = "integer"
FLOAT = "float"
MONETARY = "monetary"
DATE = "date"
URL = "url"
DOCUMENT_LINK = "documentlink"
UNKNOWN = "unknown"
[docs]
class CustomFieldValues(ConstModel):
field: int
value: Any
[docs]
class CustomFieldTypedDict(TypedDict):
field: int
value: Any
# Possibly not used after refactoring
[docs]
class DocumentSearchHitType(ConstModel):
score: float | None = None
highlights: str | None = None
note_highlights: str | None = None
rank: int | None = None
[docs]
class MatchingAlgorithmType(IntEnumWithUnknown):
NONE = 0
ANY = 1
ALL = 2
LITERAL = 3
REGEX = 4
FUZZY = 5
AUTO = 6
UNKNOWN = -1
@override
@classmethod
def _missing_(cls, value: object) -> "Literal[MatchingAlgorithmType.UNKNOWN]":
logger.debug("Handling unknown enum value", extra={"enum_class": cls.__name__, "value": value})
return cls.UNKNOWN
[docs]
class PermissionSetType(ConstModel):
users: list[int] = Field(default_factory=list)
groups: list[int] = Field(default_factory=list)
[docs]
class PermissionTableType(ConstModel):
view: PermissionSetType = Field(default_factory=PermissionSetType)
change: PermissionSetType = Field(default_factory=PermissionSetType)
[docs]
class RetrieveFileMode(StrEnum):
DOWNLOAD = "download"
PREVIEW = "preview"
THUMBNAIL = "thumb"
[docs]
class SavedViewFilterRuleType(ConstModel):
rule_type: int
value: str | None = None
saved_view: int | None = None
[docs]
class ShareLinkFileVersionType(StrEnumWithUnknown):
ARCHIVE = "archive"
ORIGINAL = "original"
UNKNOWN = "unknown"
@override
@classmethod
def _missing_(cls, value: object) -> "Literal[ShareLinkFileVersionType.UNKNOWN]":
logger.debug("Handling unknown enum value", extra={"enum_class": cls.__name__, "value": value})
return cls.UNKNOWN
[docs]
class StatusType(StrEnumWithUnknown):
OK = "OK"
ERROR = "ERROR"
UNKNOWN = "UNKNOWN"
@override
@classmethod
def _missing_(cls, value: object) -> "Literal[StatusType.UNKNOWN]":
logger.debug("Handling unknown enum value", extra={"enum_class": cls.__name__, "value": value})
return cls.UNKNOWN
[docs]
class StatusDatabaseMigrationStatusType(ConstModel):
latest_migration: str | None = None
unapplied_migrations: list[str] = Field(default_factory=list)
[docs]
class StatusDatabaseType(ConstModel):
type: str | None = None
url: str | None = None
status: StatusType | None = None
error: str | None = None
migration_status: StatusDatabaseMigrationStatusType | None = None
[docs]
class StatusStorageType(ConstModel):
total: int | None = None
available: int | None = None
[docs]
class StatusTasksType(ConstModel):
redis_url: str | None = None
redis_status: StatusType | None = None
redis_error: str | None = None
celery_status: StatusType | None = None
index_status: StatusType | None = None
index_last_modified: datetime | None = None
index_error: str | None = None
classifier_status: StatusType | None = None
classifier_last_trained: datetime | None = None
classifier_error: str | None = None
[docs]
class TaskStatusType(StrEnumWithUnknown):
PENDING = "PENDING"
STARTED = "STARTED"
SUCCESS = "SUCCESS"
FAILURE = "FAILURE"
UNKNOWN = "UNKNOWN"
[docs]
class TaskTypeType(StrEnumWithUnknown):
AUTO = "auto_task"
SCHEDULED_TASK = "scheduled_task"
MANUAL_TASK = "manual_task"
UNKNOWN = "unknown"
[docs]
class WorkflowActionType(IntEnumWithUnknown):
ASSIGNMENT = 1
REMOVAL = 2
EMAIL = 3
WEBHOOK = 4
UNKNOWN = -1
[docs]
class WorkflowTriggerType(IntEnumWithUnknown):
CONSUMPTION = 1
DOCUMENT_ADDED = 2
DOCUMENT_UPDATED = 3
UNKNOWN = -1
[docs]
class WorkflowTriggerSourceType(IntEnumWithUnknown):
CONSUME_FOLDER = 1
API_UPLOAD = 2
MAIL_FETCH = 3
UNKNOWN = -1
[docs]
class WorkflowTriggerMatchingType(IntEnumWithUnknown):
NONE = 0
ANY = 1
ALL = 2
LITERAL = 3
REGEX = 4
FUZZY = 5
UNKNOWN = -1
[docs]
class ScheduleDateFieldType(StrEnumWithUnknown):
ADDED = "added"
CREATED = "created"
MODIFIED = "modified"
CUSTOM_FIELD = "custom_field"
UNKNOWN = "unknown"
[docs]
class WorkflowTriggerScheduleDateFieldType(StrEnumWithUnknown):
ADDED = "added"
CREATED = "created"
MODIFIED = "modified"
CUSTOM_FIELD = "custom_field"
UNKNOWN = "unknown"
[docs]
class SavedViewDisplayModeType(StrEnumWithUnknown):
TABLE = "table"
SMALL_CARDS = "smallCards"
LARGE_CARDS = "largeCards"
UNKNOWN = "unknown"
[docs]
class SavedViewDisplayFieldType(StrEnumWithUnknown):
TITLE = "title"
CREATED = "created"
ADDED = "added"
TAGS = "tag"
CORRESPONDENT = "correspondent"
DOCUMENT_TYPE = "documenttype"
STORAGE_PATH = "storagepath"
NOTES = "note"
OWNER = "owner"
SHARED = "shared"
ASN = "asn"
PAGE_COUNT = "pagecount"
CUSTOM_FIELD = "custom_field_%d"
UNKNOWN = "unknown"
[docs]
class DocumentStorageType(StrEnumWithUnknown):
UNENCRYPTED = "unencrypted"
GPG = "gpg"
UNKNOWN = "unknown"
[docs]
class TaskNameType(StrEnumWithUnknown):
CONSUME_FILE = "consume_file"
TRAIN_CLASSIFIER = "train_classifier"
CHECK_SANITY = "check_sanity"
INDEX_OPTIMIZE = "index_optimize"
UNKNOWN = "unknown"