easypaperless
easypaperless — Python API wrapper for paperless-ngx.
1"""easypaperless — Python API wrapper for paperless-ngx.""" 2 3import importlib.metadata 4import logging 5 6__version__: str = importlib.metadata.version("easypaperless") 7 8from easypaperless.client import PaperlessClient 9from easypaperless.exceptions import ( 10 AuthError, 11 NotFoundError, 12 PaperlessError, 13 ServerError, 14 TaskTimeoutError, 15 UploadError, 16 ValidationError, 17) 18from easypaperless.models import ( 19 MatchingAlgorithm, 20 Correspondent, 21 CustomField, 22 CustomFieldValue, 23 Document, 24 DocumentMetadata, 25 DocumentNote, 26 DocumentType, 27 FieldDataType, 28 FileMetadataEntry, 29 PermissionSet, 30 SearchHit, 31 SetPermissions, 32 StoragePath, 33 Tag, 34 Task, 35 TaskStatus, 36) 37from easypaperless.sync import SyncPaperlessClient 38 39logging.getLogger("easypaperless").addHandler(logging.NullHandler()) 40 41__all__ = [ 42 "__version__", 43 # Clients 44 "PaperlessClient", 45 "SyncPaperlessClient", 46 # Models 47 "MatchingAlgorithm", 48 "Correspondent", 49 "CustomField", 50 "CustomFieldValue", 51 "Document", 52 "DocumentMetadata", 53 "DocumentNote", 54 "DocumentType", 55 "FieldDataType", 56 "FileMetadataEntry", 57 "PermissionSet", 58 "SearchHit", 59 "SetPermissions", 60 "StoragePath", 61 "Tag", 62 "Task", 63 "TaskStatus", 64 # Exceptions 65 "PaperlessError", 66 "AuthError", 67 "NotFoundError", 68 "ValidationError", 69 "ServerError", 70 "UploadError", 71 "TaskTimeoutError", 72]
105class PaperlessClient( 106 DocumentsMixin, 107 NotesMixin, 108 UploadMixin, 109 DocumentBulkMixin, 110 NonDocumentBulkMixin, 111 TagsMixin, 112 CorrespondentsMixin, 113 DocumentTypesMixin, 114 StoragePathsMixin, 115 CustomFieldsMixin, 116 _ClientCore, 117): 118 """Async client for the paperless-ngx API. 119 120 All operations are flat methods on the client. String names are resolved 121 to integer IDs automatically wherever the API requires IDs (tags, 122 correspondents, document types, storage paths). 123 124 Use as an async context manager to ensure the underlying HTTP connection 125 pool is closed when you are done: 126 127 Example: 128 async with PaperlessClient(url="http://localhost:8000", api_key="abc") as client: 129 docs = await client.list_documents() 130 """ 131 132 # Explicit assignments so pdoc documents these inherited methods. 133 # Documents 134 list_documents = DocumentsMixin.list_documents 135 get_document = DocumentsMixin.get_document 136 get_document_metadata = DocumentsMixin.get_document_metadata 137 update_document = DocumentsMixin.update_document 138 delete_document = DocumentsMixin.delete_document 139 download_document = DocumentsMixin.download_document 140 # Notes 141 get_notes = NotesMixin.get_notes 142 create_note = NotesMixin.create_note 143 delete_note = NotesMixin.delete_note 144 # Upload 145 upload_document = UploadMixin.upload_document 146 # Document bulk operations 147 bulk_edit = DocumentBulkMixin.bulk_edit 148 bulk_add_tag = DocumentBulkMixin.bulk_add_tag 149 bulk_remove_tag = DocumentBulkMixin.bulk_remove_tag 150 bulk_modify_tags = DocumentBulkMixin.bulk_modify_tags 151 bulk_delete = DocumentBulkMixin.bulk_delete 152 bulk_set_correspondent = DocumentBulkMixin.bulk_set_correspondent 153 bulk_set_document_type = DocumentBulkMixin.bulk_set_document_type 154 bulk_set_storage_path = DocumentBulkMixin.bulk_set_storage_path 155 bulk_modify_custom_fields = DocumentBulkMixin.bulk_modify_custom_fields 156 bulk_set_permissions = DocumentBulkMixin.bulk_set_permissions 157 # Non-document bulk operations 158 bulk_edit_objects = NonDocumentBulkMixin.bulk_edit_objects 159 bulk_delete_tags = NonDocumentBulkMixin.bulk_delete_tags 160 bulk_delete_correspondents = NonDocumentBulkMixin.bulk_delete_correspondents 161 bulk_delete_document_types = NonDocumentBulkMixin.bulk_delete_document_types 162 bulk_delete_storage_paths = NonDocumentBulkMixin.bulk_delete_storage_paths 163 bulk_set_permissions_tags = NonDocumentBulkMixin.bulk_set_permissions_tags 164 bulk_set_permissions_correspondents = NonDocumentBulkMixin.bulk_set_permissions_correspondents 165 bulk_set_permissions_document_types = NonDocumentBulkMixin.bulk_set_permissions_document_types 166 bulk_set_permissions_storage_paths = NonDocumentBulkMixin.bulk_set_permissions_storage_paths 167 # Tags 168 list_tags = TagsMixin.list_tags 169 get_tag = TagsMixin.get_tag 170 create_tag = TagsMixin.create_tag 171 update_tag = TagsMixin.update_tag 172 delete_tag = TagsMixin.delete_tag 173 # Correspondents 174 list_correspondents = CorrespondentsMixin.list_correspondents 175 get_correspondent = CorrespondentsMixin.get_correspondent 176 create_correspondent = CorrespondentsMixin.create_correspondent 177 update_correspondent = CorrespondentsMixin.update_correspondent 178 delete_correspondent = CorrespondentsMixin.delete_correspondent 179 # Document types 180 list_document_types = DocumentTypesMixin.list_document_types 181 get_document_type = DocumentTypesMixin.get_document_type 182 create_document_type = DocumentTypesMixin.create_document_type 183 update_document_type = DocumentTypesMixin.update_document_type 184 delete_document_type = DocumentTypesMixin.delete_document_type 185 # Storage paths 186 list_storage_paths = StoragePathsMixin.list_storage_paths 187 get_storage_path = StoragePathsMixin.get_storage_path 188 create_storage_path = StoragePathsMixin.create_storage_path 189 update_storage_path = StoragePathsMixin.update_storage_path 190 delete_storage_path = StoragePathsMixin.delete_storage_path 191 # Custom fields 192 list_custom_fields = CustomFieldsMixin.list_custom_fields 193 get_custom_field = CustomFieldsMixin.get_custom_field 194 create_custom_field = CustomFieldsMixin.create_custom_field 195 update_custom_field = CustomFieldsMixin.update_custom_field 196 delete_custom_field = CustomFieldsMixin.delete_custom_field 197 198 199 200 def __init__( 201 self, 202 url: str, 203 api_key: str, 204 *, 205 timeout: float = 30.0, 206 poll_interval: float = 2.0, 207 poll_timeout: float = 60.0, 208 ) -> None: 209 """Create an async paperless-ngx client. 210 211 Args: 212 url: Base URL of the paperless-ngx instance 213 (e.g. ``"http://localhost:8000"``). 214 api_key: API token. Generate one in paperless-ngx under 215 *Settings → API → Generate Token*. 216 timeout: Default request timeout in seconds. Individual 217 operations (e.g. uploads) may override this per-call. 218 Default: ``30.0``. 219 poll_interval: Seconds between status checks when ``wait=True`` 220 is passed to :meth:`upload_document`. Default: ``2.0``. 221 poll_timeout: Maximum seconds to wait for a document to finish 222 processing before raising 223 :exc:`~easypaperless.exceptions.TaskTimeoutError`. 224 Default: ``60.0``. 225 """ 226 super().__init__( 227 url, api_key, timeout=timeout, poll_interval=poll_interval, poll_timeout=poll_timeout 228 ) 229 230 async def __aenter__(self) -> PaperlessClient: 231 return self 232 233 async def __aexit__(self, *args: Any) -> None: 234 await self.close()
Async client for the paperless-ngx API.
All operations are flat methods on the client. String names are resolved to integer IDs automatically wherever the API requires IDs (tags, correspondents, document types, storage paths).
Use as an async context manager to ensure the underlying HTTP connection pool is closed when you are done:
Example: async with PaperlessClient(url="http://localhost:8000", api_key="abc") as client: docs = await client.list_documents()
200 def __init__( 201 self, 202 url: str, 203 api_key: str, 204 *, 205 timeout: float = 30.0, 206 poll_interval: float = 2.0, 207 poll_timeout: float = 60.0, 208 ) -> None: 209 """Create an async paperless-ngx client. 210 211 Args: 212 url: Base URL of the paperless-ngx instance 213 (e.g. ``"http://localhost:8000"``). 214 api_key: API token. Generate one in paperless-ngx under 215 *Settings → API → Generate Token*. 216 timeout: Default request timeout in seconds. Individual 217 operations (e.g. uploads) may override this per-call. 218 Default: ``30.0``. 219 poll_interval: Seconds between status checks when ``wait=True`` 220 is passed to :meth:`upload_document`. Default: ``2.0``. 221 poll_timeout: Maximum seconds to wait for a document to finish 222 processing before raising 223 :exc:`~easypaperless.exceptions.TaskTimeoutError`. 224 Default: ``60.0``. 225 """ 226 super().__init__( 227 url, api_key, timeout=timeout, poll_interval=poll_interval, poll_timeout=poll_timeout 228 )
Create an async paperless-ngx client.
Args:
url: Base URL of the paperless-ngx instance
(e.g. "http://localhost:8000").
api_key: API token. Generate one in paperless-ngx under
*Settings → API → Generate Token*.
timeout: Default request timeout in seconds. Individual
operations (e.g. uploads) may override this per-call.
Default: 30.0.
poll_interval: Seconds between status checks when wait=True
is passed to upload_document(). Default: 2.0.
poll_timeout: Maximum seconds to wait for a document to finish
processing before raising
~easypaperless.exceptions.TaskTimeoutError.
Default: 60.0.
115 async def list_documents( 116 self, 117 *, 118 search: str | None = None, 119 search_mode: str = "title_or_text", 120 ids: list[int] | None = None, 121 tags: list[int | str] | None = None, 122 any_tags: list[int | str] | None = None, 123 exclude_tags: list[int | str] | None = None, 124 correspondent: int | str | None = None, 125 any_correspondent: list[int | str] | None = None, 126 exclude_correspondents: list[int | str] | None = None, 127 document_type: int | str | None = None, 128 any_document_type: list[int | str] | None = None, 129 exclude_document_types: list[int | str] | None = None, 130 storage_path: int | str | None = None, 131 any_storage_paths: list[int | str] | None = None, 132 exclude_storage_paths: list[int | str] | None = None, 133 owner: int | None = None, 134 exclude_owners: list[int] | None = None, 135 custom_fields: list[int | str] | None = None, 136 any_custom_fields: list[int | str] | None = None, 137 exclude_custom_fields: list[int | str] | None = None, 138 custom_field_query: list[Any] | None = None, 139 archive_serial_number: int | None = None, 140 archive_serial_number_from: int | None = None, 141 archive_serial_number_till: int | None = None, 142 created_after: date | str | None = None, 143 created_before: date | str | None = None, 144 added_after: date | datetime | str | None = None, 145 added_from: date | datetime | str | None = None, 146 added_before: date | datetime | str | None = None, 147 added_until: date | datetime | str | None = None, 148 modified_after: date | datetime | str | None = None, 149 modified_from: date | datetime | str | None = None, 150 modified_before: date | datetime | str | None = None, 151 modified_until: date | datetime | str | None = None, 152 checksum: str | None = None, 153 page_size: int = 25, 154 page: int | None = None, 155 ordering: str | None = None, 156 descending: bool = False, 157 max_results: int | None = None, 158 on_page: Callable[[int, int | None], None] | None = None, 159 ) -> list[Document]: 160 """Return a filtered list of documents. 161 162 All tag, correspondent, document-type, storage-path, and custom-field 163 parameters accept either integer IDs or string names — names are 164 resolved to IDs transparently. 165 166 Args: 167 search: Search string. Behaviour depends on ``search_mode``. 168 search_mode: How ``search`` is applied. One of: 169 170 * ``"title_or_text"`` *(default)* — full-text search across 171 title and OCR content (Whoosh FTS, raw API ``search`` 172 parameter). 173 * ``"title"`` — case-insensitive substring match on title 174 only (raw API ``title__icontains``). 175 * ``"query"`` — paperless query language, e.g. 176 ``"tag:invoice date:[2024 TO *]"`` (raw API ``query``). 177 * ``"original_filename"`` — case-insensitive substring match 178 on the original file name (raw API 179 ``original_filename__icontains``). 180 181 ids: Return only documents whose ID is in this list. 182 tags: Documents must have **all** of these tags (AND semantics). 183 Accepts tag IDs or tag names. 184 any_tags: Documents must have **at least one** of these tags 185 (OR semantics). Accepts tag IDs or tag names. 186 exclude_tags: Documents must have **none** of these tags. 187 Accepts tag IDs or tag names. 188 correspondent: Filter to documents assigned to exactly this 189 correspondent. Accepts a correspondent ID or name. 190 any_correspondent: Filter to documents assigned to **any** of 191 these correspondents (OR semantics). Accepts IDs or names. 192 Takes precedence over ``correspondent`` when both are given. 193 exclude_correspondents: Exclude documents assigned to any of 194 these correspondents. Accepts IDs or names. 195 document_type: Filter to documents of exactly this type. 196 Accepts a document-type ID or name. 197 any_document_type: Filter to documents whose type is **any** of 198 these (OR semantics). Accepts IDs or names. Takes 199 precedence over ``document_type`` when both are given. 200 exclude_document_types: Exclude documents whose type is any of 201 these. Accepts IDs or names. 202 storage_path: Filter to documents assigned to exactly this 203 storage path. Accepts a storage path ID or name. 204 any_storage_paths: Filter to documents assigned to **any** of 205 these storage paths (OR semantics). Accepts IDs or names. 206 Takes precedence over ``storage_path`` when both are given. 207 exclude_storage_paths: Exclude documents assigned to any of 208 these storage paths. Accepts IDs or names. 209 owner: Filter to documents owned by this user ID. 210 exclude_owners: Exclude documents owned by any of these user IDs. 211 custom_fields: Documents must have **all** of these custom fields 212 set (AND semantics). Accepts custom field IDs or names. 213 any_custom_fields: Documents must have **at least one** of these 214 custom fields set (OR semantics). Accepts IDs or names. 215 exclude_custom_fields: Documents must have **none** of these 216 custom fields set. Accepts IDs or names. 217 custom_field_query: Filter documents by custom field values using 218 a structured query. Simple form: 219 ``["Invoice Amount", "gt", 100]``. Compound form: 220 ``["AND", [["Amount", "gt", 100], ["Status", "exact", "paid"]]]``. 221 Field references accept integer IDs or string names (resolved 222 server-side, not by the client). 223 archive_serial_number: Filter by exact archive serial number. 224 archive_serial_number_from: Filter by archive serial number 225 greater than or equal to this value. 226 archive_serial_number_till: Filter by archive serial number 227 less than or equal to this value. 228 created_after: ISO-8601 date string or ``date`` object. Only 229 documents created **after** this date are returned. 230 created_before: ISO-8601 date string or ``date`` object. Only 231 documents created **before** this date are returned. 232 added_after: Date, datetime, or ISO-8601 string. Only 233 documents **added** (ingested) after this date/time. 234 added_from: Date, datetime, or ISO-8601 string. Only 235 documents **added** on or after this date/time. 236 added_before: Date, datetime, or ISO-8601 string. Only 237 documents **added** before this date/time. 238 added_until: Date, datetime, or ISO-8601 string. Only 239 documents **added** on or before this date/time. 240 modified_after: Date, datetime, or ISO-8601 string. Only 241 documents **modified** after this date/time. 242 modified_from: Date, datetime, or ISO-8601 string. Only 243 documents **modified** on or after this date/time. 244 modified_before: Date, datetime, or ISO-8601 string. Only 245 documents **modified** before this date/time. 246 modified_until: Date, datetime, or ISO-8601 string. Only 247 documents **modified** on or before this date/time. 248 checksum: MD5 checksum of the original file (exact match). 249 page_size: Number of results per API page. Default: ``25``. 250 page: Return only this specific page of results (1-based). When 251 set, auto-pagination is disabled. Default: ``None``. 252 ordering: Field name to sort by. Default: ``None``. 253 descending: When ``True``, reverses the sort direction. 254 Default: ``False``. 255 max_results: Stop after collecting this many documents. 256 Default: ``None``. 257 on_page: Callback invoked after each page fetch. Receives 258 ``(fetched_so_far, total)``. Ignored when ``page`` is set. 259 260 Returns: 261 List of :class:`~easypaperless.models.documents.Document` 262 objects. 263 """ 264 params: dict[str, Any] = {"page_size": page_size} 265 266 if search is not None: 267 api_param = _SEARCH_MODE_MAP.get(search_mode, "search") 268 params[api_param] = search 269 270 if ids is not None: 271 params["id__in"] = ",".join(str(i) for i in ids) 272 273 if tags is not None: 274 resolved = await self._resolver.resolve_list("tags", tags) 275 params["tags__id__all"] = ",".join(str(t) for t in resolved) 276 277 if any_tags is not None: 278 resolved = await self._resolver.resolve_list("tags", any_tags) 279 params["tags__id__in"] = ",".join(str(t) for t in resolved) 280 281 if exclude_tags is not None: 282 resolved = await self._resolver.resolve_list("tags", exclude_tags) 283 params["tags__id__none"] = ",".join(str(t) for t in resolved) 284 285 if any_correspondent is not None: 286 resolved = await self._resolver.resolve_list("correspondents", any_correspondent) 287 params["correspondent__id__in"] = ",".join(str(c) for c in resolved) 288 elif correspondent is not None: 289 resolved_id = await self._resolver.resolve("correspondents", correspondent) 290 params["correspondent__id__in"] = resolved_id 291 292 if exclude_correspondents is not None: 293 resolved = await self._resolver.resolve_list("correspondents", exclude_correspondents) 294 params["correspondent__id__none"] = ",".join(str(c) for c in resolved) 295 296 if any_document_type is not None: 297 resolved = await self._resolver.resolve_list("document_types", any_document_type) 298 params["document_type__id__in"] = ",".join(str(d) for d in resolved) 299 elif document_type is not None: 300 resolved_id = await self._resolver.resolve("document_types", document_type) 301 params["document_type"] = resolved_id 302 303 if exclude_document_types is not None: 304 resolved = await self._resolver.resolve_list("document_types", exclude_document_types) 305 params["document_type__id__none"] = ",".join(str(d) for d in resolved) 306 307 # Storage path filters 308 if any_storage_paths is not None: 309 resolved = await self._resolver.resolve_list("storage_paths", any_storage_paths) 310 params["storage_path__id__in"] = ",".join(str(s) for s in resolved) 311 elif storage_path is not None: 312 resolved_id = await self._resolver.resolve("storage_paths", storage_path) 313 params["storage_path__id__in"] = resolved_id 314 315 if exclude_storage_paths is not None: 316 resolved = await self._resolver.resolve_list("storage_paths", exclude_storage_paths) 317 params["storage_path__id__none"] = ",".join(str(s) for s in resolved) 318 319 # Owner filters 320 if owner is not None: 321 params["owner__id__in"] = owner 322 323 if exclude_owners is not None: 324 params["owner__id__none"] = ",".join(str(o) for o in exclude_owners) 325 326 # Custom field existence filters 327 if custom_fields is not None: 328 resolved = await self._resolver.resolve_list("custom_fields", custom_fields) 329 params["custom_fields__id__all"] = ",".join(str(f) for f in resolved) 330 331 if any_custom_fields is not None: 332 resolved = await self._resolver.resolve_list("custom_fields", any_custom_fields) 333 params["custom_fields__id__in"] = ",".join(str(f) for f in resolved) 334 335 if exclude_custom_fields is not None: 336 resolved = await self._resolver.resolve_list("custom_fields", exclude_custom_fields) 337 params["custom_fields__id__none"] = ",".join(str(f) for f in resolved) 338 339 # Custom field value query 340 if custom_field_query is not None: 341 params["custom_field_query"] = json.dumps(custom_field_query) 342 343 # Archive serial number filters 344 if archive_serial_number is not None: 345 params["archive_serial_number"] = archive_serial_number 346 347 if archive_serial_number_from is not None: 348 params["archive_serial_number__gte"] = archive_serial_number_from 349 350 if archive_serial_number_till is not None: 351 params["archive_serial_number__lte"] = archive_serial_number_till 352 353 # Date filters — created (date only) 354 if created_after is not None: 355 params["created__date__gt"] = self._format_date_value(created_after) 356 357 if created_before is not None: 358 params["created__date__lt"] = self._format_date_value(created_before) 359 360 # Date filters — added (date or datetime) 361 if added_after is not None: 362 key = "added__gt" if self._is_datetime(added_after) else "added__date__gt" 363 params[key] = self._format_date_value(added_after) 364 365 if added_from is not None: 366 key = "added__gte" if self._is_datetime(added_from) else "added__date__gte" 367 params[key] = self._format_date_value(added_from) 368 369 if added_before is not None: 370 key = "added__lt" if self._is_datetime(added_before) else "added__date__lt" 371 params[key] = self._format_date_value(added_before) 372 373 if added_until is not None: 374 key = "added__lte" if self._is_datetime(added_until) else "added__date__lte" 375 params[key] = self._format_date_value(added_until) 376 377 # Date filters — modified (date or datetime) 378 if modified_after is not None: 379 key = "modified__gt" if self._is_datetime(modified_after) else "modified__date__gt" 380 params[key] = self._format_date_value(modified_after) 381 382 if modified_from is not None: 383 key = "modified__gte" if self._is_datetime(modified_from) else "modified__date__gte" 384 params[key] = self._format_date_value(modified_from) 385 386 if modified_before is not None: 387 key = "modified__lt" if self._is_datetime(modified_before) else "modified__date__lt" 388 params[key] = self._format_date_value(modified_before) 389 390 if modified_until is not None: 391 key = "modified__lte" if self._is_datetime(modified_until) else "modified__date__lte" 392 params[key] = self._format_date_value(modified_until) 393 394 if checksum is not None: 395 params["checksum__iexact"] = checksum 396 397 if ordering is not None: 398 params["ordering"] = f"-{ordering}" if descending else ordering 399 400 if page is not None: 401 params["page"] = page 402 resp = await self._session.get("/documents/", params=params) 403 items = resp.json().get("results", []) 404 if max_results is not None: 405 items = items[:max_results] 406 return [Document.model_validate(item) for item in items] 407 408 items = await self._session.get_all_pages( 409 "/documents/", params, max_results=max_results, on_page=on_page 410 ) 411 return [Document.model_validate(item) for item in items]
Return a filtered list of documents.
All tag, correspondent, document-type, storage-path, and custom-field parameters accept either integer IDs or string names — names are resolved to IDs transparently.
Args:
search: Search string. Behaviour depends on search_mode.
search_mode: How search is applied. One of:
* ``"title_or_text"`` *(default)* — full-text search across
title and OCR content (Whoosh FTS, raw API ``search``
parameter).
* ``"title"`` — case-insensitive substring match on title
only (raw API ``title__icontains``).
* ``"query"`` — paperless query language, e.g.
``"tag:invoice date:[2024 TO *]"`` (raw API ``query``).
* ``"original_filename"`` — case-insensitive substring match
on the original file name (raw API
``original_filename__icontains``).
ids: Return only documents whose ID is in this list.
tags: Documents must have **all** of these tags (AND semantics).
Accepts tag IDs or tag names.
any_tags: Documents must have **at least one** of these tags
(OR semantics). Accepts tag IDs or tag names.
exclude_tags: Documents must have **none** of these tags.
Accepts tag IDs or tag names.
correspondent: Filter to documents assigned to exactly this
correspondent. Accepts a correspondent ID or name.
any_correspondent: Filter to documents assigned to **any** of
these correspondents (OR semantics). Accepts IDs or names.
Takes precedence over ``correspondent`` when both are given.
exclude_correspondents: Exclude documents assigned to any of
these correspondents. Accepts IDs or names.
document_type: Filter to documents of exactly this type.
Accepts a document-type ID or name.
any_document_type: Filter to documents whose type is **any** of
these (OR semantics). Accepts IDs or names. Takes
precedence over ``document_type`` when both are given.
exclude_document_types: Exclude documents whose type is any of
these. Accepts IDs or names.
storage_path: Filter to documents assigned to exactly this
storage path. Accepts a storage path ID or name.
any_storage_paths: Filter to documents assigned to **any** of
these storage paths (OR semantics). Accepts IDs or names.
Takes precedence over ``storage_path`` when both are given.
exclude_storage_paths: Exclude documents assigned to any of
these storage paths. Accepts IDs or names.
owner: Filter to documents owned by this user ID.
exclude_owners: Exclude documents owned by any of these user IDs.
custom_fields: Documents must have **all** of these custom fields
set (AND semantics). Accepts custom field IDs or names.
any_custom_fields: Documents must have **at least one** of these
custom fields set (OR semantics). Accepts IDs or names.
exclude_custom_fields: Documents must have **none** of these
custom fields set. Accepts IDs or names.
custom_field_query: Filter documents by custom field values using
a structured query. Simple form:
``["Invoice Amount", "gt", 100]``. Compound form:
``["AND", [["Amount", "gt", 100], ["Status", "exact", "paid"]]]``.
Field references accept integer IDs or string names (resolved
server-side, not by the client).
archive_serial_number: Filter by exact archive serial number.
archive_serial_number_from: Filter by archive serial number
greater than or equal to this value.
archive_serial_number_till: Filter by archive serial number
less than or equal to this value.
created_after: ISO-8601 date string or ``date`` object. Only
documents created **after** this date are returned.
created_before: ISO-8601 date string or ``date`` object. Only
documents created **before** this date are returned.
added_after: Date, datetime, or ISO-8601 string. Only
documents **added** (ingested) after this date/time.
added_from: Date, datetime, or ISO-8601 string. Only
documents **added** on or after this date/time.
added_before: Date, datetime, or ISO-8601 string. Only
documents **added** before this date/time.
added_until: Date, datetime, or ISO-8601 string. Only
documents **added** on or before this date/time.
modified_after: Date, datetime, or ISO-8601 string. Only
documents **modified** after this date/time.
modified_from: Date, datetime, or ISO-8601 string. Only
documents **modified** on or after this date/time.
modified_before: Date, datetime, or ISO-8601 string. Only
documents **modified** before this date/time.
modified_until: Date, datetime, or ISO-8601 string. Only
documents **modified** on or before this date/time.
checksum: MD5 checksum of the original file (exact match).
page_size: Number of results per API page. Default: ``25``.
page: Return only this specific page of results (1-based). When
set, auto-pagination is disabled. Default: ``None``.
ordering: Field name to sort by. Default: ``None``.
descending: When ``True``, reverses the sort direction.
Default: ``False``.
max_results: Stop after collecting this many documents.
Default: ``None``.
on_page: Callback invoked after each page fetch. Receives
``(fetched_so_far, total)``. Ignored when ``page`` is set.
Returns:
List of ~easypaperless.models.documents.Document
objects.
38 async def get_document(self, id: int, *, include_metadata: bool = False) -> Document: 39 """Fetch a single document by its ID. 40 41 Args: 42 id: Numeric paperless-ngx document ID. 43 include_metadata: When ``True``, the extended file-level metadata 44 (checksums, sizes, MIME type) is fetched concurrently from 45 ``/documents/{id}/metadata/`` and attached to 46 :attr:`~easypaperless.models.documents.Document.metadata`. 47 Default: ``False``. 48 49 Returns: 50 The :class:`~easypaperless.models.documents.Document` with the 51 given ID. When ``include_metadata=True`` the ``metadata`` 52 attribute is populated; otherwise it is ``None``. 53 54 Raises: 55 ~easypaperless.exceptions.NotFoundError: If no document exists 56 with that ID. 57 """ 58 if include_metadata: 59 doc_resp, meta_resp = await asyncio.gather( 60 self._session.get(f"/documents/{id}/"), 61 self._session.get(f"/documents/{id}/metadata/"), 62 ) 63 data = doc_resp.json() 64 data["metadata"] = meta_resp.json() 65 else: 66 resp = await self._session.get(f"/documents/{id}/") 67 data = resp.json() 68 return Document.model_validate(data)
Fetch a single document by its ID.
Args:
id: Numeric paperless-ngx document ID.
include_metadata: When True, the extended file-level metadata
(checksums, sizes, MIME type) is fetched concurrently from
/documents/{id}/metadata/ and attached to
~easypaperless.models.documents.Document.metadata.
Default: False.
Returns:
The ~easypaperless.models.documents.Document with the
given ID. When include_metadata=True the metadata
attribute is populated; otherwise it is None.
Raises: ~easypaperless.exceptions.NotFoundError: If no document exists with that ID.
70 async def get_document_metadata(self, id: int) -> DocumentMetadata: 71 """Fetch the extended file-level metadata for a document. 72 73 This is a lower-overhead alternative to 74 ``get_document(id, include_metadata=True)`` when you only need the 75 metadata and not the full document object. 76 77 Args: 78 id: Numeric paperless-ngx document ID. 79 80 Returns: 81 A :class:`~easypaperless.models.documents.DocumentMetadata` 82 instance containing checksums, sizes, MIME type, and embedded 83 file metadata. 84 85 Raises: 86 ~easypaperless.exceptions.NotFoundError: If no document exists 87 with that ID. 88 """ 89 resp = await self._session.get(f"/documents/{id}/metadata/") 90 return DocumentMetadata.model_validate(resp.json())
Fetch the extended file-level metadata for a document.
This is a lower-overhead alternative to
get_document(id, include_metadata=True) when you only need the
metadata and not the full document object.
Args: id: Numeric paperless-ngx document ID.
Returns:
A ~easypaperless.models.documents.DocumentMetadata
instance containing checksums, sizes, MIME type, and embedded
file metadata.
Raises: ~easypaperless.exceptions.NotFoundError: If no document exists with that ID.
413 async def update_document( 414 self, 415 id: int, 416 *, 417 title: str | None = None, 418 content: str | None = None, 419 date: str | None = None, 420 correspondent: int | str | None = None, 421 document_type: int | str | None = None, 422 storage_path: int | str | None = None, 423 tags: list[int | str] | None = None, 424 asn: int | None = None, 425 custom_fields: list[dict[str, Any]] | None = None, 426 owner: int | None = None, 427 set_permissions: SetPermissions | None = None, 428 ) -> Document: 429 """Partially update a document (PATCH semantics — only passed fields change). 430 431 Args: 432 id: Numeric ID of the document to update. 433 title: New document title. 434 content: OCR text content of the document. 435 date: Creation date as an ISO-8601 string (``"YYYY-MM-DD"``). 436 correspondent: Correspondent to assign, as an ID or name. 437 Pass ``0`` to clear. 438 document_type: Document type to assign, as an ID or name. 439 Pass ``0`` to clear. 440 storage_path: Storage path to assign, as an ID or name. 441 Pass ``0`` to clear. 442 tags: Full replacement list of tags (IDs or names). The existing 443 tag list is replaced, not merged. 444 asn: Archive serial number to assign. 445 custom_fields: List of ``{"field": <field_id>, "value": ...}`` 446 dicts. Replaces the existing custom-field values. 447 owner: Numeric user ID to assign as document owner. 448 set_permissions: Explicit view/change permission sets. 449 450 Returns: 451 The updated :class:`~easypaperless.models.documents.Document`. 452 """ 453 logger.debug("Updating document id=%d", id) 454 payload: dict[str, Any] = {} 455 456 if title is not None: 457 payload["title"] = title 458 if content is not None: 459 payload["content"] = content 460 if date is not None: 461 payload["created"] = date 462 if correspondent is not None: 463 payload["correspondent"] = await self._resolver.resolve("correspondents", correspondent) 464 if document_type is not None: 465 payload["document_type"] = await self._resolver.resolve("document_types", document_type) 466 if storage_path is not None: 467 payload["storage_path"] = await self._resolver.resolve("storage_paths", storage_path) 468 if tags is not None: 469 payload["tags"] = await self._resolver.resolve_list("tags", tags) 470 if asn is not None: 471 payload["archive_serial_number"] = asn 472 if custom_fields is not None: 473 payload["custom_fields"] = custom_fields 474 if owner is not None: 475 payload["owner"] = owner 476 if set_permissions is not None: 477 payload["set_permissions"] = set_permissions.model_dump() 478 479 resp = await self._session.patch(f"/documents/{id}/", json=payload) 480 return Document.model_validate(resp.json())
Partially update a document (PATCH semantics — only passed fields change).
Args:
id: Numeric ID of the document to update.
title: New document title.
content: OCR text content of the document.
date: Creation date as an ISO-8601 string ("YYYY-MM-DD").
correspondent: Correspondent to assign, as an ID or name.
Pass 0 to clear.
document_type: Document type to assign, as an ID or name.
Pass 0 to clear.
storage_path: Storage path to assign, as an ID or name.
Pass 0 to clear.
tags: Full replacement list of tags (IDs or names). The existing
tag list is replaced, not merged.
asn: Archive serial number to assign.
custom_fields: List of {"field": <field_id>, "value": ...}
dicts. Replaces the existing custom-field values.
owner: Numeric user ID to assign as document owner.
set_permissions: Explicit view/change permission sets.
Returns:
The updated ~easypaperless.models.documents.Document.
482 async def delete_document(self, id: int) -> None: 483 """Permanently delete a document. 484 485 Args: 486 id: Numeric ID of the document to delete. 487 488 Raises: 489 ~easypaperless.exceptions.NotFoundError: If no document exists 490 with that ID. 491 """ 492 logger.debug("Deleting document id=%d", id) 493 await self._session.delete(f"/documents/{id}/")
Permanently delete a document.
Args: id: Numeric ID of the document to delete.
Raises: ~easypaperless.exceptions.NotFoundError: If no document exists with that ID.
495 async def download_document(self, id: int, *, original: bool = False) -> bytes: 496 """Download the binary content of a document. 497 498 Args: 499 id: Numeric ID of the document to download. 500 original: If ``False`` *(default)*, returns the archived 501 (post-processed) PDF. If ``True``, returns the original 502 file that was uploaded. 503 504 Returns: 505 Raw file bytes. 506 """ 507 endpoint = "download" if original else "archive" 508 resp = await self._session.get_download(f"/documents/{id}/{endpoint}/") 509 content_type = resp.headers.get("content-type", "") 510 if "text/html" in content_type or resp.content[:9].lower().startswith(b"<!doctype"): 511 raise ServerError( 512 f"Download returned an HTML page (content-type: {content_type!r}). " 513 "The server redirected to a login page even after re-attaching auth. " 514 "Run with --verbose to see redirect details.", 515 status_code=None, 516 ) 517 return resp.content
Download the binary content of a document.
Args:
id: Numeric ID of the document to download.
original: If False (default), returns the archived
(post-processed) PDF. If True, returns the original
file that was uploaded.
Returns: Raw file bytes.
21 async def get_notes(self, document_id: int) -> list[DocumentNote]: 22 """Fetch all notes attached to a document. 23 24 Args: 25 document_id: Numeric ID of the document whose notes to retrieve. 26 27 Returns: 28 List of :class:`~easypaperless.models.documents.DocumentNote` objects, 29 ordered by creation time. 30 31 Raises: 32 ~easypaperless.exceptions.NotFoundError: If no document exists 33 with that ID. 34 """ 35 logger.debug("Fetching notes for document id=%d", document_id) 36 resp = await self._session.get(f"/documents/{document_id}/notes/") 37 return [DocumentNote.model_validate(item) for item in resp.json()]
Fetch all notes attached to a document.
Args: document_id: Numeric ID of the document whose notes to retrieve.
Returns:
List of ~easypaperless.models.documents.DocumentNote objects,
ordered by creation time.
Raises: ~easypaperless.exceptions.NotFoundError: If no document exists with that ID.
39 async def create_note(self, document_id: int, *, note: str) -> DocumentNote: 40 """Create a new note on a document. 41 42 Args: 43 document_id: Numeric ID of the document to annotate. 44 note: Text content of the note. 45 46 Returns: 47 The newly created :class:`~easypaperless.models.documents.DocumentNote`. 48 49 Raises: 50 ~easypaperless.exceptions.NotFoundError: If no document exists 51 with that ID. 52 """ 53 logger.debug("Creating note for document id=%d", document_id) 54 resp = await self._session.post( 55 f"/documents/{document_id}/notes/", 56 json={"note": note}, 57 ) 58 # paperless-ngx returns the full list of notes after creation; 59 # the newly created note is the last item in the list. 60 data = resp.json() 61 if isinstance(data, list): 62 return DocumentNote.model_validate(data[-1]) 63 return DocumentNote.model_validate(data)
Create a new note on a document.
Args: document_id: Numeric ID of the document to annotate. note: Text content of the note.
Returns:
The newly created ~easypaperless.models.documents.DocumentNote.
Raises: ~easypaperless.exceptions.NotFoundError: If no document exists with that ID.
65 async def delete_note(self, document_id: int, note_id: int) -> None: 66 """Delete a note from a document. 67 68 Args: 69 document_id: Numeric ID of the document that owns the note. 70 note_id: Numeric ID of the note to delete. 71 72 Raises: 73 ~easypaperless.exceptions.NotFoundError: If no document or note 74 exists with the given IDs. 75 """ 76 logger.debug("Deleting note id=%d from document id=%d", note_id, document_id) 77 await self._session.delete(f"/documents/{document_id}/notes/", params={"id": note_id})
Delete a note from a document.
Args: document_id: Numeric ID of the document that owns the note. note_id: Numeric ID of the note to delete.
Raises: ~easypaperless.exceptions.NotFoundError: If no document or note exists with the given IDs.
31 async def upload_document( 32 self, 33 file: str | Path, 34 *, 35 title: str | None = None, 36 created: str | None = None, 37 correspondent: int | str | None = None, 38 document_type: int | str | None = None, 39 storage_path: int | str | None = None, 40 tags: list[int | str] | None = None, 41 asn: int | None = None, 42 wait: bool = False, 43 poll_interval: float | None = None, 44 poll_timeout: float | None = None, 45 ) -> str | Document: 46 """Upload a document to paperless-ngx. 47 48 Args: 49 file: Path to the file to upload (``str`` or 50 :class:`~pathlib.Path`). 51 title: Title to assign to the document. Paperless-ngx derives 52 one from the file name if omitted. 53 created: Creation date as an ISO-8601 string (``"YYYY-MM-DD"``). 54 correspondent: Correspondent to assign, as an ID or name. 55 document_type: Document type to assign, as an ID or name. 56 storage_path: Storage path to assign, as an ID or name. 57 tags: Tags to assign, as IDs or names. 58 asn: Archive serial number to assign. 59 wait: If ``False`` *(default)*, returns immediately with the 60 task ID string. If ``True``, polls the task-status endpoint 61 until processing completes and returns the resulting 62 :class:`~easypaperless.models.documents.Document`. 63 poll_interval: Override the client-level ``poll_interval`` for 64 this upload. Only used when ``wait=True``. 65 poll_timeout: Override the client-level ``poll_timeout`` for 66 this upload. Only used when ``wait=True``. 67 68 Returns: 69 The Celery task ID string when ``wait=False``, or the fully 70 processed :class:`~easypaperless.models.documents.Document` 71 when ``wait=True``. 72 73 Raises: 74 ~easypaperless.exceptions.UploadError: If paperless-ngx reports 75 that the processing task failed. 76 ~easypaperless.exceptions.TaskTimeoutError: If ``wait=True`` and 77 processing does not complete within the timeout. 78 """ 79 file_path = Path(file) 80 file_bytes = file_path.read_bytes() 81 logger.info("Uploading %r (%d bytes)", file_path.name, len(file_bytes)) 82 83 data: dict[str, Any] = {} 84 if title is not None: 85 data["title"] = title 86 if created is not None: 87 data["created"] = created 88 if correspondent is not None: 89 data["correspondent"] = await self._resolver.resolve("correspondents", correspondent) 90 if document_type is not None: 91 data["document_type"] = await self._resolver.resolve("document_types", document_type) 92 if storage_path is not None: 93 data["storage_path"] = await self._resolver.resolve("storage_paths", storage_path) 94 if tags is not None: 95 resolved = await self._resolver.resolve_list("tags", tags) 96 data["tags"] = resolved 97 if asn is not None: 98 data["archive_serial_number"] = asn 99 100 files = {"document": (file_path.name, file_bytes)} 101 resp = await self._session.post("/documents/post_document/", data=data, files=files) 102 task_id: str = resp.text.strip('"') 103 logger.debug("Upload accepted, task_id=%r", task_id) 104 105 if not wait: 106 return task_id 107 108 interval = poll_interval if poll_interval is not None else self._poll_interval 109 timeout = poll_timeout if poll_timeout is not None else self._poll_timeout 110 return await self._poll_task(task_id, poll_interval=interval, poll_timeout=timeout)
Upload a document to paperless-ngx.
Args:
file: Path to the file to upload (str or
~pathlib.Path).
title: Title to assign to the document. Paperless-ngx derives
one from the file name if omitted.
created: Creation date as an ISO-8601 string ("YYYY-MM-DD").
correspondent: Correspondent to assign, as an ID or name.
document_type: Document type to assign, as an ID or name.
storage_path: Storage path to assign, as an ID or name.
tags: Tags to assign, as IDs or names.
asn: Archive serial number to assign.
wait: If False (default), returns immediately with the
task ID string. If True, polls the task-status endpoint
until processing completes and returns the resulting
~easypaperless.models.documents.Document.
poll_interval: Override the client-level poll_interval for
this upload. Only used when wait=True.
poll_timeout: Override the client-level poll_timeout for
this upload. Only used when wait=True.
Returns:
The Celery task ID string when wait=False, or the fully
processed ~easypaperless.models.documents.Document
when wait=True.
Raises:
~easypaperless.exceptions.UploadError: If paperless-ngx reports
that the processing task failed.
~easypaperless.exceptions.TaskTimeoutError: If wait=True and
processing does not complete within the timeout.
20 async def bulk_edit( 21 self, document_ids: list[int], method: str, **parameters: Any 22 ) -> None: 23 """Execute a bulk-edit operation on a list of documents. 24 25 This is a low-level method; prefer the higher-level helpers such as 26 :meth:`bulk_add_tag`, :meth:`bulk_remove_tag`, and 27 :meth:`bulk_modify_tags`. 28 29 Args: 30 document_ids: List of document IDs to operate on. 31 method: Bulk-edit method name as recognised by paperless-ngx 32 (e.g. ``"add_tag"``, ``"remove_tag"``, ``"delete"``). 33 **parameters: Additional keyword arguments forwarded to the API as 34 the ``parameters`` object. 35 """ 36 payload = {"documents": document_ids, "method": method, "parameters": parameters} 37 # Use a longer timeout for bulk operations — large batches can take 38 # considerably more than the default 30 s on the paperless-ngx side. 39 await self._session.post("/documents/bulk_edit/", json=payload, timeout=120.0)
Execute a bulk-edit operation on a list of documents.
This is a low-level method; prefer the higher-level helpers such as
bulk_add_tag(), bulk_remove_tag(), and
bulk_modify_tags().
Args:
document_ids: List of document IDs to operate on.
method: Bulk-edit method name as recognised by paperless-ngx
(e.g. "add_tag", "remove_tag", "delete").
**parameters: Additional keyword arguments forwarded to the API as
the parameters object.
41 async def bulk_add_tag(self, document_ids: list[int], tag: int | str) -> None: 42 """Add a tag to multiple documents in a single request. 43 44 Args: 45 document_ids: List of document IDs to tag. 46 tag: Tag to add, as an ID or name. 47 """ 48 tag_id = await self._resolver.resolve("tags", tag) 49 await self.bulk_edit(document_ids, "add_tag", tag=tag_id)
Add a tag to multiple documents in a single request.
Args: document_ids: List of document IDs to tag. tag: Tag to add, as an ID or name.
51 async def bulk_remove_tag(self, document_ids: list[int], tag: int | str) -> None: 52 """Remove a tag from multiple documents in a single request. 53 54 Args: 55 document_ids: List of document IDs to un-tag. 56 tag: Tag to remove, as an ID or name. 57 """ 58 tag_id = await self._resolver.resolve("tags", tag) 59 await self.bulk_edit(document_ids, "remove_tag", tag=tag_id)
Remove a tag from multiple documents in a single request.
Args: document_ids: List of document IDs to un-tag. tag: Tag to remove, as an ID or name.
79 async def bulk_delete(self, document_ids: list[int]) -> None: 80 """Permanently delete multiple documents in a single request. 81 82 Args: 83 document_ids: List of document IDs to delete. 84 """ 85 await self.bulk_edit(document_ids, "delete")
Permanently delete multiple documents in a single request.
Args: document_ids: List of document IDs to delete.
87 async def bulk_set_correspondent( 88 self, document_ids: list[int], correspondent: int | str | None 89 ) -> None: 90 """Assign a correspondent to multiple documents in a single request. 91 92 Args: 93 document_ids: List of document IDs to modify. 94 correspondent: Correspondent to assign, as an ID or name. 95 Pass ``None`` to clear the assignment. 96 """ 97 cor_id: int | None = None 98 if correspondent is not None: 99 cor_id = await self._resolver.resolve("correspondents", correspondent) 100 await self.bulk_edit(document_ids, "set_correspondent", correspondent=cor_id)
Assign a correspondent to multiple documents in a single request.
Args:
document_ids: List of document IDs to modify.
correspondent: Correspondent to assign, as an ID or name.
Pass None to clear the assignment.
102 async def bulk_set_document_type( 103 self, document_ids: list[int], document_type: int | str | None 104 ) -> None: 105 """Assign a document type to multiple documents in a single request. 106 107 Args: 108 document_ids: List of document IDs to modify. 109 document_type: Document type to assign, as an ID or name. 110 Pass ``None`` to clear the assignment. 111 """ 112 dt_id: int | None = None 113 if document_type is not None: 114 dt_id = await self._resolver.resolve("document_types", document_type) 115 await self.bulk_edit(document_ids, "set_document_type", document_type=dt_id)
Assign a document type to multiple documents in a single request.
Args:
document_ids: List of document IDs to modify.
document_type: Document type to assign, as an ID or name.
Pass None to clear the assignment.
117 async def bulk_set_storage_path( 118 self, document_ids: list[int], storage_path: int | str | None 119 ) -> None: 120 """Assign a storage path to multiple documents in a single request. 121 122 Args: 123 document_ids: List of document IDs to modify. 124 storage_path: Storage path to assign, as an ID or name. 125 Pass ``None`` to clear the assignment. 126 """ 127 sp_id: int | None = None 128 if storage_path is not None: 129 sp_id = await self._resolver.resolve("storage_paths", storage_path) 130 await self.bulk_edit(document_ids, "set_storage_path", storage_path=sp_id)
Assign a storage path to multiple documents in a single request.
Args:
document_ids: List of document IDs to modify.
storage_path: Storage path to assign, as an ID or name.
Pass None to clear the assignment.
132 async def bulk_modify_custom_fields( 133 self, 134 document_ids: list[int], 135 *, 136 add_fields: list[dict[str, Any]] | None = None, 137 remove_fields: list[int] | None = None, 138 ) -> None: 139 """Add and/or remove custom field values on multiple documents. 140 141 Args: 142 document_ids: List of document IDs to modify. 143 add_fields: Custom-field value dicts to add (each must contain 144 ``"field"`` and ``"value"`` keys). 145 remove_fields: Custom-field IDs whose values should be removed. 146 """ 147 await self.bulk_edit( 148 document_ids, 149 "modify_custom_fields", 150 add_custom_fields=add_fields or [], 151 remove_custom_fields=remove_fields or [], 152 )
Add and/or remove custom field values on multiple documents.
Args:
document_ids: List of document IDs to modify.
add_fields: Custom-field value dicts to add (each must contain
"field" and "value" keys).
remove_fields: Custom-field IDs whose values should be removed.
154 async def bulk_set_permissions( 155 self, 156 document_ids: list[int], 157 *, 158 set_permissions: SetPermissions | None = None, 159 owner: int | None = None, 160 merge: bool = False, 161 ) -> None: 162 """Set permissions and/or owner on multiple documents. 163 164 Args: 165 document_ids: List of document IDs to modify. 166 set_permissions: Explicit view/change permission sets to apply. 167 owner: Numeric user ID to assign as document owner. 168 merge: When ``True``, new permissions are merged with existing 169 ones rather than replacing them. 170 """ 171 params: dict[str, Any] = {"merge": merge} 172 if set_permissions is not None: 173 params["set_permissions"] = set_permissions.model_dump() 174 if owner is not None: 175 params["owner"] = owner 176 await self.bulk_edit(document_ids, "set_permissions", **params)
Set permissions and/or owner on multiple documents.
Args:
document_ids: List of document IDs to modify.
set_permissions: Explicit view/change permission sets to apply.
owner: Numeric user ID to assign as document owner.
merge: When True, new permissions are merged with existing
ones rather than replacing them.
18 async def bulk_edit_objects( 19 self, 20 object_type: str, 21 object_ids: list[int], 22 operation: str, 23 **parameters: Any, 24 ) -> None: 25 """Execute a bulk operation on non-document objects (tags, etc.). 26 27 This is a low-level method; prefer the higher-level helpers such as 28 :meth:`bulk_delete_tags` or :meth:`bulk_set_permissions_tags`. 29 30 Args: 31 object_type: The paperless-ngx object type string (e.g. 32 ``"tags"``, ``"correspondents"``). 33 object_ids: List of object IDs to operate on. 34 operation: Operation name recognised by the 35 ``/bulk_edit_objects/`` endpoint. 36 **parameters: Additional keyword arguments forwarded directly to 37 the API payload. 38 """ 39 payload = { 40 "objects": object_ids, 41 "object_type": object_type, 42 "operation": operation, 43 **parameters, 44 } 45 await self._session.post("/bulk_edit_objects/", json=payload)
Execute a bulk operation on non-document objects (tags, etc.).
This is a low-level method; prefer the higher-level helpers such as
bulk_delete_tags() or bulk_set_permissions_tags().
Args:
object_type: The paperless-ngx object type string (e.g.
"tags", "correspondents").
object_ids: List of object IDs to operate on.
operation: Operation name recognised by the
/bulk_edit_objects/ endpoint.
**parameters: Additional keyword arguments forwarded directly to
the API payload.
57 async def bulk_delete_correspondents(self, ids: list[int]) -> None: 58 """Permanently delete multiple correspondents in a single request. 59 60 Args: 61 ids: List of correspondent IDs to delete. 62 """ 63 await self.bulk_edit_objects("correspondents", ids, "delete")
Permanently delete multiple correspondents in a single request.
Args: ids: List of correspondent IDs to delete.
65 async def bulk_delete_document_types(self, ids: list[int]) -> None: 66 """Permanently delete multiple document types in a single request. 67 68 Args: 69 ids: List of document type IDs to delete. 70 """ 71 await self.bulk_edit_objects("document_types", ids, "delete")
Permanently delete multiple document types in a single request.
Args: ids: List of document type IDs to delete.
73 async def bulk_delete_storage_paths(self, ids: list[int]) -> None: 74 """Permanently delete multiple storage paths in a single request. 75 76 Args: 77 ids: List of storage path IDs to delete. 78 """ 79 await self.bulk_edit_objects("storage_paths", ids, "delete")
Permanently delete multiple storage paths in a single request.
Args: ids: List of storage path IDs to delete.
107 async def bulk_set_permissions_correspondents( 108 self, 109 ids: list[int], 110 *, 111 set_permissions: SetPermissions | None = None, 112 owner: int | None = None, 113 merge: bool = False, 114 ) -> None: 115 """Set permissions and/or owner on multiple correspondents. 116 117 Args: 118 ids: List of correspondent IDs to modify. 119 set_permissions: Explicit view/change permission sets to apply. 120 owner: Numeric user ID to assign as owner. 121 merge: When ``True``, new permissions are merged with existing 122 ones rather than replacing them. 123 """ 124 params: dict[str, Any] = {"merge": merge} 125 if set_permissions is not None: 126 params["permissions"] = set_permissions.model_dump() 127 if owner is not None: 128 params["owner"] = owner 129 await self.bulk_edit_objects("correspondents", ids, "set_permissions", **params)
Set permissions and/or owner on multiple correspondents.
Args:
ids: List of correspondent IDs to modify.
set_permissions: Explicit view/change permission sets to apply.
owner: Numeric user ID to assign as owner.
merge: When True, new permissions are merged with existing
ones rather than replacing them.
131 async def bulk_set_permissions_document_types( 132 self, 133 ids: list[int], 134 *, 135 set_permissions: SetPermissions | None = None, 136 owner: int | None = None, 137 merge: bool = False, 138 ) -> None: 139 """Set permissions and/or owner on multiple document types. 140 141 Args: 142 ids: List of document type IDs to modify. 143 set_permissions: Explicit view/change permission sets to apply. 144 owner: Numeric user ID to assign as owner. 145 merge: When ``True``, new permissions are merged with existing 146 ones rather than replacing them. 147 """ 148 params: dict[str, Any] = {"merge": merge} 149 if set_permissions is not None: 150 params["permissions"] = set_permissions.model_dump() 151 if owner is not None: 152 params["owner"] = owner 153 await self.bulk_edit_objects("document_types", ids, "set_permissions", **params)
Set permissions and/or owner on multiple document types.
Args:
ids: List of document type IDs to modify.
set_permissions: Explicit view/change permission sets to apply.
owner: Numeric user ID to assign as owner.
merge: When True, new permissions are merged with existing
ones rather than replacing them.
155 async def bulk_set_permissions_storage_paths( 156 self, 157 ids: list[int], 158 *, 159 set_permissions: SetPermissions | None = None, 160 owner: int | None = None, 161 merge: bool = False, 162 ) -> None: 163 """Set permissions and/or owner on multiple storage paths. 164 165 Args: 166 ids: List of storage path IDs to modify. 167 set_permissions: Explicit view/change permission sets to apply. 168 owner: Numeric user ID to assign as owner. 169 merge: When ``True``, new permissions are merged with existing 170 ones rather than replacing them. 171 """ 172 params: dict[str, Any] = {"merge": merge} 173 if set_permissions is not None: 174 params["permissions"] = set_permissions.model_dump() 175 if owner is not None: 176 params["owner"] = owner 177 await self.bulk_edit_objects("storage_paths", ids, "set_permissions", **params)
Set permissions and/or owner on multiple storage paths.
Args:
ids: List of storage path IDs to modify.
set_permissions: Explicit view/change permission sets to apply.
owner: Numeric user ID to assign as owner.
merge: When True, new permissions are merged with existing
ones rather than replacing them.
91 async def get_tag(self, id: int) -> Tag: 92 """Fetch a single tag by its ID. 93 94 Args: 95 id: Numeric tag ID. 96 97 Returns: 98 The :class:`~easypaperless.models.tags.Tag` with the given ID. 99 100 Raises: 101 ~easypaperless.exceptions.NotFoundError: If no tag exists with 102 that ID. 103 """ 104 return cast(Tag, await self._get_resource("tags", id, Tag))
Fetch a single tag by its ID.
Args: id: Numeric tag ID.
Returns:
The ~easypaperless.models.tags.Tag with the given ID.
Raises: ~easypaperless.exceptions.NotFoundError: If no tag exists with that ID.
106 async def create_tag( 107 self, 108 *, 109 name: str, 110 color: str | None = None, 111 is_inbox_tag: bool | None = None, 112 match: str | None = None, 113 matching_algorithm: MatchingAlgorithm | None = None, 114 is_insensitive: bool | None = None, 115 parent: int | None = None, 116 owner: int | None = None, 117 set_permissions: SetPermissions | None = None, 118 ) -> Tag: 119 """Create a new tag. 120 121 Args: 122 name: Tag name. Must be unique. 123 color: Background colour in the paperless-ngx UI as a CSS hex 124 string (e.g. ``"#ff0000"``). 125 is_inbox_tag: When ``True``, newly ingested documents receive this 126 tag automatically until processed. At most one tag should be 127 the inbox tag. 128 match: Auto-matching pattern tested against incoming document 129 content. Interpretation depends on ``matching_algorithm``. 130 matching_algorithm: Controls how ``match`` is applied. 131 See :class:`~easypaperless.models.MatchingAlgorithm`. 132 is_insensitive: When ``True``, ``match`` is evaluated 133 case-insensitively. 134 parent: ID of an existing tag to use as parent, enabling 135 hierarchical tag trees. ``None`` creates a root-level tag. 136 owner: Numeric user ID to assign as owner. ``None`` creates the 137 tag with no owner (visible to all users). 138 set_permissions: Explicit view/change permission sets. Defaults to 139 no permissions (all users have access via public visibility). 140 141 Returns: 142 The newly created :class:`~easypaperless.models.tags.Tag`. 143 """ 144 return cast(Tag, await self._create_resource( 145 "tags", 146 Tag, 147 owner=owner, 148 set_permissions=set_permissions, 149 name=name, 150 color=color, 151 is_inbox_tag=is_inbox_tag, 152 match=match, 153 matching_algorithm=matching_algorithm, 154 is_insensitive=is_insensitive, 155 parent=parent, 156 ))
Create a new tag.
Args:
name: Tag name. Must be unique.
color: Background colour in the paperless-ngx UI as a CSS hex
string (e.g. "#ff0000").
is_inbox_tag: When True, newly ingested documents receive this
tag automatically until processed. At most one tag should be
the inbox tag.
match: Auto-matching pattern tested against incoming document
content. Interpretation depends on matching_algorithm.
matching_algorithm: Controls how match is applied.
See ~easypaperless.models.MatchingAlgorithm.
is_insensitive: When True, match is evaluated
case-insensitively.
parent: ID of an existing tag to use as parent, enabling
hierarchical tag trees. None creates a root-level tag.
owner: Numeric user ID to assign as owner. None creates the
tag with no owner (visible to all users).
set_permissions: Explicit view/change permission sets. Defaults to
no permissions (all users have access via public visibility).
Returns:
The newly created ~easypaperless.models.tags.Tag.
158 async def update_tag( 159 self, 160 id: int, 161 *, 162 name: str | None = None, 163 color: str | None = None, 164 is_inbox_tag: bool | None = None, 165 match: str | None = None, 166 matching_algorithm: MatchingAlgorithm | None = None, 167 is_insensitive: bool | None = None, 168 parent: int | None = None, 169 ) -> Tag: 170 """Partially update a tag (PATCH semantics). 171 172 Args: 173 id: Numeric ID of the tag to update. 174 name: Tag name. Must be unique. 175 color: Background colour in the paperless-ngx UI as a CSS hex 176 string (e.g. ``"#00ff00"``). 177 is_inbox_tag: When ``True``, newly ingested documents receive this 178 tag automatically until processed. At most one tag should be 179 the inbox tag. 180 match: Auto-matching pattern tested against incoming document 181 content. Interpretation depends on ``matching_algorithm``. 182 matching_algorithm: Controls how ``match`` is applied. 183 See :class:`~easypaperless.models.MatchingAlgorithm`. 184 is_insensitive: When ``True``, ``match`` is evaluated 185 case-insensitively. 186 parent: ID of an existing tag to use as parent, enabling 187 hierarchical tag trees. 188 189 Returns: 190 The updated :class:`~easypaperless.models.tags.Tag`. 191 """ 192 return cast(Tag, await self._update_resource( 193 "tags", 194 id, 195 Tag, 196 name=name, 197 color=color, 198 is_inbox_tag=is_inbox_tag, 199 match=match, 200 matching_algorithm=matching_algorithm, 201 is_insensitive=is_insensitive, 202 parent=parent, 203 ))
Partially update a tag (PATCH semantics).
Args:
id: Numeric ID of the tag to update.
name: Tag name. Must be unique.
color: Background colour in the paperless-ngx UI as a CSS hex
string (e.g. "#00ff00").
is_inbox_tag: When True, newly ingested documents receive this
tag automatically until processed. At most one tag should be
the inbox tag.
match: Auto-matching pattern tested against incoming document
content. Interpretation depends on matching_algorithm.
matching_algorithm: Controls how match is applied.
See ~easypaperless.models.MatchingAlgorithm.
is_insensitive: When True, match is evaluated
case-insensitively.
parent: ID of an existing tag to use as parent, enabling
hierarchical tag trees.
Returns:
The updated ~easypaperless.models.tags.Tag.
205 async def delete_tag(self, id: int) -> None: 206 """Delete a tag. 207 208 Args: 209 id: Numeric ID of the tag to delete. 210 211 Raises: 212 ~easypaperless.exceptions.NotFoundError: If no tag exists with 213 that ID. 214 """ 215 await self._delete_resource("tags", id)
Delete a tag.
Args: id: Numeric ID of the tag to delete.
Raises: ~easypaperless.exceptions.NotFoundError: If no tag exists with that ID.
46 async def list_correspondents( 47 self, 48 *, 49 ids: list[int] | None = None, 50 name_contains: str | None = None, 51 name_exact: str | None = None, 52 page: int | None = None, 53 page_size: int | None = None, 54 ordering: str | None = None, 55 descending: bool = False, 56 ) -> list[Correspondent]: 57 """Return correspondents defined in paperless-ngx. 58 59 Args: 60 ids: Return only correspondents whose ID is in this list. 61 name_contains: Case-insensitive substring filter on correspondent 62 name (raw API ``name__icontains``). 63 name_exact: Case-insensitive exact match on correspondent name 64 (raw API ``name__iexact``). 65 page: Return only this specific page (1-based). Disables 66 auto-pagination. Default: ``None`` (return all). 67 page_size: Number of results per page. When omitted, the server 68 default is used. 69 ordering: Field to sort by. Examples: ``"name"``, ``"id"``. 70 descending: When ``True``, reverses the sort direction of 71 ``ordering``. Ignored when ``ordering`` is ``None``. 72 73 Returns: 74 List of 75 :class:`~easypaperless.models.correspondents.Correspondent` 76 objects. 77 """ 78 params: dict[str, Any] = {} 79 if ids is not None: 80 params["id__in"] = ",".join(str(i) for i in ids) 81 if name_contains is not None: 82 params["name__icontains"] = name_contains 83 if name_exact is not None: 84 params["name__iexact"] = name_exact 85 if page is not None: 86 params["page"] = page 87 if page_size is not None: 88 params["page_size"] = page_size 89 if ordering is not None: 90 params["ordering"] = f"-{ordering}" if descending else ordering 91 return cast( 92 list[Correspondent], 93 await self._list_resource("correspondents", Correspondent, params or None), 94 )
Return correspondents defined in paperless-ngx.
Args:
ids: Return only correspondents whose ID is in this list.
name_contains: Case-insensitive substring filter on correspondent
name (raw API name__icontains).
name_exact: Case-insensitive exact match on correspondent name
(raw API name__iexact).
page: Return only this specific page (1-based). Disables
auto-pagination. Default: None (return all).
page_size: Number of results per page. When omitted, the server
default is used.
ordering: Field to sort by. Examples: "name", "id".
descending: When True, reverses the sort direction of
ordering. Ignored when ordering is None.
Returns:
List of
~easypaperless.models.correspondents.Correspondent
objects.
96 async def get_correspondent(self, id: int) -> Correspondent: 97 """Fetch a single correspondent by its ID. 98 99 Args: 100 id: Numeric correspondent ID. 101 102 Returns: 103 The :class:`~easypaperless.models.correspondents.Correspondent` 104 with the given ID. 105 106 Raises: 107 ~easypaperless.exceptions.NotFoundError: If no correspondent 108 exists with that ID. 109 """ 110 return cast(Correspondent, await self._get_resource("correspondents", id, Correspondent))
Fetch a single correspondent by its ID.
Args: id: Numeric correspondent ID.
Returns:
The ~easypaperless.models.correspondents.Correspondent
with the given ID.
Raises: ~easypaperless.exceptions.NotFoundError: If no correspondent exists with that ID.
112 async def create_correspondent( 113 self, 114 *, 115 name: str, 116 match: str | None = None, 117 matching_algorithm: MatchingAlgorithm | None = None, 118 is_insensitive: bool | None = None, 119 owner: int | None = None, 120 set_permissions: SetPermissions | None = None, 121 ) -> Correspondent: 122 """Create a new correspondent. 123 124 Args: 125 name: Correspondent name (sender/recipient). Must be unique. 126 match: Auto-matching pattern tested against incoming document 127 content. Interpretation depends on ``matching_algorithm``. 128 matching_algorithm: Controls how ``match`` is applied. 129 See :class:`~easypaperless.models.MatchingAlgorithm`. 130 is_insensitive: When ``True``, ``match`` is evaluated 131 case-insensitively. 132 owner: Numeric user ID to assign as owner. ``None`` creates the 133 correspondent with no owner (visible to all users). 134 set_permissions: Explicit view/change permission sets. Defaults to 135 no permissions (all users have access via public visibility). 136 137 Returns: 138 The newly created 139 :class:`~easypaperless.models.correspondents.Correspondent`. 140 """ 141 return cast(Correspondent, await self._create_resource( 142 "correspondents", 143 Correspondent, 144 owner=owner, 145 set_permissions=set_permissions, 146 name=name, 147 match=match, 148 matching_algorithm=matching_algorithm, 149 is_insensitive=is_insensitive, 150 ))
Create a new correspondent.
Args:
name: Correspondent name (sender/recipient). Must be unique.
match: Auto-matching pattern tested against incoming document
content. Interpretation depends on matching_algorithm.
matching_algorithm: Controls how match is applied.
See ~easypaperless.models.MatchingAlgorithm.
is_insensitive: When True, match is evaluated
case-insensitively.
owner: Numeric user ID to assign as owner. None creates the
correspondent with no owner (visible to all users).
set_permissions: Explicit view/change permission sets. Defaults to
no permissions (all users have access via public visibility).
Returns:
The newly created
~easypaperless.models.correspondents.Correspondent.
152 async def update_correspondent( 153 self, 154 id: int, 155 *, 156 name: str | None = None, 157 match: str | None = None, 158 matching_algorithm: MatchingAlgorithm | None = None, 159 is_insensitive: bool | None = None, 160 ) -> Correspondent: 161 """Partially update a correspondent (PATCH semantics). 162 163 Args: 164 id: Numeric ID of the correspondent to update. 165 name: Correspondent name (sender/recipient). Must be unique. 166 match: Auto-matching pattern tested against incoming document 167 content. Interpretation depends on ``matching_algorithm``. 168 matching_algorithm: Controls how ``match`` is applied. 169 See :class:`~easypaperless.models.MatchingAlgorithm`. 170 is_insensitive: When ``True``, ``match`` is evaluated 171 case-insensitively. 172 173 Returns: 174 The updated 175 :class:`~easypaperless.models.correspondents.Correspondent`. 176 """ 177 return cast(Correspondent, await self._update_resource( 178 "correspondents", 179 id, 180 Correspondent, 181 name=name, 182 match=match, 183 matching_algorithm=matching_algorithm, 184 is_insensitive=is_insensitive, 185 ))
Partially update a correspondent (PATCH semantics).
Args:
id: Numeric ID of the correspondent to update.
name: Correspondent name (sender/recipient). Must be unique.
match: Auto-matching pattern tested against incoming document
content. Interpretation depends on matching_algorithm.
matching_algorithm: Controls how match is applied.
See ~easypaperless.models.MatchingAlgorithm.
is_insensitive: When True, match is evaluated
case-insensitively.
Returns:
The updated
~easypaperless.models.correspondents.Correspondent.
187 async def delete_correspondent(self, id: int) -> None: 188 """Delete a correspondent. 189 190 Args: 191 id: Numeric ID of the correspondent to delete. 192 193 Raises: 194 ~easypaperless.exceptions.NotFoundError: If no correspondent 195 exists with that ID. 196 """ 197 await self._delete_resource("correspondents", id)
Delete a correspondent.
Args: id: Numeric ID of the correspondent to delete.
Raises: ~easypaperless.exceptions.NotFoundError: If no correspondent exists with that ID.
46 async def list_document_types( 47 self, 48 *, 49 ids: list[int] | None = None, 50 name_contains: str | None = None, 51 name_exact: str | None = None, 52 page: int | None = None, 53 page_size: int | None = None, 54 ordering: str | None = None, 55 descending: bool = False, 56 ) -> list[DocumentType]: 57 """Return document types defined in paperless-ngx. 58 59 Args: 60 ids: Return only document types whose ID is in this list. 61 name_contains: Case-insensitive substring filter on document-type 62 name (raw API ``name__icontains``). 63 name_exact: Case-insensitive exact match on document-type name 64 (raw API ``name__iexact``). 65 page: Return only this specific page (1-based). Disables 66 auto-pagination. Default: ``None`` (return all). 67 page_size: Number of results per page. When omitted, the server 68 default is used. 69 ordering: Field to sort by. Examples: ``"name"``, ``"id"``. 70 descending: When ``True``, reverses the sort direction of 71 ``ordering``. Ignored when ``ordering`` is ``None``. 72 73 Returns: 74 List of 75 :class:`~easypaperless.models.document_types.DocumentType` 76 objects. 77 """ 78 params: dict[str, Any] = {} 79 if ids is not None: 80 params["id__in"] = ",".join(str(i) for i in ids) 81 if name_contains is not None: 82 params["name__icontains"] = name_contains 83 if name_exact is not None: 84 params["name__iexact"] = name_exact 85 if page is not None: 86 params["page"] = page 87 if page_size is not None: 88 params["page_size"] = page_size 89 if ordering is not None: 90 params["ordering"] = f"-{ordering}" if descending else ordering 91 return cast( 92 list[DocumentType], 93 await self._list_resource("document_types", DocumentType, params or None), 94 )
Return document types defined in paperless-ngx.
Args:
ids: Return only document types whose ID is in this list.
name_contains: Case-insensitive substring filter on document-type
name (raw API name__icontains).
name_exact: Case-insensitive exact match on document-type name
(raw API name__iexact).
page: Return only this specific page (1-based). Disables
auto-pagination. Default: None (return all).
page_size: Number of results per page. When omitted, the server
default is used.
ordering: Field to sort by. Examples: "name", "id".
descending: When True, reverses the sort direction of
ordering. Ignored when ordering is None.
Returns:
List of
~easypaperless.models.document_types.DocumentType
objects.
96 async def get_document_type(self, id: int) -> DocumentType: 97 """Fetch a single document type by its ID. 98 99 Args: 100 id: Numeric document-type ID. 101 102 Returns: 103 The :class:`~easypaperless.models.document_types.DocumentType` 104 with the given ID. 105 106 Raises: 107 ~easypaperless.exceptions.NotFoundError: If no document type 108 exists with that ID. 109 """ 110 return cast(DocumentType, await self._get_resource("document_types", id, DocumentType))
Fetch a single document type by its ID.
Args: id: Numeric document-type ID.
Returns:
The ~easypaperless.models.document_types.DocumentType
with the given ID.
Raises: ~easypaperless.exceptions.NotFoundError: If no document type exists with that ID.
112 async def create_document_type( 113 self, 114 *, 115 name: str, 116 match: str | None = None, 117 matching_algorithm: MatchingAlgorithm | None = None, 118 is_insensitive: bool | None = None, 119 owner: int | None = None, 120 set_permissions: SetPermissions | None = None, 121 ) -> DocumentType: 122 """Create a new document type. 123 124 Args: 125 name: Document-type name (e.g. ``"Invoice"``, ``"Receipt"``). 126 Must be unique. 127 match: Auto-matching pattern tested against incoming document 128 content. Interpretation depends on ``matching_algorithm``. 129 matching_algorithm: Controls how ``match`` is applied. 130 See :class:`~easypaperless.models.MatchingAlgorithm`. 131 is_insensitive: When ``True``, ``match`` is evaluated 132 case-insensitively. 133 owner: Numeric user ID to assign as owner. ``None`` creates the 134 document type with no owner (visible to all users). 135 set_permissions: Explicit view/change permission sets. Defaults to 136 no permissions (all users have access via public visibility). 137 138 Returns: 139 The newly created 140 :class:`~easypaperless.models.document_types.DocumentType`. 141 """ 142 return cast(DocumentType, await self._create_resource( 143 "document_types", 144 DocumentType, 145 owner=owner, 146 set_permissions=set_permissions, 147 name=name, 148 match=match, 149 matching_algorithm=matching_algorithm, 150 is_insensitive=is_insensitive, 151 ))
Create a new document type.
Args:
name: Document-type name (e.g. "Invoice", "Receipt").
Must be unique.
match: Auto-matching pattern tested against incoming document
content. Interpretation depends on matching_algorithm.
matching_algorithm: Controls how match is applied.
See ~easypaperless.models.MatchingAlgorithm.
is_insensitive: When True, match is evaluated
case-insensitively.
owner: Numeric user ID to assign as owner. None creates the
document type with no owner (visible to all users).
set_permissions: Explicit view/change permission sets. Defaults to
no permissions (all users have access via public visibility).
Returns:
The newly created
~easypaperless.models.document_types.DocumentType.
153 async def update_document_type( 154 self, 155 id: int, 156 *, 157 name: str | None = None, 158 match: str | None = None, 159 matching_algorithm: MatchingAlgorithm | None = None, 160 is_insensitive: bool | None = None, 161 ) -> DocumentType: 162 """Partially update a document type (PATCH semantics). 163 164 Args: 165 id: Numeric ID of the document type to update. 166 name: Document-type name (e.g. ``"Invoice"``, ``"Receipt"``). 167 Must be unique. 168 match: Auto-matching pattern tested against incoming document 169 content. Interpretation depends on ``matching_algorithm``. 170 matching_algorithm: Controls how ``match`` is applied. 171 See :class:`~easypaperless.models.MatchingAlgorithm`. 172 is_insensitive: When ``True``, ``match`` is evaluated 173 case-insensitively. 174 175 Returns: 176 The updated 177 :class:`~easypaperless.models.document_types.DocumentType`. 178 """ 179 return cast(DocumentType, await self._update_resource( 180 "document_types", 181 id, 182 DocumentType, 183 name=name, 184 match=match, 185 matching_algorithm=matching_algorithm, 186 is_insensitive=is_insensitive, 187 ))
Partially update a document type (PATCH semantics).
Args:
id: Numeric ID of the document type to update.
name: Document-type name (e.g. "Invoice", "Receipt").
Must be unique.
match: Auto-matching pattern tested against incoming document
content. Interpretation depends on matching_algorithm.
matching_algorithm: Controls how match is applied.
See ~easypaperless.models.MatchingAlgorithm.
is_insensitive: When True, match is evaluated
case-insensitively.
Returns:
The updated
~easypaperless.models.document_types.DocumentType.
189 async def delete_document_type(self, id: int) -> None: 190 """Delete a document type. 191 192 Args: 193 id: Numeric ID of the document type to delete. 194 195 Raises: 196 ~easypaperless.exceptions.NotFoundError: If no document type 197 exists with that ID. 198 """ 199 await self._delete_resource("document_types", id)
Delete a document type.
Args: id: Numeric ID of the document type to delete.
Raises: ~easypaperless.exceptions.NotFoundError: If no document type exists with that ID.
46 async def list_storage_paths( 47 self, 48 *, 49 ids: list[int] | None = None, 50 name_contains: str | None = None, 51 name_exact: str | None = None, 52 page: int | None = None, 53 page_size: int | None = None, 54 ordering: str | None = None, 55 descending: bool = False, 56 ) -> list[StoragePath]: 57 """Return storage paths defined in paperless-ngx. 58 59 Args: 60 ids: Return only storage paths whose ID is in this list. 61 name_contains: Case-insensitive substring filter on storage-path 62 name (raw API ``name__icontains``). 63 name_exact: Case-insensitive exact match on storage-path name 64 (raw API ``name__iexact``). 65 page: Return only this specific page (1-based). Disables 66 auto-pagination. Default: ``None`` (return all). 67 page_size: Number of results per page. When omitted, the server 68 default is used. 69 ordering: Field to sort by. Examples: ``"name"``, ``"id"``. 70 descending: When ``True``, reverses the sort direction of 71 ``ordering``. Ignored when ``ordering`` is ``None``. 72 73 Returns: 74 List of 75 :class:`~easypaperless.models.storage_paths.StoragePath` 76 objects. 77 """ 78 params: dict[str, Any] = {} 79 if ids is not None: 80 params["id__in"] = ",".join(str(i) for i in ids) 81 if name_contains is not None: 82 params["name__icontains"] = name_contains 83 if name_exact is not None: 84 params["name__iexact"] = name_exact 85 if page is not None: 86 params["page"] = page 87 if page_size is not None: 88 params["page_size"] = page_size 89 if ordering is not None: 90 params["ordering"] = f"-{ordering}" if descending else ordering 91 return cast( 92 list[StoragePath], 93 await self._list_resource("storage_paths", StoragePath, params or None), 94 )
Return storage paths defined in paperless-ngx.
Args:
ids: Return only storage paths whose ID is in this list.
name_contains: Case-insensitive substring filter on storage-path
name (raw API name__icontains).
name_exact: Case-insensitive exact match on storage-path name
(raw API name__iexact).
page: Return only this specific page (1-based). Disables
auto-pagination. Default: None (return all).
page_size: Number of results per page. When omitted, the server
default is used.
ordering: Field to sort by. Examples: "name", "id".
descending: When True, reverses the sort direction of
ordering. Ignored when ordering is None.
Returns:
List of
~easypaperless.models.storage_paths.StoragePath
objects.
96 async def get_storage_path(self, id: int) -> StoragePath: 97 """Fetch a single storage path by its ID. 98 99 Args: 100 id: Numeric storage-path ID. 101 102 Returns: 103 The :class:`~easypaperless.models.storage_paths.StoragePath` 104 with the given ID. 105 106 Raises: 107 ~easypaperless.exceptions.NotFoundError: If no storage path 108 exists with that ID. 109 """ 110 return cast(StoragePath, await self._get_resource("storage_paths", id, StoragePath))
Fetch a single storage path by its ID.
Args: id: Numeric storage-path ID.
Returns:
The ~easypaperless.models.storage_paths.StoragePath
with the given ID.
Raises: ~easypaperless.exceptions.NotFoundError: If no storage path exists with that ID.
112 async def create_storage_path( 113 self, 114 *, 115 name: str, 116 path: str | None = None, 117 match: str | None = None, 118 matching_algorithm: MatchingAlgorithm | None = None, 119 is_insensitive: bool | None = None, 120 owner: int | None = None, 121 set_permissions: SetPermissions | None = None, 122 ) -> StoragePath: 123 """Create a new storage path. 124 125 Args: 126 name: Storage-path name. Must be unique. 127 path: Template string for the archive file path. Supported 128 placeholders: ``{created_year}``, ``{created_month}``, 129 ``{created_day}``, ``{correspondent}``, ``{document_type}``, 130 ``{title}``, ``{asn}``. Example: 131 ``"{created_year}/{correspondent}/{title}"``. When omitted, 132 the server default location is used. 133 match: Auto-matching pattern tested against incoming document 134 content. Interpretation depends on ``matching_algorithm``. 135 matching_algorithm: Controls how ``match`` is applied. 136 See :class:`~easypaperless.models.MatchingAlgorithm`. 137 is_insensitive: When ``True``, ``match`` is evaluated 138 case-insensitively. 139 owner: Numeric user ID to assign as owner. ``None`` creates the 140 storage path with no owner (visible to all users). 141 set_permissions: Explicit view/change permission sets. Defaults to 142 no permissions (all users have access via public visibility). 143 144 Returns: 145 The newly created 146 :class:`~easypaperless.models.storage_paths.StoragePath`. 147 """ 148 return cast(StoragePath, await self._create_resource( 149 "storage_paths", 150 StoragePath, 151 owner=owner, 152 set_permissions=set_permissions, 153 name=name, 154 path=path, 155 match=match, 156 matching_algorithm=matching_algorithm, 157 is_insensitive=is_insensitive, 158 ))
Create a new storage path.
Args:
name: Storage-path name. Must be unique.
path: Template string for the archive file path. Supported
placeholders: {created_year}, {created_month},
{created_day}, {correspondent}, {document_type},
{title}, {asn}. Example:
"{created_year}/{correspondent}/{title}". When omitted,
the server default location is used.
match: Auto-matching pattern tested against incoming document
content. Interpretation depends on matching_algorithm.
matching_algorithm: Controls how match is applied.
See ~easypaperless.models.MatchingAlgorithm.
is_insensitive: When True, match is evaluated
case-insensitively.
owner: Numeric user ID to assign as owner. None creates the
storage path with no owner (visible to all users).
set_permissions: Explicit view/change permission sets. Defaults to
no permissions (all users have access via public visibility).
Returns:
The newly created
~easypaperless.models.storage_paths.StoragePath.
160 async def update_storage_path( 161 self, 162 id: int, 163 *, 164 name: str | None = None, 165 path: str | None = None, 166 match: str | None = None, 167 matching_algorithm: MatchingAlgorithm | None = None, 168 is_insensitive: bool | None = None, 169 ) -> StoragePath: 170 """Partially update a storage path (PATCH semantics). 171 172 Args: 173 id: Numeric ID of the storage path to update. 174 name: Storage-path name. Must be unique. 175 path: Template string for the archive file path. Supported 176 placeholders: ``{created_year}``, ``{created_month}``, 177 ``{created_day}``, ``{correspondent}``, ``{document_type}``, 178 ``{title}``, ``{asn}``. Example: ``"{title}"``. 179 match: Auto-matching pattern tested against incoming document 180 content. Interpretation depends on ``matching_algorithm``. 181 matching_algorithm: Controls how ``match`` is applied. 182 See :class:`~easypaperless.models.MatchingAlgorithm`. 183 is_insensitive: When ``True``, ``match`` is evaluated 184 case-insensitively. 185 186 Returns: 187 The updated 188 :class:`~easypaperless.models.storage_paths.StoragePath`. 189 """ 190 return cast(StoragePath, await self._update_resource( 191 "storage_paths", 192 id, 193 StoragePath, 194 name=name, 195 path=path, 196 match=match, 197 matching_algorithm=matching_algorithm, 198 is_insensitive=is_insensitive, 199 ))
Partially update a storage path (PATCH semantics).
Args:
id: Numeric ID of the storage path to update.
name: Storage-path name. Must be unique.
path: Template string for the archive file path. Supported
placeholders: {created_year}, {created_month},
{created_day}, {correspondent}, {document_type},
{title}, {asn}. Example: "{title}".
match: Auto-matching pattern tested against incoming document
content. Interpretation depends on matching_algorithm.
matching_algorithm: Controls how match is applied.
See ~easypaperless.models.MatchingAlgorithm.
is_insensitive: When True, match is evaluated
case-insensitively.
Returns:
The updated
~easypaperless.models.storage_paths.StoragePath.
201 async def delete_storage_path(self, id: int) -> None: 202 """Delete a storage path. 203 204 Args: 205 id: Numeric ID of the storage path to delete. 206 207 Raises: 208 ~easypaperless.exceptions.NotFoundError: If no storage path 209 exists with that ID. 210 """ 211 await self._delete_resource("storage_paths", id)
Delete a storage path.
Args: id: Numeric ID of the storage path to delete.
Raises: ~easypaperless.exceptions.NotFoundError: If no storage path exists with that ID.
45 async def list_custom_fields( 46 self, 47 *, 48 page: int | None = None, 49 page_size: int | None = None, 50 ordering: str | None = None, 51 descending: bool = False, 52 ) -> list[CustomField]: 53 """Return all custom fields defined in paperless-ngx. 54 55 Args: 56 page: Return only this specific page (1-based). Disables 57 auto-pagination. Default: ``None`` (return all). 58 page_size: Number of results per page. When omitted, the server 59 default is used. 60 ordering: Field to sort by. Examples: ``"name"``, ``"id"``. 61 descending: When ``True``, reverses the sort direction of 62 ``ordering``. Ignored when ``ordering`` is ``None``. 63 64 Returns: 65 List of 66 :class:`~easypaperless.models.custom_fields.CustomField` 67 objects. 68 """ 69 params: dict[str, Any] = {} 70 if page is not None: 71 params["page"] = page 72 if page_size is not None: 73 params["page_size"] = page_size 74 if ordering is not None: 75 params["ordering"] = f"-{ordering}" if descending else ordering 76 return cast( 77 list[CustomField], 78 await self._list_resource("custom_fields", CustomField, params or None), 79 )
Return all custom fields defined in paperless-ngx.
Args:
page: Return only this specific page (1-based). Disables
auto-pagination. Default: None (return all).
page_size: Number of results per page. When omitted, the server
default is used.
ordering: Field to sort by. Examples: "name", "id".
descending: When True, reverses the sort direction of
ordering. Ignored when ordering is None.
Returns:
List of
~easypaperless.models.custom_fields.CustomField
objects.
81 async def get_custom_field(self, id: int) -> CustomField: 82 """Fetch a single custom field by its ID. 83 84 Args: 85 id: Numeric custom-field ID. 86 87 Returns: 88 The :class:`~easypaperless.models.custom_fields.CustomField` 89 with the given ID. 90 91 Raises: 92 ~easypaperless.exceptions.NotFoundError: If no custom field 93 exists with that ID. 94 """ 95 return cast(CustomField, await self._get_resource("custom_fields", id, CustomField))
Fetch a single custom field by its ID.
Args: id: Numeric custom-field ID.
Returns:
The ~easypaperless.models.custom_fields.CustomField
with the given ID.
Raises: ~easypaperless.exceptions.NotFoundError: If no custom field exists with that ID.
97 async def create_custom_field( 98 self, 99 *, 100 name: str, 101 data_type: str, 102 extra_data: Any | None = None, 103 owner: int | None = None, 104 set_permissions: SetPermissions | None = None, 105 ) -> CustomField: 106 """Create a new custom field. 107 108 Args: 109 name: Field name shown in the UI. Must be unique. 110 data_type: Value type. One of ``"string"``, ``"boolean"``, 111 ``"integer"``, ``"float"``, ``"monetary"``, ``"date"``, 112 ``"url"``, ``"documentlink"``, ``"select"``. 113 extra_data: Additional configuration for the field type. For 114 ``data_type="select"``, pass 115 ``{"select_options": ["Option A", "Option B"]}``. For all 116 other types, leave as ``None``. 117 owner: Numeric user ID to assign as owner. ``None`` creates the 118 field with no owner (visible to all users). 119 set_permissions: Explicit view/change permission sets. Defaults to 120 no permissions (all users have access via public visibility). 121 122 Returns: 123 The newly created 124 :class:`~easypaperless.models.custom_fields.CustomField`. 125 """ 126 return cast(CustomField, await self._create_resource( 127 "custom_fields", 128 CustomField, 129 owner=owner, 130 set_permissions=set_permissions, 131 name=name, 132 data_type=data_type, 133 extra_data=extra_data, 134 ))
Create a new custom field.
Args:
name: Field name shown in the UI. Must be unique.
data_type: Value type. One of "string", "boolean",
"integer", "float", "monetary", "date",
"url", "documentlink", "select".
extra_data: Additional configuration for the field type. For
data_type="select", pass
{"select_options": ["Option A", "Option B"]}. For all
other types, leave as None.
owner: Numeric user ID to assign as owner. None creates the
field with no owner (visible to all users).
set_permissions: Explicit view/change permission sets. Defaults to
no permissions (all users have access via public visibility).
Returns:
The newly created
~easypaperless.models.custom_fields.CustomField.
136 async def update_custom_field( 137 self, 138 id: int, 139 *, 140 name: str | None = None, 141 extra_data: Any | None = None, 142 ) -> CustomField: 143 """Partially update a custom field (PATCH semantics). 144 145 Note: 146 ``data_type`` is intentionally excluded — the paperless-ngx API 147 does not allow changing the type of an existing custom field. To 148 change the type, delete and recreate the field. 149 150 Args: 151 id: Numeric ID of the custom field to update. 152 name: Field name shown in the UI. Must be unique. 153 extra_data: Additional configuration for the field type (e.g. 154 ``{"select_options": ["Option A", "Option B"]}`` for select 155 fields). Passing ``None`` is a no-op and will not clear the 156 existing value. 157 158 Returns: 159 The updated 160 :class:`~easypaperless.models.custom_fields.CustomField`. 161 """ 162 return cast(CustomField, await self._update_resource( 163 "custom_fields", 164 id, 165 CustomField, 166 name=name, 167 extra_data=extra_data, 168 ))
Partially update a custom field (PATCH semantics).
Note:
data_type is intentionally excluded — the paperless-ngx API
does not allow changing the type of an existing custom field. To
change the type, delete and recreate the field.
Args:
id: Numeric ID of the custom field to update.
name: Field name shown in the UI. Must be unique.
extra_data: Additional configuration for the field type (e.g.
{"select_options": ["Option A", "Option B"]} for select
fields). Passing None is a no-op and will not clear the
existing value.
Returns:
The updated
~easypaperless.models.custom_fields.CustomField.
170 async def delete_custom_field(self, id: int) -> None: 171 """Delete a custom field. 172 173 Args: 174 id: Numeric ID of the custom field to delete. 175 176 Raises: 177 ~easypaperless.exceptions.NotFoundError: If no custom field 178 exists with that ID. 179 """ 180 await self._delete_resource("custom_fields", id)
Delete a custom field.
Args: id: Numeric ID of the custom field to delete.
Raises: ~easypaperless.exceptions.NotFoundError: If no custom field exists with that ID.
65class SyncPaperlessClient( 66 SyncDocumentsMixin, 67 SyncNotesMixin, 68 SyncUploadMixin, 69 SyncDocumentBulkMixin, 70 SyncNonDocumentBulkMixin, 71 SyncTagsMixin, 72 SyncCorrespondentsMixin, 73 SyncDocumentTypesMixin, 74 SyncStoragePathsMixin, 75 SyncCustomFieldsMixin, 76 _SyncCore, 77): 78 """Synchronous interface to paperless-ngx. 79 80 Exposes the same methods as 81 :class:`~easypaperless.client.PaperlessClient` but runs them 82 synchronously, making it suitable for scripts and REPL sessions that do 83 not use ``asyncio``. 84 85 All methods have identical signatures to their async counterparts on 86 :class:`~easypaperless.client.PaperlessClient`. Operations run on a 87 dedicated background event loop thread so that the httpx connection pool 88 is reused across calls and cleanup works correctly. 89 90 Use as a context manager to ensure proper cleanup: 91 92 Example: 93 with SyncPaperlessClient(url="http://localhost:8000", api_key="abc") as client: 94 tags = client.list_tags() 95 docs = client.list_documents(search="invoice") 96 97 Note: 98 Cannot be used inside an already-running event loop (e.g. Jupyter 99 notebooks). Use :class:`~easypaperless.client.PaperlessClient` 100 directly in those environments. 101 """ 102 103 # Explicit assignments so pdoc documents these inherited methods. 104 # Documents 105 list_documents = SyncDocumentsMixin.list_documents 106 get_document = SyncDocumentsMixin.get_document 107 get_document_metadata = SyncDocumentsMixin.get_document_metadata 108 update_document = SyncDocumentsMixin.update_document 109 delete_document = SyncDocumentsMixin.delete_document 110 download_document = SyncDocumentsMixin.download_document 111 # Notes 112 get_notes = SyncNotesMixin.get_notes 113 create_note = SyncNotesMixin.create_note 114 delete_note = SyncNotesMixin.delete_note 115 # Upload 116 upload_document = SyncUploadMixin.upload_document 117 # Document bulk operations 118 bulk_edit = SyncDocumentBulkMixin.bulk_edit 119 bulk_add_tag = SyncDocumentBulkMixin.bulk_add_tag 120 bulk_remove_tag = SyncDocumentBulkMixin.bulk_remove_tag 121 bulk_modify_tags = SyncDocumentBulkMixin.bulk_modify_tags 122 bulk_delete = SyncDocumentBulkMixin.bulk_delete 123 bulk_set_correspondent = SyncDocumentBulkMixin.bulk_set_correspondent 124 bulk_set_document_type = SyncDocumentBulkMixin.bulk_set_document_type 125 bulk_set_storage_path = SyncDocumentBulkMixin.bulk_set_storage_path 126 bulk_modify_custom_fields = SyncDocumentBulkMixin.bulk_modify_custom_fields 127 bulk_set_permissions = SyncDocumentBulkMixin.bulk_set_permissions 128 # Non-document bulk operations 129 bulk_edit_objects = SyncNonDocumentBulkMixin.bulk_edit_objects 130 bulk_delete_tags = SyncNonDocumentBulkMixin.bulk_delete_tags 131 bulk_delete_correspondents = SyncNonDocumentBulkMixin.bulk_delete_correspondents 132 bulk_delete_document_types = SyncNonDocumentBulkMixin.bulk_delete_document_types 133 bulk_delete_storage_paths = SyncNonDocumentBulkMixin.bulk_delete_storage_paths 134 bulk_set_permissions_tags = SyncNonDocumentBulkMixin.bulk_set_permissions_tags 135 bulk_set_permissions_correspondents = SyncNonDocumentBulkMixin.bulk_set_permissions_correspondents 136 bulk_set_permissions_document_types = SyncNonDocumentBulkMixin.bulk_set_permissions_document_types 137 bulk_set_permissions_storage_paths = SyncNonDocumentBulkMixin.bulk_set_permissions_storage_paths 138 # Tags 139 list_tags = SyncTagsMixin.list_tags 140 get_tag = SyncTagsMixin.get_tag 141 create_tag = SyncTagsMixin.create_tag 142 update_tag = SyncTagsMixin.update_tag 143 delete_tag = SyncTagsMixin.delete_tag 144 # Correspondents 145 list_correspondents = SyncCorrespondentsMixin.list_correspondents 146 get_correspondent = SyncCorrespondentsMixin.get_correspondent 147 create_correspondent = SyncCorrespondentsMixin.create_correspondent 148 update_correspondent = SyncCorrespondentsMixin.update_correspondent 149 delete_correspondent = SyncCorrespondentsMixin.delete_correspondent 150 # Document types 151 list_document_types = SyncDocumentTypesMixin.list_document_types 152 get_document_type = SyncDocumentTypesMixin.get_document_type 153 create_document_type = SyncDocumentTypesMixin.create_document_type 154 update_document_type = SyncDocumentTypesMixin.update_document_type 155 delete_document_type = SyncDocumentTypesMixin.delete_document_type 156 # Storage paths 157 list_storage_paths = SyncStoragePathsMixin.list_storage_paths 158 get_storage_path = SyncStoragePathsMixin.get_storage_path 159 create_storage_path = SyncStoragePathsMixin.create_storage_path 160 update_storage_path = SyncStoragePathsMixin.update_storage_path 161 delete_storage_path = SyncStoragePathsMixin.delete_storage_path 162 # Custom fields 163 list_custom_fields = SyncCustomFieldsMixin.list_custom_fields 164 get_custom_field = SyncCustomFieldsMixin.get_custom_field 165 create_custom_field = SyncCustomFieldsMixin.create_custom_field 166 update_custom_field = SyncCustomFieldsMixin.update_custom_field 167 delete_custom_field = SyncCustomFieldsMixin.delete_custom_field 168 169 def __init__(self, url: str, api_key: str, **kwargs: Any) -> None: 170 """Create a synchronous paperless-ngx client. 171 172 Args: 173 url: Base URL of the paperless-ngx instance 174 (e.g. ``"http://localhost:8000"``). 175 api_key: API token. Generate one in paperless-ngx under 176 *Settings → API → Generate Token*. 177 **kwargs: Additional keyword arguments forwarded to 178 :class:`~easypaperless.client.PaperlessClient` (e.g. 179 ``poll_interval``, ``poll_timeout``). 180 """ 181 super().__init__(url, api_key, **kwargs) 182 183 def __enter__(self) -> SyncPaperlessClient: 184 return self 185 186 def __exit__(self, *args: Any) -> None: 187 self.close()
Synchronous interface to paperless-ngx.
Exposes the same methods as
~easypaperless.client.PaperlessClient but runs them
synchronously, making it suitable for scripts and REPL sessions that do
not use asyncio.
All methods have identical signatures to their async counterparts on
~easypaperless.client.PaperlessClient. Operations run on a
dedicated background event loop thread so that the httpx connection pool
is reused across calls and cleanup works correctly.
Use as a context manager to ensure proper cleanup:
Example: with SyncPaperlessClient(url="http://localhost:8000", api_key="abc") as client: tags = client.list_tags() docs = client.list_documents(search="invoice")
Note:
Cannot be used inside an already-running event loop (e.g. Jupyter
notebooks). Use ~easypaperless.client.PaperlessClient
directly in those environments.
169 def __init__(self, url: str, api_key: str, **kwargs: Any) -> None: 170 """Create a synchronous paperless-ngx client. 171 172 Args: 173 url: Base URL of the paperless-ngx instance 174 (e.g. ``"http://localhost:8000"``). 175 api_key: API token. Generate one in paperless-ngx under 176 *Settings → API → Generate Token*. 177 **kwargs: Additional keyword arguments forwarded to 178 :class:`~easypaperless.client.PaperlessClient` (e.g. 179 ``poll_interval``, ``poll_timeout``). 180 """ 181 super().__init__(url, api_key, **kwargs)
Create a synchronous paperless-ngx client.
Args:
url: Base URL of the paperless-ngx instance
(e.g. "http://localhost:8000").
api_key: API token. Generate one in paperless-ngx under
Settings → API → Generate Token.
**kwargs: Additional keyword arguments forwarded to
~easypaperless.client.PaperlessClient (e.g.
poll_interval, poll_timeout).
31 def list_documents( 32 self, 33 *, 34 search: str | None = None, 35 search_mode: str = "title_or_text", 36 ids: list[int] | None = None, 37 tags: list[int | str] | None = None, 38 any_tags: list[int | str] | None = None, 39 exclude_tags: list[int | str] | None = None, 40 correspondent: int | str | None = None, 41 any_correspondent: list[int | str] | None = None, 42 exclude_correspondents: list[int | str] | None = None, 43 document_type: int | str | None = None, 44 any_document_type: list[int | str] | None = None, 45 exclude_document_types: list[int | str] | None = None, 46 storage_path: int | str | None = None, 47 any_storage_paths: list[int | str] | None = None, 48 exclude_storage_paths: list[int | str] | None = None, 49 owner: int | None = None, 50 exclude_owners: list[int] | None = None, 51 custom_fields: list[int | str] | None = None, 52 any_custom_fields: list[int | str] | None = None, 53 exclude_custom_fields: list[int | str] | None = None, 54 custom_field_query: list[Any] | None = None, 55 archive_serial_number: int | None = None, 56 archive_serial_number_from: int | None = None, 57 archive_serial_number_till: int | None = None, 58 created_after: date | str | None = None, 59 created_before: date | str | None = None, 60 added_after: date | datetime | str | None = None, 61 added_from: date | datetime | str | None = None, 62 added_before: date | datetime | str | None = None, 63 added_until: date | datetime | str | None = None, 64 modified_after: date | datetime | str | None = None, 65 modified_from: date | datetime | str | None = None, 66 modified_before: date | datetime | str | None = None, 67 modified_until: date | datetime | str | None = None, 68 checksum: str | None = None, 69 page_size: int = 25, 70 page: int | None = None, 71 ordering: str | None = None, 72 descending: bool = False, 73 max_results: int | None = None, 74 on_page: Callable[[int, int | None], None] | None = None, 75 ) -> list[Document]: 76 return self._run(self._async_client.list_documents( 77 search=search, 78 search_mode=search_mode, 79 ids=ids, 80 tags=tags, 81 any_tags=any_tags, 82 exclude_tags=exclude_tags, 83 correspondent=correspondent, 84 any_correspondent=any_correspondent, 85 exclude_correspondents=exclude_correspondents, 86 document_type=document_type, 87 any_document_type=any_document_type, 88 exclude_document_types=exclude_document_types, 89 storage_path=storage_path, 90 any_storage_paths=any_storage_paths, 91 exclude_storage_paths=exclude_storage_paths, 92 owner=owner, 93 exclude_owners=exclude_owners, 94 custom_fields=custom_fields, 95 any_custom_fields=any_custom_fields, 96 exclude_custom_fields=exclude_custom_fields, 97 custom_field_query=custom_field_query, 98 archive_serial_number=archive_serial_number, 99 archive_serial_number_from=archive_serial_number_from, 100 archive_serial_number_till=archive_serial_number_till, 101 created_after=created_after, 102 created_before=created_before, 103 added_after=added_after, 104 added_from=added_from, 105 added_before=added_before, 106 added_until=added_until, 107 modified_after=modified_after, 108 modified_from=modified_from, 109 modified_before=modified_before, 110 modified_until=modified_until, 111 checksum=checksum, 112 page_size=page_size, 113 page=page, 114 ordering=ordering, 115 descending=descending, 116 max_results=max_results, 117 on_page=on_page, 118 ))
120 def update_document( 121 self, 122 id: int, 123 *, 124 title: str | None = None, 125 content: str | None = None, 126 date: str | None = None, 127 correspondent: int | str | None = None, 128 document_type: int | str | None = None, 129 storage_path: int | str | None = None, 130 tags: list[int | str] | None = None, 131 asn: int | None = None, 132 custom_fields: list[dict[str, Any]] | None = None, 133 owner: int | None = None, 134 set_permissions: SetPermissions | None = None, 135 ) -> Document: 136 return self._run(self._async_client.update_document( 137 id, 138 title=title, 139 content=content, 140 date=date, 141 correspondent=correspondent, 142 document_type=document_type, 143 storage_path=storage_path, 144 tags=tags, 145 asn=asn, 146 custom_fields=custom_fields, 147 owner=owner, 148 set_permissions=set_permissions, 149 ))
24 def upload_document( 25 self, 26 file: str | Path, 27 *, 28 title: str | None = None, 29 created: str | None = None, 30 correspondent: int | str | None = None, 31 document_type: int | str | None = None, 32 storage_path: int | str | None = None, 33 tags: list[int | str] | None = None, 34 asn: int | None = None, 35 wait: bool = False, 36 poll_interval: float | None = None, 37 poll_timeout: float | None = None, 38 ) -> str | Document: 39 return self._run(self._async_client.upload_document( 40 file, 41 title=title, 42 created=created, 43 correspondent=correspondent, 44 document_type=document_type, 45 storage_path=storage_path, 46 tags=tags, 47 asn=asn, 48 wait=wait, 49 poll_interval=poll_interval, 50 poll_timeout=poll_timeout, 51 ))
67 def bulk_modify_custom_fields( 68 self, 69 document_ids: list[int], 70 *, 71 add_fields: list[dict[str, Any]] | None = None, 72 remove_fields: list[int] | None = None, 73 ) -> None: 74 return self._run( 75 self._async_client.bulk_modify_custom_fields( 76 document_ids, add_fields=add_fields, remove_fields=remove_fields 77 ) 78 )
80 def bulk_set_permissions( 81 self, 82 document_ids: list[int], 83 *, 84 set_permissions: SetPermissions | None = None, 85 owner: int | None = None, 86 merge: bool = False, 87 ) -> None: 88 return self._run( 89 self._async_client.bulk_set_permissions( 90 document_ids, 91 set_permissions=set_permissions, 92 owner=owner, 93 merge=merge, 94 ) 95 )
64 def bulk_set_permissions_correspondents( 65 self, 66 ids: list[int], 67 *, 68 set_permissions: SetPermissions | None = None, 69 owner: int | None = None, 70 merge: bool = False, 71 ) -> None: 72 return self._run( 73 self._async_client.bulk_set_permissions_correspondents( 74 ids, set_permissions=set_permissions, owner=owner, merge=merge 75 ) 76 )
78 def bulk_set_permissions_document_types( 79 self, 80 ids: list[int], 81 *, 82 set_permissions: SetPermissions | None = None, 83 owner: int | None = None, 84 merge: bool = False, 85 ) -> None: 86 return self._run( 87 self._async_client.bulk_set_permissions_document_types( 88 ids, set_permissions=set_permissions, owner=owner, merge=merge 89 ) 90 )
92 def bulk_set_permissions_storage_paths( 93 self, 94 ids: list[int], 95 *, 96 set_permissions: SetPermissions | None = None, 97 owner: int | None = None, 98 merge: bool = False, 99 ) -> None: 100 return self._run( 101 self._async_client.bulk_set_permissions_storage_paths( 102 ids, set_permissions=set_permissions, owner=owner, merge=merge 103 ) 104 )
49 def create_tag( 50 self, 51 *, 52 name: str, 53 color: str | None = None, 54 is_inbox_tag: bool | None = None, 55 match: str | None = None, 56 matching_algorithm: MatchingAlgorithm | None = None, 57 is_insensitive: bool | None = None, 58 parent: int | None = None, 59 owner: int | None = None, 60 set_permissions: SetPermissions | None = None, 61 ) -> Tag: 62 return self._run(self._async_client.create_tag( 63 name=name, 64 color=color, 65 is_inbox_tag=is_inbox_tag, 66 match=match, 67 matching_algorithm=matching_algorithm, 68 is_insensitive=is_insensitive, 69 parent=parent, 70 owner=owner, 71 set_permissions=set_permissions, 72 ))
74 def update_tag( 75 self, 76 id: int, 77 *, 78 name: str | None = None, 79 color: str | None = None, 80 is_inbox_tag: bool | None = None, 81 match: str | None = None, 82 matching_algorithm: MatchingAlgorithm | None = None, 83 is_insensitive: bool | None = None, 84 parent: int | None = None, 85 ) -> Tag: 86 return self._run(self._async_client.update_tag( 87 id, 88 name=name, 89 color=color, 90 is_inbox_tag=is_inbox_tag, 91 match=match, 92 matching_algorithm=matching_algorithm, 93 is_insensitive=is_insensitive, 94 parent=parent, 95 ))
25 def list_correspondents( 26 self, 27 *, 28 ids: list[int] | None = None, 29 name_contains: str | None = None, 30 name_exact: str | None = None, 31 page: int | None = None, 32 page_size: int | None = None, 33 ordering: str | None = None, 34 descending: bool = False, 35 ) -> list[Correspondent]: 36 return self._run(self._async_client.list_correspondents( 37 ids=ids, 38 name_contains=name_contains, 39 name_exact=name_exact, 40 page=page, 41 page_size=page_size, 42 ordering=ordering, 43 descending=descending, 44 ))
49 def create_correspondent( 50 self, 51 *, 52 name: str, 53 match: str | None = None, 54 matching_algorithm: MatchingAlgorithm | None = None, 55 is_insensitive: bool | None = None, 56 owner: int | None = None, 57 set_permissions: SetPermissions | None = None, 58 ) -> Correspondent: 59 return self._run(self._async_client.create_correspondent( 60 name=name, 61 match=match, 62 matching_algorithm=matching_algorithm, 63 is_insensitive=is_insensitive, 64 owner=owner, 65 set_permissions=set_permissions, 66 ))
68 def update_correspondent( 69 self, 70 id: int, 71 *, 72 name: str | None = None, 73 match: str | None = None, 74 matching_algorithm: MatchingAlgorithm | None = None, 75 is_insensitive: bool | None = None, 76 ) -> Correspondent: 77 return self._run(self._async_client.update_correspondent( 78 id, 79 name=name, 80 match=match, 81 matching_algorithm=matching_algorithm, 82 is_insensitive=is_insensitive, 83 ))
25 def list_document_types( 26 self, 27 *, 28 ids: list[int] | None = None, 29 name_contains: str | None = None, 30 name_exact: str | None = None, 31 page: int | None = None, 32 page_size: int | None = None, 33 ordering: str | None = None, 34 descending: bool = False, 35 ) -> list[DocumentType]: 36 return self._run(self._async_client.list_document_types( 37 ids=ids, 38 name_contains=name_contains, 39 name_exact=name_exact, 40 page=page, 41 page_size=page_size, 42 ordering=ordering, 43 descending=descending, 44 ))
49 def create_document_type( 50 self, 51 *, 52 name: str, 53 match: str | None = None, 54 matching_algorithm: MatchingAlgorithm | None = None, 55 is_insensitive: bool | None = None, 56 owner: int | None = None, 57 set_permissions: SetPermissions | None = None, 58 ) -> DocumentType: 59 return self._run(self._async_client.create_document_type( 60 name=name, 61 match=match, 62 matching_algorithm=matching_algorithm, 63 is_insensitive=is_insensitive, 64 owner=owner, 65 set_permissions=set_permissions, 66 ))
68 def update_document_type( 69 self, 70 id: int, 71 *, 72 name: str | None = None, 73 match: str | None = None, 74 matching_algorithm: MatchingAlgorithm | None = None, 75 is_insensitive: bool | None = None, 76 ) -> DocumentType: 77 return self._run(self._async_client.update_document_type( 78 id, 79 name=name, 80 match=match, 81 matching_algorithm=matching_algorithm, 82 is_insensitive=is_insensitive, 83 ))
25 def list_storage_paths( 26 self, 27 *, 28 ids: list[int] | None = None, 29 name_contains: str | None = None, 30 name_exact: str | None = None, 31 page: int | None = None, 32 page_size: int | None = None, 33 ordering: str | None = None, 34 descending: bool = False, 35 ) -> list[StoragePath]: 36 return self._run(self._async_client.list_storage_paths( 37 ids=ids, 38 name_contains=name_contains, 39 name_exact=name_exact, 40 page=page, 41 page_size=page_size, 42 ordering=ordering, 43 descending=descending, 44 ))
49 def create_storage_path( 50 self, 51 *, 52 name: str, 53 path: str | None = None, 54 match: str | None = None, 55 matching_algorithm: MatchingAlgorithm | None = None, 56 is_insensitive: bool | None = None, 57 owner: int | None = None, 58 set_permissions: SetPermissions | None = None, 59 ) -> StoragePath: 60 return self._run(self._async_client.create_storage_path( 61 name=name, 62 path=path, 63 match=match, 64 matching_algorithm=matching_algorithm, 65 is_insensitive=is_insensitive, 66 owner=owner, 67 set_permissions=set_permissions, 68 ))
70 def update_storage_path( 71 self, 72 id: int, 73 *, 74 name: str | None = None, 75 path: str | None = None, 76 match: str | None = None, 77 matching_algorithm: MatchingAlgorithm | None = None, 78 is_insensitive: bool | None = None, 79 ) -> StoragePath: 80 return self._run(self._async_client.update_storage_path( 81 id, 82 name=name, 83 path=path, 84 match=match, 85 matching_algorithm=matching_algorithm, 86 is_insensitive=is_insensitive, 87 ))
24 def list_custom_fields( 25 self, 26 *, 27 page: int | None = None, 28 page_size: int | None = None, 29 ordering: str | None = None, 30 descending: bool = False, 31 ) -> list[CustomField]: 32 return self._run(self._async_client.list_custom_fields( 33 page=page, 34 page_size=page_size, 35 ordering=ordering, 36 descending=descending, 37 ))
42 def create_custom_field( 43 self, 44 *, 45 name: str, 46 data_type: str, 47 extra_data: Any | None = None, 48 owner: int | None = None, 49 set_permissions: SetPermissions | None = None, 50 ) -> CustomField: 51 return self._run(self._async_client.create_custom_field( 52 name=name, 53 data_type=data_type, 54 extra_data=extra_data, 55 owner=owner, 56 set_permissions=set_permissions, 57 ))
9class MatchingAlgorithm(IntEnum): 10 """Auto-matching algorithm used by tags, correspondents, document types, and storage paths.""" 11 12 NONE = 0 13 ANY_WORD = 1 14 ALL_WORDS = 2 15 EXACT = 3 16 REGEX = 4 17 FUZZY = 5 18 AUTO = 6
Auto-matching algorithm used by tags, correspondents, document types, and storage paths.
13class Correspondent(BaseModel): 14 """A paperless-ngx correspondent (sender or recipient of a document). 15 16 Attributes: 17 id: Unique correspondent ID. 18 name: Correspondent name. 19 document_count: Number of documents assigned to this correspondent. 20 last_correspondence: Date of the most recent document assigned here, 21 or ``None``. 22 """ 23 24 model_config = ConfigDict(extra="ignore") 25 26 id: int 27 name: str 28 slug: str | None = None 29 match: str | None = None 30 matching_algorithm: MatchingAlgorithm | None = None 31 is_insensitive: bool | None = None 32 document_count: int | None = None 33 last_correspondence: date | None = None 34 owner: int | None = None 35 user_can_change: bool | None = None
A paperless-ngx correspondent (sender or recipient of a document).
Attributes:
id: Unique correspondent ID.
name: Correspondent name.
document_count: Number of documents assigned to this correspondent.
last_correspondence: Date of the most recent document assigned here,
or None.
26class CustomField(BaseModel): 27 """A custom field definition in paperless-ngx. 28 29 Attributes: 30 id: Unique custom-field ID. 31 name: Field name shown in the UI. 32 data_type: The value type for this field (see 33 :class:`FieldDataType`). 34 extra_data: Additional configuration (e.g. select options), or 35 ``None``. 36 document_count: Number of documents that have a value for this field. 37 """ 38 39 model_config = ConfigDict(extra="ignore") 40 41 id: int 42 name: str 43 data_type: FieldDataType 44 extra_data: Any = None 45 document_count: int | None = None
A custom field definition in paperless-ngx.
Attributes:
id: Unique custom-field ID.
name: Field name shown in the UI.
data_type: The value type for this field (see
FieldDataType).
extra_data: Additional configuration (e.g. select options), or
None.
document_count: Number of documents that have a value for this field.
66class CustomFieldValue(BaseModel): 67 """A custom field value attached to a document. 68 69 Attributes: 70 field: ID of the :class:`~easypaperless.models.custom_fields.CustomField` 71 definition. 72 value: The stored value; its Python type depends on the field's 73 ``data_type``. 74 """ 75 76 model_config = ConfigDict(extra="ignore") 77 78 field: int 79 value: Any = None
A custom field value attached to a document.
Attributes:
field: ID of the ~easypaperless.models.custom_fields.CustomField
definition.
value: The stored value; its Python type depends on the field's
data_type.
172class Document(BaseModel): 173 """A paperless-ngx document. 174 175 Attributes: 176 id: Unique document ID. 177 title: Document title. 178 content: Full OCR text content, if available. 179 tags: List of tag IDs assigned to this document. 180 document_type: ID of the assigned document type, or ``None``. 181 correspondent: ID of the assigned correspondent, or ``None``. 182 storage_path: ID of the assigned storage path, or ``None``. 183 created: Full creation datetime. 184 created_date: Date portion of creation (``date`` object). 185 archive_serial_number: Archive serial number (ASN), or ``None``. 186 custom_fields: List of :class:`CustomFieldValue` instances. 187 notes: User notes attached to this document. 188 search_hit: Full-text search relevance metadata, populated only when 189 the document was returned by a full-text search. 190 metadata: Extended file-level metadata (checksums, sizes, MIME type). 191 ``None`` unless the document was fetched with 192 ``include_metadata=True`` or enriched via 193 :meth:`~easypaperless.PaperlessClient.get_document_metadata`. 194 """ 195 196 model_config = ConfigDict(extra="ignore", populate_by_name=True) 197 198 id: int 199 title: str 200 content: str | None = None 201 tags: list[int] = Field(default_factory=list) 202 document_type: int | None = None 203 correspondent: int | None = None 204 storage_path: int | None = None 205 created: datetime | None = None 206 created_date: date | None = None 207 modified: datetime | None = None 208 added: datetime | None = None 209 archive_serial_number: int | None = None 210 original_file_name: str | None = None 211 archived_file_name: str | None = None 212 owner: int | None = None 213 user_can_change: bool | None = None 214 is_shared_by_requester: bool | None = None 215 notes: list[DocumentNote] = Field(default_factory=list) 216 custom_fields: list[CustomFieldValue] = Field(default_factory=list) 217 search_hit: SearchHit | None = Field(default=None, alias="__search_hit__") 218 metadata: DocumentMetadata | None = None
A paperless-ngx document.
Attributes:
id: Unique document ID.
title: Document title.
content: Full OCR text content, if available.
tags: List of tag IDs assigned to this document.
document_type: ID of the assigned document type, or None.
correspondent: ID of the assigned correspondent, or None.
storage_path: ID of the assigned storage path, or None.
created: Full creation datetime.
created_date: Date portion of creation (date object).
archive_serial_number: Archive serial number (ASN), or None.
custom_fields: List of CustomFieldValue instances.
notes: User notes attached to this document.
search_hit: Full-text search relevance metadata, populated only when
the document was returned by a full-text search.
metadata: Extended file-level metadata (checksums, sizes, MIME type).
None unless the document was fetched with
include_metadata=True or enriched via
~easypaperless.PaperlessClient.get_document_metadata().
131class DocumentMetadata(BaseModel): 132 """Extended file-level metadata for a document. 133 134 Returned by ``GET /api/documents/{id}/metadata/`` and optionally attached 135 to a :class:`Document` when :meth:`~easypaperless.PaperlessClient.get_document` 136 is called with ``include_metadata=True``. 137 138 Because reading metadata requires disk I/O it is **not** included in 139 document list responses; it must be requested explicitly. 140 141 Attributes: 142 original_checksum: MD5 checksum of the original uploaded file. 143 original_size: Size of the original file in bytes. 144 original_mime_type: MIME type of the original file 145 (e.g. ``"application/pdf"``). 146 media_filename: Path of the archived file relative to the 147 paperless-ngx media root. 148 has_archive_version: ``True`` when paperless-ngx has produced an 149 archived (post-processed) PDF in addition to the original. 150 original_metadata: File-level metadata entries extracted from the 151 original document (PDF XMP/info tags, etc.). 152 archive_checksum: MD5 checksum of the archived file, or ``None`` if 153 no archive version exists. 154 archive_size: Size of the archived file in bytes, or ``None``. 155 archive_metadata: File-level metadata entries from the archived 156 document, or ``None``. 157 """ 158 159 model_config = ConfigDict(extra="ignore") 160 161 original_checksum: str | None = None 162 original_size: int | None = None 163 original_mime_type: str | None = None 164 media_filename: str | None = None 165 has_archive_version: bool | None = None 166 original_metadata: list[FileMetadataEntry] = Field(default_factory=list) 167 archive_checksum: str | None = None 168 archive_size: int | None = None 169 archive_metadata: list[FileMetadataEntry] | None = None
Extended file-level metadata for a document.
Returned by GET /api/documents/{id}/metadata/ and optionally attached
to a Document when ~easypaperless.PaperlessClient.get_document()
is called with include_metadata=True.
Because reading metadata requires disk I/O it is not included in document list responses; it must be requested explicitly.
Attributes:
original_checksum: MD5 checksum of the original uploaded file.
original_size: Size of the original file in bytes.
original_mime_type: MIME type of the original file
(e.g. "application/pdf").
media_filename: Path of the archived file relative to the
paperless-ngx media root.
has_archive_version: True when paperless-ngx has produced an
archived (post-processed) PDF in addition to the original.
original_metadata: File-level metadata entries extracted from the
original document (PDF XMP/info tags, etc.).
archive_checksum: MD5 checksum of the archived file, or None if
no archive version exists.
archive_size: Size of the archived file in bytes, or None.
archive_metadata: File-level metadata entries from the archived
document, or None.
82class DocumentNote(BaseModel): 83 """A user note attached to a document. 84 85 Attributes: 86 id: Unique note ID. 87 note: Text content of the note. 88 created: Timestamp when the note was created. 89 document: ID of the parent document. 90 user: ID of the user who created the note. 91 """ 92 93 model_config = ConfigDict(extra="ignore") 94 95 id: int | None = None 96 note: str 97 created: datetime | None = None 98 document: int | None = None 99 user: int | None = None 100 101 @field_validator("user", mode="before") 102 @classmethod 103 def _coerce_user(cls, v: object) -> int | None: 104 if isinstance(v, dict): 105 return v.get("id") 106 return v # type: ignore[return-value]
A user note attached to a document.
Attributes: id: Unique note ID. note: Text content of the note. created: Timestamp when the note was created. document: ID of the parent document. user: ID of the user who created the note.
11class DocumentType(BaseModel): 12 """A paperless-ngx document type (e.g. Invoice, Receipt, Contract). 13 14 Attributes: 15 id: Unique document-type ID. 16 name: Document-type name. 17 document_count: Number of documents assigned to this document type. 18 """ 19 20 model_config = ConfigDict(extra="ignore") 21 22 id: int 23 name: str 24 slug: str | None = None 25 match: str | None = None 26 matching_algorithm: MatchingAlgorithm | None = None 27 is_insensitive: bool | None = None 28 document_count: int | None = None 29 owner: int | None = None 30 user_can_change: bool | None = None
A paperless-ngx document type (e.g. Invoice, Receipt, Contract).
Attributes: id: Unique document-type ID. name: Document-type name. document_count: Number of documents assigned to this document type.
12class FieldDataType(StrEnum): 13 """Allowed data types for a custom field.""" 14 15 string = "string" 16 boolean = "boolean" 17 integer = "integer" 18 float = "float" 19 monetary = "monetary" 20 date = "date" 21 url = "url" 22 documentlink = "documentlink" 23 select = "select"
Allowed data types for a custom field.
109class FileMetadataEntry(BaseModel): 110 """A single embedded metadata key-value pair from a document file. 111 112 Paperless-ngx reads file-level metadata (e.g. PDF XMP/info tags) and 113 exposes each entry in this format. ``namespace`` and ``prefix`` are 114 ``None`` for non-namespaced entries. 115 116 Attributes: 117 namespace: XML namespace URI, or ``None``. 118 prefix: Namespace prefix (e.g. ``"pdf"``), or ``None``. 119 key: Metadata key (e.g. ``"Producer"``). 120 value: Metadata value as a string. 121 """ 122 123 model_config = ConfigDict(extra="ignore") 124 125 namespace: str | None = None 126 prefix: str | None = None 127 key: str 128 value: str
A single embedded metadata key-value pair from a document file.
Paperless-ngx reads file-level metadata (e.g. PDF XMP/info tags) and
exposes each entry in this format. namespace and prefix are
None for non-namespaced entries.
Attributes:
namespace: XML namespace URI, or None.
prefix: Namespace prefix (e.g. "pdf"), or None.
key: Metadata key (e.g. "Producer").
value: Metadata value as a string.
7class PermissionSet(BaseModel): 8 users: list[int] = Field(default_factory=list) 9 groups: list[int] = Field(default_factory=list)
!!! abstract "Usage Documentation" Models
A base class for creating Pydantic models.
Attributes:
__class_vars__: The names of the class variables defined on the model.
__private_attributes__: Metadata about the private attributes of the model.
__signature__: The synthesized __init__ [Signature][inspect.Signature] of the model.
__pydantic_complete__: Whether model building is completed, or if there are still undefined fields.
__pydantic_core_schema__: The core schema of the model.
__pydantic_custom_init__: Whether the model has a custom `__init__` function.
__pydantic_decorators__: Metadata containing the decorators defined on the model.
This replaces `Model.__validators__` and `Model.__root_validators__` from Pydantic V1.
__pydantic_generic_metadata__: Metadata for generic models; contains data used for a similar purpose to
__args__, __origin__, __parameters__ in typing-module generics. May eventually be replaced by these.
__pydantic_parent_namespace__: Parent namespace of the model, used for automatic rebuilding of models.
__pydantic_post_init__: The name of the post-init method for the model, if defined.
__pydantic_root_model__: Whether the model is a [`RootModel`][pydantic.root_model.RootModel].
__pydantic_serializer__: The `pydantic-core` `SchemaSerializer` used to dump instances of the model.
__pydantic_validator__: The `pydantic-core` `SchemaValidator` used to validate instances of the model.
__pydantic_fields__: A dictionary of field names and their corresponding [`FieldInfo`][pydantic.fields.FieldInfo] objects.
__pydantic_computed_fields__: A dictionary of computed field names and their corresponding [`ComputedFieldInfo`][pydantic.fields.ComputedFieldInfo] objects.
__pydantic_extra__: A dictionary containing extra values, if [`extra`][pydantic.config.ConfigDict.extra]
is set to `'allow'`.
__pydantic_fields_set__: The names of fields explicitly set during instantiation.
__pydantic_private__: Values of private attributes set on the model instance.
49class SearchHit(BaseModel): 50 """Full-text search relevance metadata returned alongside a document. 51 52 Attributes: 53 score: Relevance score assigned by the Whoosh FTS engine. 54 highlights: HTML snippet with matching terms highlighted. 55 rank: Position in the result set by relevance. 56 """ 57 58 model_config = ConfigDict(extra="ignore") 59 60 score: float | None = None 61 highlights: str | None = None 62 note_highlights: str | None = None 63 rank: int | None = None
Full-text search relevance metadata returned alongside a document.
Attributes: score: Relevance score assigned by the Whoosh FTS engine. highlights: HTML snippet with matching terms highlighted. rank: Position in the result set by relevance.
12class SetPermissions(BaseModel): 13 view: PermissionSet = Field(default_factory=PermissionSet) 14 change: PermissionSet = Field(default_factory=PermissionSet)
!!! abstract "Usage Documentation" Models
A base class for creating Pydantic models.
Attributes:
__class_vars__: The names of the class variables defined on the model.
__private_attributes__: Metadata about the private attributes of the model.
__signature__: The synthesized __init__ [Signature][inspect.Signature] of the model.
__pydantic_complete__: Whether model building is completed, or if there are still undefined fields.
__pydantic_core_schema__: The core schema of the model.
__pydantic_custom_init__: Whether the model has a custom `__init__` function.
__pydantic_decorators__: Metadata containing the decorators defined on the model.
This replaces `Model.__validators__` and `Model.__root_validators__` from Pydantic V1.
__pydantic_generic_metadata__: Metadata for generic models; contains data used for a similar purpose to
__args__, __origin__, __parameters__ in typing-module generics. May eventually be replaced by these.
__pydantic_parent_namespace__: Parent namespace of the model, used for automatic rebuilding of models.
__pydantic_post_init__: The name of the post-init method for the model, if defined.
__pydantic_root_model__: Whether the model is a [`RootModel`][pydantic.root_model.RootModel].
__pydantic_serializer__: The `pydantic-core` `SchemaSerializer` used to dump instances of the model.
__pydantic_validator__: The `pydantic-core` `SchemaValidator` used to validate instances of the model.
__pydantic_fields__: A dictionary of field names and their corresponding [`FieldInfo`][pydantic.fields.FieldInfo] objects.
__pydantic_computed_fields__: A dictionary of computed field names and their corresponding [`ComputedFieldInfo`][pydantic.fields.ComputedFieldInfo] objects.
__pydantic_extra__: A dictionary containing extra values, if [`extra`][pydantic.config.ConfigDict.extra]
is set to `'allow'`.
__pydantic_fields_set__: The names of fields explicitly set during instantiation.
__pydantic_private__: Values of private attributes set on the model instance.
11class StoragePath(BaseModel): 12 """A paperless-ngx storage path — controls where archived files are stored. 13 14 Attributes: 15 id: Unique storage-path ID. 16 name: Storage-path name. 17 path: Template string used to build the storage path, e.g. 18 ``"{created_year}/{correspondent}/{title}"``. 19 document_count: Number of documents using this storage path. 20 """ 21 22 model_config = ConfigDict(extra="ignore") 23 24 id: int 25 name: str 26 slug: str | None = None 27 path: str | None = None 28 match: str | None = None 29 matching_algorithm: MatchingAlgorithm | None = None 30 is_insensitive: bool | None = None 31 document_count: int | None = None 32 owner: int | None = None 33 user_can_change: bool | None = None
A paperless-ngx storage path — controls where archived files are stored.
Attributes:
id: Unique storage-path ID.
name: Storage-path name.
path: Template string used to build the storage path, e.g.
"{created_year}/{correspondent}/{title}".
document_count: Number of documents using this storage path.
11class Tag(BaseModel): 12 """A paperless-ngx tag. 13 14 Attributes: 15 id: Unique tag ID. 16 name: Tag name. 17 color: Hex colour code used in the UI (e.g. ``"#ff0000"``). 18 is_inbox_tag: If ``True``, newly ingested documents receive this tag 19 automatically until they are processed. 20 document_count: Number of documents currently assigned to this tag. 21 parent: ID of the parent tag for hierarchical tag trees, or ``None``. 22 children: IDs of child tags, or ``None``. 23 """ 24 25 model_config = ConfigDict(extra="ignore") 26 27 id: int 28 name: str 29 slug: str | None = None 30 color: str | None = None 31 text_color: str | None = None 32 match: str | None = None 33 matching_algorithm: MatchingAlgorithm | None = None 34 is_insensitive: bool | None = None 35 is_inbox_tag: bool | None = None 36 document_count: int | None = None 37 owner: int | None = None 38 user_can_change: bool | None = None 39 parent: int | None = None 40 children: list[int] | None = None
A paperless-ngx tag.
Attributes:
id: Unique tag ID.
name: Tag name.
color: Hex colour code used in the UI (e.g. "#ff0000").
is_inbox_tag: If True, newly ingested documents receive this tag
automatically until they are processed.
document_count: Number of documents currently assigned to this tag.
parent: ID of the parent tag for hierarchical tag trees, or None.
children: IDs of child tags, or None.
24class Task(BaseModel): 25 """A paperless-ngx background processing task (e.g. document ingestion). 26 27 Attributes: 28 task_id: Unique Celery task identifier. 29 task_file_name: Original file name submitted for processing. 30 status: Current task status as a :class:`TaskStatus` enum value. 31 result: Human-readable result or error message, set on completion. 32 related_document: String representation of the resulting document ID 33 on success, ``None`` otherwise. 34 """ 35 36 model_config = ConfigDict(extra="ignore") 37 38 task_id: str 39 task_file_name: str | None = None 40 date_created: datetime | None = None 41 date_done: datetime | None = None 42 type: str | None = None 43 status: TaskStatus | None = None 44 result: str | None = None 45 acknowledged: bool | None = None 46 related_document: str | None = None
A paperless-ngx background processing task (e.g. document ingestion).
Attributes:
task_id: Unique Celery task identifier.
task_file_name: Original file name submitted for processing.
status: Current task status as a TaskStatus enum value.
result: Human-readable result or error message, set on completion.
related_document: String representation of the resulting document ID
on success, None otherwise.
13class TaskStatus(StrEnum): 14 """Status values for a paperless-ngx background processing task.""" 15 16 PENDING = "PENDING" 17 STARTED = "STARTED" 18 SUCCESS = "SUCCESS" 19 FAILURE = "FAILURE" 20 RETRY = "RETRY" 21 REVOKED = "REVOKED"
Status values for a paperless-ngx background processing task.
7class PaperlessError(Exception): 8 """Base exception for all easypaperless errors. 9 10 Attributes: 11 status_code: The HTTP status code associated with the error, or 12 ``None`` for non-HTTP errors (e.g. timeouts, local validation). 13 """ 14 15 def __init__(self, message: str, status_code: int | None = None) -> None: 16 """Create a PaperlessError. 17 18 Args: 19 message: Human-readable error description. 20 status_code: HTTP status code, if applicable. 21 """ 22 super().__init__(message) 23 self.status_code = status_code
Base exception for all easypaperless errors.
Attributes:
status_code: The HTTP status code associated with the error, or
None for non-HTTP errors (e.g. timeouts, local validation).
15 def __init__(self, message: str, status_code: int | None = None) -> None: 16 """Create a PaperlessError. 17 18 Args: 19 message: Human-readable error description. 20 status_code: HTTP status code, if applicable. 21 """ 22 super().__init__(message) 23 self.status_code = status_code
Create a PaperlessError.
Args: message: Human-readable error description. status_code: HTTP status code, if applicable.
26class AuthError(PaperlessError): 27 """Raised on 401 or 403 responses. 28 29 Indicates that the API key is missing, invalid, or lacks permission for 30 the requested operation. 31 """
Raised on 401 or 403 responses.
Indicates that the API key is missing, invalid, or lacks permission for the requested operation.
34class NotFoundError(PaperlessError): 35 """Raised on 404 responses or when a name lookup fails."""
Raised on 404 responses or when a name lookup fails.
38class ValidationError(PaperlessError): 39 """Raised on 422 responses or bad input supplied by the caller."""
Raised on 422 responses or bad input supplied by the caller.
42class ServerError(PaperlessError): 43 """Raised on 5xx responses or unrecoverable transport errors."""
Raised on 5xx responses or unrecoverable transport errors.
46class UploadError(PaperlessError): 47 """Raised when file submission or document processing fails. 48 49 Typically raised when paperless-ngx reports a ``FAILURE`` status on the 50 Celery task created by 51 :meth:`~easypaperless.client.PaperlessClient.upload_document`. 52 """
Raised when file submission or document processing fails.
Typically raised when paperless-ngx reports a FAILURE status on the
Celery task created by
~easypaperless.client.PaperlessClient.upload_document().
55class TaskTimeoutError(PaperlessError): 56 """Raised when upload polling exceeds the configured timeout. 57 58 Raised by :meth:`~easypaperless.client.PaperlessClient.upload_document` 59 (with ``wait=True``) when the document processing task does not reach a 60 terminal state within ``poll_timeout`` seconds. 61 """ 62 63 def __init__(self, message: str) -> None: 64 super().__init__(message, status_code=None)
Raised when upload polling exceeds the configured timeout.
Raised by ~easypaperless.client.PaperlessClient.upload_document()
(with wait=True) when the document processing task does not reach a
terminal state within poll_timeout seconds.