Migrating from v5 to v6
v6 is almost a full rewrite of pypaperless. Three things drove it:
- Models were too tightly coupled to the HTTP layer. In v5, every model instance carried a reference to the client and called it directly. That made testing awkward and sharing models between contexts impossible. v6 models are plain data — all I/O goes through services.
- No runtime type safety. v5 used dataclasses with manual dict conversion, so bad API responses would silently produce wrong values. v6 uses Pydantic v2, which validates every response at parse time.
aiohttpgot removed.httpxis modern, has a cleaner sync/async API and a built-in mock transport that makes testing easier.
Quick checklist
| # | What to change | Section |
|---|---|---|
| 1 | Replace aiohttp / yarl with httpx |
Dependencies |
| 2 | Update Paperless(...) constructor arguments |
Initializing the client |
| 3 | Replace reduce() with filter() — different call pattern |
Iteration and filtering |
| 4 | Move update(), delete(), save() from model instances to services |
CRUD |
| 5 | Replace request_permissions = True with with_permissions() |
Permissions |
| 6 | Replace doc.get_download(), get_metadata(), etc. |
Document convenience methods removed |
| 7 | Note deletion: note.delete() → service call |
Document notes |
| 8 | generate_api_token() custom-client argument renamed |
Token generation |
| 9 | New: paperless.profile, paperless.trash, paperless.documents.history |
New resources |
Dependencies
Replace aiohttp and yarl with httpx:
Initializing the client
The constructor signature changed. session was renamed to client, and request_args was removed.
import httpx
paperless = Paperless("http://localhost:8000", "mytoken")
paperless = Paperless("http://localhost:8000", "mytoken", client=my_httpx_client)
# SSL / TLS customization — pass a pre-configured httpx.AsyncClient
client = httpx.AsyncClient(verify=False) # or verify="/path/to/cert.pem"
paperless = Paperless("http://localhost:8000", "mytoken", client=client)
The url parameter no longer accepts yarl.URL objects — pass a plain string.
Token generation
generate_api_token() is a static method on Paperless. The optional custom-client argument was renamed from session to client:
Iteration and filtering
reduce() was replaced by filter(). The key difference: the context manager now yields the service object, so you iterate over ctx instead of reusing the outer service name.
The pages() method was removed. Use as_list() or iterate directly.
CRUD: update, delete, save
In v5, CRUD operations lived on model instances. In v6 they live on the service.
update()
delete()
This applies to every resource — correspondents, tags, custom fields, etc.
save() — creating new resources
For all other resources (correspondents, tags, …):
Permissions
The mutable request_permissions setter was replaced by a with_permissions() context manager. The flag is now automatically reset on exit.
Document convenience methods removed
The shortcut methods on Document instances that delegated back to the helper were removed. Call the service directly.
| v5 (on model instance) | v6 (on service) |
|---|---|
await doc.get_download() |
await paperless.documents.download(doc.id) |
await doc.get_download(original=True) |
await paperless.documents.download(doc.id, original=True) |
await doc.get_metadata() |
await paperless.documents.metadata(doc.id) |
await doc.get_preview() |
await paperless.documents.preview(doc.id) |
await doc.get_thumbnail() |
await paperless.documents.thumbnail(doc.id) |
await doc.get_suggestions() |
await paperless.documents.suggestions(doc.id) |
Document notes
Note deletion moved from the model to the service, consistent with the general CRUD pattern.
Creating a new note:
New resources
Two new top-level services were added.
paperless.profile
Access the currently authenticated user's own profile:
See Profile for details.
paperless.trash
Browse and manage soft-deleted documents:
async for doc in paperless.trash:
print(doc.id, doc.title, doc.deleted_at)
await paperless.trash.restore([42, 43])
await paperless.trash.empty() # empties entire trash
The Document.is_deleted property returns True for documents retrieved from the trash.
See Trash for details.
paperless.documents.history
A new document audit-log sub-service:
entries = await paperless.documents.history(42)
for entry in entries:
print(entry.actor, entry.timestamp, entry.action)
Error handling
PaperlessConnectionError is now raised for httpx.ConnectError rather than aiohttp.ClientConnectorError. If you catch library-specific exceptions, update accordingly:
```