paperap.models.abstract package

class paperap.models.abstract.BaseModel(**data)[source]

Bases: BaseModel, ABC

Base model for all Paperless-ngx API objects.

Provide automatic serialization, deserialization, and API interactions with minimal configuration. This abstract class serves as the foundation for all models in the Paperap library, handling data validation, dirty tracking, and API communication.

_meta

Metadata for the model, including filtering and resource information.

_save_lock

Lock for saving operations to prevent race conditions.

_pending_save

Future object for pending save operations.

_save_executor

Executor for asynchronous save operations.

_status

Current status of the model (INITIALIZING, READY, UPDATING, SAVING).

_original_data

Original data from the server for dirty checking.

_saved_data

Data last sent to the database during save operations.

_resource

Associated resource for API interactions.

Raises:

ValueError – If resource is not provided during initialization.

Examples

Models are typically accessed through the client interface:

>>> document = client.documents.get(123)
>>> print(document.title)
>>> document.title = "New Title"
>>> document.save()
Parameters:

data (Any)

class Meta(model)[source]

Bases: Generic

Metadata for the Model.

Define model behavior, filtering capabilities, and API interaction rules. Each model class has its own Meta instance that controls how the model interacts with the Paperless-ngx API.

model

Reference to the model class this metadata belongs to.

name

The name of the model, used in API paths and error messages.

read_only_fields

Fields that should not be modified by the client.

filtering_disabled

Fields that cannot be used for filtering.

filtering_fields

Fields allowed for filtering operations.

supported_filtering_params

Specific filter parameters allowed during queryset filtering (e.g., “content__icontains”, “id__gt”).

blacklist_filtering_params

Filter parameters explicitly disallowed.

filtering_strategies

Strategies that determine how filtering is handled (ALLOW_ALL, ALLOW_NONE, WHITELIST, BLACKLIST).

field_map

Map of API field names to model attribute names.

save_on_write

If True, updating attributes triggers automatic save. If None, follows client.settings.save_on_write.

save_timeout

Timeout in seconds for save operations.

Raises:

ValueError – If both ALLOW_ALL and ALLOW_NONE filtering strategies are set, which would create contradictory behavior.

Examples

Defining a custom Meta for a model:

>>> class Document(StandardModel):
>>>     class Meta(StandardModel.Meta):
>>>         read_only_fields = {"content", "checksum"}
>>>         filtering_strategies = {FilteringStrategies.WHITELIST}
>>>         supported_filtering_params = {"title__icontains", "created__gt"}
Parameters:

model (type[_Self])

__init__(model)[source]
Parameters:

model (type[_Self])

blacklist_filtering_params: ClassVar[set[str]] = {}
field_map: dict[str, str] = {}
filter_allowed(filter_param)[source]

Check if a filter parameter is allowed based on the filtering strategies.

Evaluate whether a given filter parameter can be used with this model based on the configured filtering strategies and rules. This method implements the filtering logic defined by the model’s filtering_strategies.

Parameters:

filter_param (str) – The filter parameter to check (e.g., “title__contains”).

Returns:

True if the filter is allowed, False otherwise.

Return type:

bool

Examples

>>> meta.filter_allowed("title__contains")
True
>>> meta.filter_allowed("content__exact")
False  # If content is in filtering_disabled
filtering_disabled: ClassVar[set[str]] = {}
filtering_fields: ClassVar[set[str]] = {}
filtering_strategies: ClassVar[set[FilteringStrategies]] = {FilteringStrategies.BLACKLIST}
read_only_fields: ClassVar[set[str]] = {}
save_on_write: bool | None = None
save_timeout: int = ModelPrivateAttr(default=60)
supported_filtering_params: ClassVar[set[str]] = {'limit'}
model: type[TypeVar(_Self, bound= BaseModel)]
name: str
__init__(**data)[source]

Initialize the model with resource and data.

Set up the model with the provided resource and initialize it with field values from the API response or user input.

Parameters:

**data (Any) – Field values to initialize the model with.

Raises:

ValueError – If resource is not provided or properly initialized.

Notes

Models should typically be created through their resource’s methods rather than directly instantiated.

classmethod __init_subclass__(**kwargs)[source]

Initialize subclass and set up metadata.

Ensure that each subclass has its own Meta definition and properly inherits metadata attributes from parent classes. This method handles the automatic creation and configuration of model metadata.

Parameters:

**kwargs (Any) – Additional keyword arguments passed to parent __init_subclass__.

Raises:

ConfigurationError – If no Meta class is found in the class hierarchy.

Return type:

None

Notes

This method automatically: - Creates a Meta class for the subclass if not explicitly defined - Inherits and merges metadata from parent classes - Initializes the _meta instance for the subclass

__str__()[source]

Human-readable string representation.

Provide a string representation of the model that includes the model type and ID, typically used for logging and debugging purposes.

Returns:

A string representation of the model (e.g., “Document #123”).

Return type:

str

cleanup()[source]

Clean up resources used by the model.

Shut down the save executor to release resources. Call this method when the model is no longer needed to prevent resource leaks.

Return type:

None

classmethod create(**kwargs)[source]

Create a new model instance and save it to the server.

Create a new instance of the model with the specified field values and immediately save it to the Paperless NGX server. This is a convenience method that delegates to the resource’s create method.

Parameters:

**kwargs (Any) – Field values to set on the new model instance.

Returns:

A new model instance that has been saved to the server.

Return type:

Self

Examples

>>> tag = Tag.create(name="Invoices", color="#ff0000")
>>> correspondent = Correspondent.create(name="Electric Company")
>>> doc_type = DocumentType.create(name="Bill")
delete()[source]

Delete this model from the Paperless NGX server.

Remove the model from the server. After calling this method, the model instance should not be used anymore as it no longer represents a valid server object.

Raises:
Return type:

None

dirty_fields(comparison='both')[source]

Show which fields have changed since last update from the Paperless NGX database.

Compare the current model data with the last saved or retrieved data to identify changes. This method helps determine what will be sent to the server on the next save operation.

Parameters:

comparison (Literal['saved', 'db', 'both']) – Specify the data to compare against: - “saved”: Compare against the last data sent to Paperless NGX - “db”: Compare against the last data retrieved from Paperless NGX - “both”: Compare against both saved and db data (default)

Returns:

A dictionary mapping field names to tuples of

(original_value, current_value) for all fields that have changed.

Return type:

dict[str, tuple[Any, Any]]

Examples

>>> doc = client.documents.get(123)
>>> doc.title = "New Title"
>>> doc.dirty_fields()
{'title': ('Original Title', 'New Title')}
disable_save_on_write()[source]

Disable automatic saving on attribute write.

Set the model’s meta configuration to prevent automatic saving whenever an attribute is modified, overriding the client’s default setting. This affects only this specific model instance. :rtype: None

Examples

>>> doc = client.documents.get(123)
>>> doc.disable_save_on_write()
>>> doc.title = "New Title"  # This won't trigger an automatic save
>>> doc.save()  # Manual save required
enable_save_on_write()[source]

Enable automatic saving on attribute write.

Set the model’s meta configuration to allow automatic saving whenever an attribute is modified, overriding the client’s default setting. This affects only this specific model instance. :rtype: None

Examples

>>> doc = client.documents.get(123)
>>> doc.enable_save_on_write()
>>> doc.title = "New Title"  # This will trigger an automatic save
classmethod from_dict(data)[source]

Create a model instance from API response data.

Instantiate a model from a dictionary of API response data, handling field mapping and type conversion through the resource’s parse_to_model method.

Parameters:

data (dict[str, Any]) – Dictionary containing the API response data.

Returns:

A model instance initialized with the provided data.

Return type:

Self

Examples

>>> api_data = {"id": 123, "title": "Invoice", "created": "2023-01-01T00:00:00Z"}
>>> doc = Document.from_dict(api_data)
>>> print(doc.id, doc.title)
123 Invoice
is_dirty(comparison='both')[source]

Check if any field has changed since last update from the Paperless NGX database.

Determine if the model has unsaved changes by comparing current data with the last saved or retrieved data. New models are always considered dirty.

Parameters:

comparison (Literal['saved', 'db', 'both']) – Specify the data to compare against: - “saved”: Compare against the last data sent to Paperless NGX - “db”: Compare against the last data retrieved from Paperless NGX - “both”: Compare against both saved and db data (default)

Returns:

True if any field has changed, False otherwise.

Return type:

bool

Examples

>>> doc = client.documents.get(123)
>>> doc.is_dirty()
False
>>> doc.title = "New Title"
>>> doc.is_dirty()
True
abstractmethod is_new()[source]

Check if this model represents a new (unsaved) object.

Determine if the model has been saved to the server. Subclasses must implement this method, typically by checking if the model has a valid ID or other server-assigned identifier.

Returns:

True if the model is new (not yet saved), False otherwise.

Return type:

bool

Examples

>>> doc = Document.create(title="New Document")
>>> doc.is_new()  # Returns False after creation
>>>
>>> # When creating a model instance manually:
>>> doc = Document(title="Draft Document")
>>> doc.is_new()  # Returns True
matches_dict(data)[source]

Check if the model matches the provided data.

Compare the model’s current data with a given dictionary to determine if they are equivalent. This is useful for checking if a model needs to be updated based on new data from the server.

Parameters:

data (dict[str, Any]) – Dictionary containing the data to compare against.

Returns:

True if the model matches the data, False otherwise.

Return type:

bool

Examples

>>> doc = client.documents.get(123)
>>> new_data = {"id": 123, "title": "Invoice", "correspondent_id": 5}
>>> doc.matches_dict(new_data)
False  # If any values differ
model_config: ClassVar[ConfigDict] = {'arbitrary_types_allowed': True, 'extra': 'ignore', 'populate_by_name': True, 'use_enum_values': True, 'validate_assignment': True, 'validate_default': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

model_post_init(context: Any, /) None

We need to both initialize private attributes and call the user-defined model_post_init method.

Parameters:
Return type:

None

property resource: BaseResource[Self]

Get the resource associated with this model.

Provide access to the resource instance that handles API interactions for this model type, such as retrieving, creating, updating, and deleting objects.

Returns:

The resource instance for this model type.

Return type:

BaseResource[Self]

property save_executor: ThreadPoolExecutor

Get the thread pool executor for asynchronous save operations.

Provide access to the thread pool that handles asynchronous save operations, creating a new executor if one doesn’t exist yet.

Returns:

The executor for handling

asynchronous save operations.

Return type:

concurrent.futures.ThreadPoolExecutor

should_save_on_write()[source]

Check if the model should save on attribute write.

Determine if changes to model attributes should trigger an automatic save operation based on configuration settings. This method considers both the model’s meta settings and the client settings, with the model’s setting taking precedence.

Returns:

True if the model should save on write, False otherwise.

Return type:

bool

to_dict(*, include_read_only=True, exclude_none=False, exclude_unset=True)[source]

Convert the model to a dictionary for API requests.

Prepare the model data for submission to the API, with options to control which fields are included based on their properties and values.

Parameters:
  • include_read_only (bool) – Whether to include read-only fields in the output. Set to False when preparing data for update operations.

  • exclude_none (bool) – Whether to exclude fields with None values.

  • exclude_unset (bool) – Whether to exclude fields that were not explicitly set. Useful for partial updates.

Returns:

A dictionary with model data ready for API submission.

Return type:

dict[str, Any]

Examples

>>> # Full representation including all fields
>>> data = doc.to_dict()
>>>
>>> # Only include fields that can be modified
>>> update_data = doc.to_dict(include_read_only=False)
>>>
>>> # Only include fields that have been explicitly set
>>> partial_data = doc.to_dict(exclude_unset=True)
update(**kwargs)[source]

Update this model with new values.

Update the model with the provided field values. In BaseModel, this simply calls update_locally without saving. Subclasses (like StandardModel) may implement automatic saving.

Parameters:

**kwargs (Any) – New field values to set on the model.

Return type:

None

Examples

>>> model.update(name="New Name", description="Updated description")
update_locally(*, from_db=None, skip_changed_fields=False, **kwargs)[source]

Update model attributes without triggering automatic save.

Update the model’s attributes with the provided values without sending changes to the server, regardless of the save_on_write setting. This is useful for local modifications or when applying server updates.

Parameters:
  • from_db (bool | None) – Whether the update is from the database. If True, resets the dirty tracking to consider the model clean after the update.

  • skip_changed_fields (bool) – Whether to skip updating fields that have unsaved changes. Useful when merging updates from the server with local changes.

  • **kwargs (Any) – Field values to update.

Raises:

ReadOnlyFieldError – If attempting to change a read-only field when from_db is False.

Return type:

None

Examples

>>> doc = client.documents.get(123)
>>> # Update without saving to server
>>> doc.update_locally(title="New Title", correspondent_id=5)
>>> # Update from server data
>>> doc.update_locally(from_db=True, **server_data)
class paperap.models.abstract.StandardModel(**data)[source]

Bases: BaseModel, ABC

Standard model for Paperless-ngx API objects with an ID field.

Extend BaseModel to include a unique identifier and additional functionality for API objects that require an ID. Most Paperless-ngx resources are represented by StandardModel subclasses.

This class adds functionality for: - Tracking whether an object is new or existing - Automatic saving of changes to the server - Refreshing data from the server - Synchronous and asynchronous save operations

id

Unique identifier for the model from Paperless-ngx.

_resource

Associated resource for API interactions.

Examples

StandardModel subclasses are typically accessed through the client:

>>> doc = client.documents.get(123)
>>> tag = client.tags.create(name="Important")
>>> correspondent = client.correspondents.all()[0]
Parameters:

data (Any)

class Meta(model)[source]

Bases: Meta

Metadata for the StandardModel.

Define metadata specific to StandardModel, including read-only fields and filtering parameters common to all standard Paperless-ngx resources.

read_only_fields

Fields that should not be modified, including the ‘id’ field which is set by the server.

supported_filtering_params

Common filtering parameters supported for all standard models, including id-based lookups.

Parameters:

model (type[_Self])

blacklist_filtering_params: ClassVar[set[str]] = {}
field_map: dict[str, str] = {}
filtering_disabled: ClassVar[set[str]] = {}
filtering_fields: ClassVar[set[str]] = {'_resource', 'id'}
read_only_fields: ClassVar[set[str]] = {'id'}
supported_filtering_params: ClassVar[set[str]] = {'id', 'id__in', 'limit'}
model: type[_Self]
name: str
__setattr__(name, value)[source]

Override attribute setting to automatically trigger save.

Intercept attribute assignments and trigger an automatic save operation if appropriate based on the save_on_write setting. This enables the “save on write” functionality that makes the model automatically sync changes to the server.

Parameters:
  • name (str) – Attribute name to set

  • value (Any) – New attribute value

Return type:

None

Notes

  • Private attributes (starting with ‘_’) never trigger autosave

  • Autosave only happens when model status is READY

  • Autosave is skipped for new models or when save_on_write is False

__str__()[source]

Human-readable string representation.

This method returns a string representation of the model, typically used for logging and debugging purposes.

Returns:

A string representation of the model.

Return type:

str

is_new()[source]

Check if this model represents a new (unsaved) object.

Determine if the model has been saved to the server by checking if it has a valid ID (non-zero). StandardModel implements this method by checking the id attribute.

Returns:

True if the model is new (not yet saved), False otherwise.

Return type:

bool

Examples

>>> doc = Document(title="Draft")  # No ID yet
>>> doc.is_new()
True
>>> saved_doc = client.documents.get(123)
>>> saved_doc.is_new()
False
model_config: ClassVar[ConfigDict] = {'arbitrary_types_allowed': True, 'extra': 'ignore', 'populate_by_name': True, 'use_enum_values': True, 'validate_assignment': True, 'validate_default': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

model_post_init(context: Any, /) None

We need to both initialize private attributes and call the user-defined model_post_init method.

Parameters:
Return type:

None

refresh()[source]

Refresh the model with the latest data from the server.

Retrieve the latest data for the model from the server and update the model instance with any changes. This is useful when you suspect the server data may have changed due to actions by other users or automated processes.

Returns:

True if the model data changed, False if the data is identical

or the refresh failed.

Return type:

bool

Raises:

ResourceNotFoundError – If the model is not found on the server (e.g., it was deleted remotely).

Examples

>>> doc = client.documents.get(123)
>>> # After some time or operations by other users
>>> doc.refresh()  # Update with latest data from server
property resource: StandardResource[Self]

Get the resource associated with this model.

Provide access to the StandardResource instance that handles API interactions for this model type, with support for ID-based operations.

Returns:

The resource instance for this model type.

Return type:

StandardResource[Self]

save(*, force=False)[source]

Save this model to the Paperless NGX server.

Send the current model state to the server, creating a new object or updating an existing one. This is a convenience method that calls save_sync.

Parameters:

force (bool) – Whether to force the save operation even if the model is not dirty or is already saving.

Returns:

True if the save was successful, False otherwise.

Return type:

bool

Examples

>>> doc = client.documents.get(123)
>>> doc.title = "New Title"
>>> doc.save()
>>>
>>> # Force save even if no changes
>>> doc.save(force=True)
save_async(*, force=False)[source]

Save this model instance asynchronously.

Send changes to the server in a background thread, allowing other operations to continue while waiting for the server response. The model will be updated with the server’s response when the save completes.

Parameters:

force (bool) – Whether to force the save operation even if the model is not dirty or is already saving.

Returns:

True if the save was successfully submitted to the background

thread, False otherwise (e.g., if there are no changes to save).

Return type:

bool

Examples

>>> doc = client.documents.get(123)
>>> doc.title = "New Title"
>>> # Continue execution immediately while save happens in background
>>> doc.save_async()
>>> # Do other work...
save_sync(*, force=False)[source]

Save this model instance synchronously.

Send changes to the server immediately and update the model when the server responds. This method blocks until the save operation is complete.

Parameters:

force (bool) – Whether to force the save operation even if the model is not dirty or is already saving.

Returns:

True if the save was successful, False otherwise.

Return type:

bool

Raises:

Examples

>>> doc = client.documents.get(123)
>>> doc.title = "New Title"
>>> success = doc.save_sync()
>>> print(f"Save {'succeeded' if success else 'failed'}")
update(**kwargs)[source]

Update this model with new values and save changes.

Update the model with the provided field values and automatically save the changes to the server if the model is not new. This method combines update_locally and save for convenience.

Parameters:

**kwargs (Any) – New field values to set on the model.

Return type:

None

Note

New (unsaved) instances will be updated locally but not saved automatically. Use create() to save new instances.

Examples

>>> doc = client.documents.get(123)
>>> doc.update(title="New Title", correspondent_id=5)
>>> # Changes are immediately saved to the server
id: int
class paperap.models.abstract.BaseQuerySet(resource, filters=None, _cache=None, _fetch_all=False, _next_url=None, _last_response=None, _iter=None, _urls_fetched=None)[source]

Bases: Iterable, Generic

A lazy-loaded, chainable query interface for Paperless-ngx resources.

Provides pagination, filtering, and caching functionality similar to Django’s QuerySet. Only fetches data when it’s actually needed, optimizing API requests and performance.

resource

The resource instance associated with the queryset.

filters

Dictionary of filters to apply to the API request.

_last_response

The last response received from the API.

_result_cache

List of model instances already fetched.

_fetch_all

Whether all results have been fetched.

_next_url

URL for the next page of results, if any.

_urls_fetched

List of URLs already fetched to prevent loops.

_iter

Current iterator over results, if any.

Examples

Basic usage:

>>> docs = client.documents()  # Returns a BaseQuerySet
>>> for doc in docs.filter(title__contains="invoice"):
...     print(doc.title)
Parameters:
  • resource (BaseResource[_Model, Self])

  • filters (dict[str, Any] | None)

  • _cache (list[_Model] | None)

  • _fetch_all (bool)

  • _next_url (str | None)

  • _last_response (ClientResponse)

  • _iter (Iterator[_Model] | None)

  • _urls_fetched (list[str] | None)

__bool__()[source]

Return True if the QuerySet has any results.

Calls exists() to check if any objects match the current filters.

Return type:

bool

Returns:

True if there are any objects matching the filters.

Examples

Check if any documents exist:

>>> if client.documents():
...     print("Documents found")
__contains__(item)[source]

Return True if the QuerySet contains the given object.

Checks if the given object is present in the queryset by comparing it with each object in the queryset.

Parameters:

item (Any) – The object to check for.

Return type:

bool

Returns:

True if the object is in the QuerySet.

Examples

Check if a document is in a queryset:

>>> doc = client.documents().get(123)
>>> if doc in client.documents().filter(title__contains="invoice"):
...     print("Document is an invoice")
__getitem__(key)[source]

Retrieve an item or slice of items from the QuerySet.

Supports both integer indexing and slicing, optimizing API requests when possible by using limit and offset parameters.

Parameters:

key (int | slice) – An integer index or slice object.

Return type:

_Model | list[_Model]

Returns:

A single object or list of objects.

Raises:

IndexError – If the index is out of range.

Examples

Get a single document by position:

>>> first_doc = client.documents()[0]

Get a slice of documents:

>>> recent_docs = client.documents().order_by('-created')[:10]
__init__(resource, filters=None, _cache=None, _fetch_all=False, _next_url=None, _last_response=None, _iter=None, _urls_fetched=None)[source]

Initialize a new BaseQuerySet.

Parameters:
  • resource (BaseResource[_Model, Self]) – The resource instance that will handle API requests.

  • filters (dict[str, Any] | None) – Initial filters to apply to the queryset.

  • _cache (list[_Model] | None) – Pre-populated result cache (internal use).

  • _fetch_all (bool) – Whether all results have been fetched (internal use).

  • _next_url (str | None) – URL for the next page of results (internal use).

  • _last_response (ClientResponse) – Last API response received (internal use).

  • _iter (Iterator[_Model] | None) – Current iterator over results (internal use).

  • _urls_fetched (list[str] | None) – List of URLs already fetched (internal use).

__iter__()[source]

Iterate over the objects in the QuerySet.

Implements lazy loading of results, fetching additional pages as needed when iterating through the queryset.

Return type:

Iterator[_Model]

Returns:

An iterator over the model instances.

Examples

Iterate through documents:

>>> for doc in client.documents():
...     print(doc.title)
__len__()[source]

Return the number of objects in the QuerySet.

Calls count() to determine the total number of objects matching the current filters.

Return type:

int

Returns:

The count of objects.

Examples

Get the number of documents:

>>> num_docs = len(client.documents())
>>> print(f"Total documents: {num_docs}")
all()[source]

Return a new QuerySet that copies the current one.

Creates a copy of the current queryset with the same filters. Often used to create a new queryset instance for method chaining.

Return type:

Self

Returns:

A copy of the current QuerySet.

Examples

Create a copy of a queryset:

>>> all_docs = client.documents().all()

Chain with other methods:

>>> recent_docs = client.documents().all().order_by('-created')
count()[source]

Return the total number of objects in the queryset.

Makes an API request if necessary to determine the total count of objects matching the current filters.

Return type:

int

Returns:

The total count of objects matching the filters.

Raises:

NotImplementedError – If the count cannot be determined from the API response.

Examples

Count all documents:

>>> total = client.documents().count()
>>> print(f"Total documents: {total}")

Count filtered documents:

>>> invoice_count = client.documents().filter(title__contains="invoice").count()
count_this_page()[source]

Return the number of objects on the current page.

Counts only the objects on the current page of results, without fetching additional pages. Useful for pagination displays.

Return type:

int

Returns:

The count of objects on the current page.

Raises:

NotImplementedError – If the current page count cannot be determined.

Examples

Get count of current page:

>>> page_count = client.documents().count_this_page()
>>> print(f"Items on this page: {page_count}")
exclude(**kwargs)[source]

Return a new QuerySet excluding objects with the given filters.

Parameters:

**kwargs (Any) – Filters to exclude, where keys are field names and values are excluded values. Supports the same lookup syntax as filter().

Return type:

Self

Returns:

A new QuerySet excluding objects that match the filters.

Examples

Get documents with any correspondent except ID 1:

>>> docs = client.documents().exclude(correspondent=1)

Exclude documents with specific words in title:

>>> docs = client.documents().exclude(title__contains="draft")
exists()[source]

Return True if the QuerySet contains any results.

Optimizes the API request by checking for at least one result rather than fetching all results.

Return type:

bool

Returns:

True if there are any objects matching the filters.

Examples

Check if any documents exist:

>>> if client.documents().exists():
...     print("Documents found")

Check if specific documents exist:

>>> has_invoices = client.documents().filter(title__contains="invoice").exists()
filter(**kwargs)[source]

Return a new QuerySet with the given filters applied.

Parameters:

**kwargs (Any) – Filters to apply, where keys are field names and values are desired values. Supports Django-style lookups like field__contains, field__in, etc.

Return type:

Self

Returns:

A new QuerySet with the additional filters applied.

Examples

Get documents with specific correspondent:

>>> docs = client.documents().filter(correspondent=1)

Filter with multiple conditions:

>>> docs = client.documents().filter(
...     title__contains="invoice",
...     created__gt="2023-01-01"
... )
filter_field_by_str(field, value, *, exact=True, case_insensitive=True)[source]

Filter a queryset based on a given string field.

Allows subclasses to easily implement custom filter methods for string fields with consistent behavior.

Parameters:
  • field (str) – The field name to filter by.

  • value (str) – The string value to filter against.

  • exact (bool) – Whether to filter by an exact match (True) or contains (False).

  • case_insensitive (bool) – Whether the filter should be case-insensitive.

Return type:

Self

Returns:

A new QuerySet instance with the filter applied.

Examples

Filter documents by title (case-insensitive exact match):

>>> docs = client.documents().filter_field_by_str('title', 'Invoice', exact=True)

Filter documents by title containing text (case-insensitive):

>>> docs = client.documents().filter_field_by_str('title', 'invoice', exact=False)
first()[source]

Return the first object in the QuerySet, or None if empty.

Optimizes the API request by limiting to a single result when possible.

Return type:

_Model | None

Returns:

The first object or None if no objects match.

Examples

Get the first document:

>>> first_doc = client.documents().first()
>>> if first_doc:
...     print(f"First document: {first_doc.title}")

Get the first document matching a filter:

>>> first_invoice = client.documents().filter(title__contains="invoice").first()
get(pk)[source]

Retrieve a single object from the API.

This base implementation raises NotImplementedError. Subclasses like StandardQuerySet implement this method for models with ID fields.

Parameters:

pk (Any) – The primary key (e.g., the id) of the object to retrieve.

Return type:

_Model

Returns:

A single object matching the query.

Raises:

Examples

Get document with ID 123:

>>> doc = client.documents().get(123)
last()[source]

Return the last object in the QuerySet, or None if empty.

Note

This method requires fetching all results to determine the last one, which may be inefficient for large result sets.

Return type:

_Model | None

Returns:

The last object or None if no objects match.

Examples

Get the last document:

>>> last_doc = client.documents().last()
>>> if last_doc:
...     print(f"Last document: {last_doc.title}")

Get the last document in a specific order:

>>> oldest_doc = client.documents().order_by('created').last()
none()[source]

Return an empty QuerySet.

Creates a queryset that will always return no results, which is useful for conditional queries.

Return type:

Self

Returns:

An empty QuerySet.

Examples

Create an empty queryset:

>>> empty_docs = client.documents().none()
>>> len(empty_docs)
0

Conditional query:

>>> if condition:
...     docs = client.documents().filter(title__contains="invoice")
... else:
...     docs = client.documents().none()
order_by(*fields)[source]

Return a new QuerySet ordered by the specified fields.

Parameters:

*fields (str) – Field names to order by. Prefix with ‘-’ for descending order. Multiple fields can be specified for multi-level sorting.

Return type:

Self

Returns:

A new QuerySet with the ordering applied.

Examples

Order documents by title ascending:

>>> docs = client.documents().order_by('title')

Order by multiple fields (created date descending, then title ascending):

>>> docs = client.documents().order_by('-created', 'title')
resource: BaseResource[TypeVar(_Model, bound= BaseModel), Self]
filters: dict[str, Any]
class paperap.models.abstract.StandardQuerySet(resource, filters=None, _cache=None, _fetch_all=False, _next_url=None, _last_response=None, _iter=None, _urls_fetched=None)[source]

Bases: BaseQuerySet, Generic

A queryset for StandardModel instances with ID fields.

Extends BaseQuerySet to provide additional functionality specific to models with standard fields like ‘id’, including direct lookups by ID, bulk operations, and specialized filtering methods.

resource

The StandardResource instance associated with the queryset.

Examples

Get documents by ID:

>>> doc = client.documents().get(123)

Filter documents by ID:

>>> docs = client.documents().id([1, 2, 3])

Perform bulk operations:

>>> client.documents().filter(title__contains="draft").delete()
Parameters:
  • resource (BaseResource[_Model, Self])

  • filters (dict[str, Any] | None)

  • _cache (list[_Model] | None)

  • _fetch_all (bool)

  • _next_url (str | None)

  • _last_response (ClientResponse)

  • _iter (Iterator[_Model] | None)

  • _urls_fetched (list[str] | None)

__contains__(item)[source]

Return True if the QuerySet contains the given object.

Checks if an object with the same ID is in the queryset. Can accept either a model instance or an integer ID.

Note

This method only ensures a match by ID, not by full object equality. This is intentional, as the object may be outdated or not fully populated.

Parameters:

item (Any) – The object or ID to check for.

Return type:

bool

Returns:

True if an object with the matching ID is in the QuerySet.

Examples

Check if a document is in a queryset:

>>> doc = client.documents().get(123)
>>> if doc in client.documents().filter(title__contains="invoice"):
...     print("Document is an invoice")

Check if a document ID is in a queryset:

>>> if 123 in client.documents().filter(title__contains="invoice"):
...     print("Document 123 is an invoice")
get(pk)[source]

Retrieve a single object from the API by its ID.

First checks the result cache for an object with the given ID, then falls back to making a direct API request if not found.

Parameters:

pk (int) – The ID of the object to retrieve.

Return type:

_Model

Returns:

A single object matching the ID.

Raises:

ObjectNotFoundError – If no object with the given ID exists.

Examples

Get document with ID 123:

>>> doc = client.documents().get(123)
>>> print(f"Retrieved: {doc.title}")
id(value)[source]

Filter models by ID.

Provides a convenient way to filter objects by their ID or a list of IDs.

Parameters:

value (int | list[int]) – The ID or list of IDs to filter by.

Return type:

Self

Returns:

Filtered QuerySet containing only objects with the specified ID(s).

Examples

Get document with ID 123:

>>> doc = client.documents().id(123).first()

Get multiple documents by ID:

>>> docs = client.documents().id([123, 456, 789])
>>> for doc in docs:
...     print(doc.title)
resource: StandardResource[TypeVar(_Model, bound= StandardModel), Self]
filters: dict[str, Any]
class paperap.models.abstract.StatusContext(model, new_status)[source]

Bases: object

Manage model status changes safely with proper resource locking.

Provides a mechanism to temporarily change the status of a model while ensuring the previous status is restored upon completion. Handles acquisition and release of save locks to prevent concurrent modifications that could lead to data inconsistency.

When used as a context manager, StatusContext will: 1. Optionally acquire a save lock if the new status is SAVING 2. Store the model’s current status 3. Set the model’s status to the new status 4. Execute the context body 5. Restore the original status when exiting 6. Release any acquired locks

model

The model whose status is being managed.

Type:

BaseModel

new_status

The status to set within the context.

Type:

ModelStatus

previous_status

The status before entering the context.

Type:

ModelStatus | None

Examples

Using StatusContext in a model method:

>>> class SomeModel(BaseModel):
...     def perform_update(self):
...         with StatusContext(self, ModelStatus.UPDATING):
...             # Perform an update operation
...             self._update_remote_data()

Using StatusContext with error handling:

>>> try:
...     with StatusContext(model, ModelStatus.SAVING):
...         # Attempt to save the model
...         model._save_to_api()
... except APIError:
...     # The model's status will be restored even if an error occurs
...     print("Failed to save model")
Parameters:
__enter__()[source]

Enter the context, updating the model’s status.

Performs the following operations: 1. Acquires the save lock if the new status is ModelStatus.SAVING 2. Stores the model’s current status for later restoration 3. Sets the model’s status to the new status :rtype: None

Note

This method intentionally returns None instead of self to prevent direct access to the context manager, ensuring proper status reversion.

__exit__(exc_type, exc_value, traceback)[source]

Exit the context, restoring the model’s previous status.

Performs the following cleanup operations: 1. Restores the model’s status to its previous value 2. Sets status to ModelStatus.ERROR if no previous status was recorded 3. Releases the save lock if it was acquired

Ensures proper cleanup even if an exception occurred within the context.

Parameters:
  • exc_type (type[BaseException] | None) – The exception type, if any.

  • exc_value (BaseException | None) – The exception value, if any.

  • traceback (Iterable[Any]) – The traceback information, if any.

Return type:

None

Note

This method does not suppress exceptions; they will propagate normally.

Examples

Handling exceptions while using StatusContext:

>>> try:
...     with StatusContext(model, ModelStatus.PROCESSING):
...         raise ValueError("Something went wrong")
... except ValueError:
...     # The model's status will be restored before this exception handler runs
...     print("Error occurred, but model status was properly restored")
__init__(model, new_status)[source]

Initialize the StatusContext with a model and target status.

Parameters:
  • model (BaseModel) – The model whose status will be temporarily changed.

  • new_status (ModelStatus) – The status to set while in this context.

property model: BaseModel

Get the model associated with this context.

Returns:

The model whose status is being managed.

Return type:

BaseModel

property new_status: ModelStatus

Get the status that will be set within this context.

Returns:

The status to set within the context.

Return type:

ModelStatus

property previous_status: ModelStatus | None

Get the status that was set before entering this context.

Returns:

The previous status, or None if not yet entered.

Return type:

ModelStatus | None

save_lock()[source]

Acquire the save lock for the model.

Acquires the model’s save lock to ensure that no other operations can modify the model while the status is being updated. The lock is implemented as a threading.RLock to allow reentrant locking from the same thread. :rtype: None

Note

This method sets the internal _save_lock_acquired flag to True when successful, which is used to determine if unlock is needed later.

save_unlock()[source]

Release the save lock for the model.

Releases the model’s save lock if it was acquired by this context manager, allowing other operations to modify the model. The lock is only released if it was previously acquired by this specific StatusContext instance. :rtype: None

Note

This method checks the internal _save_lock_acquired flag to ensure it only releases locks that it has acquired.

class paperap.models.abstract.SupportsBulkActions(*args, **kwargs)[source]

Bases: StandardQuerySetProtocol, Protocol

bulk_action(action, **kwargs)[source]

Perform a bulk action on all objects in the queryset.

Fetches all IDs in the queryset and passes them to the resource’s bulk_action method, allowing operations to be performed on multiple objects in a single API request.

Parameters:
  • action (str) – The action to perform (e.g., “delete”, “merge”).

  • **kwargs (Any) – Additional parameters for the action.

Return type:

TypeAliasType

Returns:

The API response containing results of the bulk action.

Raises:

NotImplementedError – If the resource doesn’t support bulk actions.

Examples

Delete all documents with “draft” in the title:

>>> client.documents().filter(title__contains="draft").bulk_action("delete")

Merge documents with custom parameters:

>>> client.documents().filter(correspondent_id=5).bulk_action(
...     "merge",
...     metadata_document_id=123
... )
bulk_assign_correspondent(correspondent_id)[source]

Assign a correspondent to all objects in the queryset.

Sets the correspondent for all objects in the queryset to the specified correspondent ID.

Parameters:

correspondent_id (int) – Correspondent ID to assign.

Return type:

TypeAliasType

Returns:

The API response containing results of the operation.

Raises:

NotImplementedError – If the resource doesn’t support bulk correspondent assignment.

Examples

Set correspondent for all invoices:

>>> client.documents().filter(title__contains="invoice").bulk_assign_correspondent(5)
bulk_assign_document_type(document_type_id)[source]

Assign a document type to all objects in the queryset.

Sets the document type for all objects in the queryset to the specified document type ID.

Parameters:

document_type_id (int) – Document type ID to assign.

Return type:

TypeAliasType

Returns:

The API response containing results of the operation.

Raises:

NotImplementedError – If the resource doesn’t support bulk document type assignment.

Examples

Set document type for all invoices:

>>> client.documents().filter(title__contains="invoice").bulk_assign_document_type(3)
bulk_assign_owner(owner_id)[source]

Assign an owner to all objects in the queryset.

Sets the owner for all objects in the queryset to the specified owner ID.

Parameters:

owner_id (int) – Owner ID to assign.

Return type:

TypeAliasType

Returns:

The API response containing results of the operation.

Raises:

NotImplementedError – If the resource doesn’t support bulk owner assignment.

Examples

Set owner for all personal documents:

>>> client.documents().filter(title__contains="personal").bulk_assign_owner(1)
bulk_assign_storage_path(storage_path_id)[source]

Assign a storage path to all objects in the queryset.

Sets the storage path for all objects in the queryset to the specified storage path ID.

Parameters:

storage_path_id (int) – Storage path ID to assign.

Return type:

TypeAliasType

Returns:

The API response containing results of the operation.

Raises:

NotImplementedError – If the resource doesn’t support bulk storage path assignment.

Examples

Set storage path for all tax documents:

>>> client.documents().filter(title__contains="tax").bulk_assign_storage_path(2)
bulk_assign_tags(tag_ids, remove_existing=False)[source]

Assign tags to all objects in the queryset.

Adds the specified tags to all objects in the queryset.

Parameters:
  • tag_ids (list[int]) – List of tag IDs to assign.

  • remove_existing (bool) – If True, remove existing tags before assigning new ones. If False (default), add the new tags to any existing tags.

Return type:

TypeAliasType

Returns:

The API response containing results of the operation.

Raises:

NotImplementedError – If the resource doesn’t support bulk tag assignment.

Examples

Add tags to all invoices:

>>> client.documents().filter(title__contains="invoice").bulk_assign_tags([1, 2])

Replace all tags on tax documents:

>>> client.documents().filter(title__contains="tax").bulk_assign_tags(
...     [5, 6],
...     remove_existing=True
... )
bulk_update(**kwargs)[source]

Update all objects in the queryset with the given values.

Allows updating multiple objects with the same field values in a single API request.

Parameters:

**kwargs (Any) – Fields to update and their new values.

Return type:

TypeAliasType

Returns:

The API response containing results of the update operation.

Raises:

NotImplementedError – If the resource doesn’t support bulk updates.

Examples

Update the correspondent for all documents with “invoice” in the title:

>>> client.documents().filter(title__contains="invoice").bulk_update(
...     correspondent=5,
...     document_type=3
... )
delete()[source]

Delete all objects in the queryset.

This is a convenience method that calls bulk_action(“delete”).

Return type:

TypeAliasType

Returns:

The API response containing results of the delete operation.

Examples

Delete all documents with “draft” in the title:

>>> client.documents().filter(title__contains="draft").delete()

Delete old documents:

>>> from datetime import datetime, timedelta
>>> one_year_ago = (datetime.now() - timedelta(days=365)).isoformat()
>>> client.documents().filter(created__lt=one_year_ago).delete()

Submodules