Coverage for src/paperap/const.py: 92%
189 statements
« prev ^ index » next coverage.py v7.6.12, created at 2025-03-20 13:17 -0400
« prev ^ index » next coverage.py v7.6.12, created at 2025-03-20 13:17 -0400
1"""
2----------------------------------------------------------------------------
4 METADATA:
6 File: const.py
7 Project: paperap
8 Created: 2025-03-04
9 Version: 0.0.8
10 Author: Jess Mann
11 Email: jess@jmann.me
12 Copyright (c) 2025 Jess Mann
14----------------------------------------------------------------------------
16 LAST MODIFIED:
18 2025-03-04 By Jess Mann
20"""
22from __future__ import annotations
24import logging
25from datetime import datetime
26from enum import Enum, IntEnum, StrEnum
27from string import Template
28from typing import (
29 Any,
30 Iterator,
31 Literal,
32 NotRequired,
33 Protocol,
34 Required,
35 Self,
36 TypeAlias,
37 TypedDict,
38 override,
39 runtime_checkable,
40)
42import pydantic
43from pydantic import ConfigDict, Field
45logger = logging.getLogger(__name__)
48class ConstModel(pydantic.BaseModel):
49 model_config = ConfigDict(
50 from_attributes=True,
51 extra="forbid",
52 use_enum_values=True,
53 validate_default=True,
54 validate_assignment=True,
55 )
57 @override
58 def __eq__(self, other: Any) -> bool:
59 if isinstance(other, dict):
60 # Ensure the dictionary keys match the model fields
61 expected_keys = set(self.model_fields.keys())
62 if set(other.keys()) != expected_keys:
63 return False
64 return all(getattr(self, key) == other.get(key) for key in expected_keys)
66 # This check probably isn't necessary before calling super (TODO?)
67 if isinstance(other, self.__class__):
68 # Compare all fields of the model
69 return self.model_dump() == other.model_dump()
71 return super().__eq__(other)
74class URLS:
75 index: Template = Template("/api/")
76 token: Template = Template("/api/token/")
77 list: Template = Template("/api/${resource}/")
78 detail: Template = Template("/api/${resource}/${pk}/")
79 create: Template = Template("/api/${resource}/")
80 update: Template = Template("/api/${resource}/${pk}/")
81 delete: Template = Template("/api/${resource}/${pk}/")
82 meta: Template = Template("/api/document/${pk}/metadata/")
83 next_asn: Template = Template("/api/document/next_asn/")
84 notes: Template = Template("/api/document/${pk}/notes/")
85 post: Template = Template("/api/document/post_document/")
86 single: Template = Template("/api/document/${pk}/")
87 suggestions: Template = Template("/api/${resource}/${pk}/suggestions/")
88 preview: Template = Template("/api/${resource}/${pk}/preview/")
89 thumbnail: Template = Template("/api/${resource}/${pk}/thumb/")
90 download: Template = Template("/api/${resource}/${pk}/download/")
93CommonEndpoints: TypeAlias = Literal["list", "detail", "create", "update", "delete"]
94Endpoints: TypeAlias = dict[CommonEndpoints | str, Template]
97class FilteringStrategies(StrEnum):
98 WHITELIST = "whitelist"
99 BLACKLIST = "blacklist"
100 ALLOW_ALL = "allow_all"
101 ALLOW_NONE = "allow_none"
104class ModelStatus(StrEnum):
105 INITIALIZING = "initializing"
106 UPDATING = "updating"
107 SAVING = "saving"
108 READY = "ready"
109 ERROR = "error"
112class CustomFieldTypes(StrEnum):
113 STRING = "string"
114 BOOLEAN = "boolean"
115 INTEGER = "integer"
116 FLOAT = "float"
117 MONETARY = "monetary"
118 DATE = "date"
119 URL = "url"
120 DOCUMENT_LINK = "documentlink"
121 UNKNOWN = "unknown"
123 @override
124 @classmethod
125 def _missing_(cls, value: object) -> "Literal[CustomFieldTypes.UNKNOWN]":
126 logger.debug("Handling unknown enum value", extra={"enum_class": cls.__name__, "value": value})
127 return cls.UNKNOWN
130class CustomFieldValues(ConstModel):
131 field: int
132 value: Any
135class CustomFieldTypedDict(TypedDict):
136 field: int
137 value: Any
140class DocumentMetadataType(ConstModel):
141 namespace: str | None = None
142 prefix: str | None = None
143 key: str | None = None
144 value: str | None = None
147class DocumentSearchHitType(ConstModel):
148 score: float | None = None
149 highlights: str | None = None
150 note_highlights: str | None = None
151 rank: int | None = None
154class MatchingAlgorithmType(IntEnum):
155 NONE = 0
156 ANY = 1
157 ALL = 2
158 LITERAL = 3
159 REGEX = 4
160 FUZZY = 5
161 AUTO = 6
162 UNKNOWN = -1
164 @override
165 @classmethod
166 def _missing_(cls, value: object) -> "Literal[MatchingAlgorithmType.UNKNOWN]":
167 logger.debug("Handling unknown enum value", extra={"enum_class": cls.__name__, "value": value})
168 return cls.UNKNOWN
171class PermissionSetType(ConstModel):
172 users: list[int] = Field(default_factory=list)
173 groups: list[int] = Field(default_factory=list)
176class PermissionTableType(ConstModel):
177 view: PermissionSetType = Field(default_factory=PermissionSetType)
178 change: PermissionSetType = Field(default_factory=PermissionSetType)
181class RetrieveFileMode(StrEnum):
182 DOWNLOAD = "download"
183 PREVIEW = "preview"
184 THUMBNAIL = "thumb"
187class SavedViewFilterRuleType(ConstModel):
188 rule_type: int | None = None
189 value: str | None = None
192class ShareLinkFileVersionType(StrEnum):
193 ARCHIVE = "archive"
194 ORIGINAL = "original"
195 UNKNOWN = "unknown"
197 @override
198 @classmethod
199 def _missing_(cls, value: object) -> "Literal[ShareLinkFileVersionType.UNKNOWN]":
200 logger.debug("Handling unknown enum value", extra={"enum_class": cls.__name__, "value": value})
201 return cls.UNKNOWN
204class StatusType(StrEnum):
205 OK = "OK"
206 ERROR = "ERROR"
207 UNKNOWN = "UNKNOWN"
209 @override
210 @classmethod
211 def _missing_(cls, value: object) -> "Literal[StatusType.UNKNOWN]":
212 logger.debug("Handling unknown enum value", extra={"enum_class": cls.__name__, "value": value})
213 return cls.UNKNOWN
216class StatusDatabaseMigrationStatusType(ConstModel):
217 latest_migration: str | None = None
218 unapplied_migrations: list[str] = Field(default_factory=list)
221class StatusDatabaseType(ConstModel):
222 type: str | None = None
223 url: str | None = None
224 status: StatusType | None = None
225 error: str | None = None
226 migration_status: StatusDatabaseMigrationStatusType | None = None
229class StatusStorageType(ConstModel):
230 total: int | None = None
231 available: int | None = None
234class StatusTasksType(ConstModel):
235 redis_url: str | None = None
236 redis_status: StatusType | None = None
237 redis_error: str | None = None
238 celery_status: StatusType | None = None
239 index_status: StatusType | None = None
240 index_last_modified: datetime | None = None
241 index_error: str | None = None
242 classifier_status: StatusType | None = None
243 classifier_last_trained: datetime | None = None
244 classifier_error: str | None = None
247class TaskStatusType(StrEnum):
248 PENDING = "PENDING"
249 STARTED = "STARTED"
250 SUCCESS = "SUCCESS"
251 FAILURE = "FAILURE"
252 UNKNOWN = "UNKNOWN"
254 @override
255 @classmethod
256 def _missing_(cls, value: object) -> "Literal[TaskStatusType.UNKNOWN]":
257 logger.debug("Handling unknown enum value", extra={"enum_class": cls.__name__, "value": value})
258 return cls.UNKNOWN
261class WorkflowActionType(IntEnum):
262 ASSIGNMENT = 1
263 UNKNOWN = -1
265 @override
266 @classmethod
267 def _missing_(cls, value: object) -> "Literal[WorkflowActionType.UNKNOWN]":
268 logger.debug("Handling unknown enum value", extra={"enum_class": cls.__name__, "value": value})
269 return cls.UNKNOWN
272class WorkflowTriggerType(IntEnum):
273 CONSUMPTION = 1
274 DOCUMENT_ADDED = 2
275 DOCUMENT_UPDATED = 3
276 UNKNOWN = -1
278 @override
279 @classmethod
280 def _missing_(cls, value: object) -> "Literal[WorkflowTriggerType.UNKNOWN]":
281 logger.debug("Handling unknown enum value", extra={"enum_class": cls.__name__, "value": value})
282 return cls.UNKNOWN
285class WorkflowTriggerSourceType(IntEnum):
286 CONSUME_FOLDER = 1
287 API_UPLOAD = 2
288 MAIL_FETCH = 3
289 UNKNOWN = -1
291 @override
292 @classmethod
293 def _missing_(cls, value: object) -> "Literal[WorkflowTriggerSourceType.UNKNOWN]":
294 logger.debug("Handling unknown enum value", extra={"enum_class": cls.__name__, "value": value})
295 return cls.UNKNOWN