Commit Graph
2365 Commits
Author SHA1 Message Date
stumpylog 5d0fb476cf Fix: narrow zstd compression level to the conventional -22..22 range
The raw library bounds (-131072, 22) come from an internal zstd
implementation constant (-ZSTD_TARGETLENGTH_MAX), not a meaningful
distinct level — deeper negative values than -22 buy nothing over -22
in practice, and the zstd CLI/community convention only uses -22..22.
Exposing the raw range via --zip-compression-level would let a user
pass a number like -50000 that "validates" but means nothing.
2026-07-23 15:42:57 -07:00
stumpylog 9558ede894 Feature: importer rejects archives with unreadable compression 2026-07-23 15:04:55 -07:00
stumpylog 437a219658 Test: add GIVEN/WHEN/THEN docstrings to compression tests
Matches the project's existing test docstring convention.
2026-07-23 14:56:14 -07:00
stumpylog 396c0b539c Test: assert zip-compression flag resolves to the right sink constant
test_zip_lzma_compression_round_trips / test_default_zip_uses_deflate
built a real zip and read compress_type back off it — that only
reconfirms zipfile.ZipFile() honors its own compression= kwarg
(ZipExportSink's own tests already cover that forwarding). What this
command owns is resolving the --zip-compression string to the right
zipfile constant; assert that resolution directly against the mocked
ZipExportSink construction call instead. Drops the sample-doc copytree
setup those tests needed only to build a real archive.
2026-07-23 14:35:43 -07:00
stumpylog 50dec14653 Test: assert compression forwarding via call args, not a real archive
test_compression_method_is_applied_to_file_entries built a real zip and
read back compress_type — but that only confirms zipfile.ZipFile()
honors its own compression= kwarg, which is documented stdlib behavior,
not something our code could get wrong. ZipExportSink's only
responsibility is forwarding compression/compresslevel unchanged to
ZipFile(); assert that directly against the mocked constructor call.
2026-07-23 14:26:01 -07:00
stumpylog cc4917efec Test: remove test asserting Python's own compression behavior
test_compressing_method_beats_stored asserted that DEFLATED produces a
smaller archive than STORED — that's zlib's job, not ours.
test_compression_method_is_applied_to_file_entries already covers what
our code is actually responsible for: threading the requested
compression method through to the zip entry's compress_type.
2026-07-23 14:06:37 -07:00
stumpylog 672fc6a0d4 Feature: add --zip-compression and --zip-compression-level flags 2026-07-23 14:02:41 -07:00
stumpylog 943d322196 Feature: ZipExportSink accepts compression method and level 2026-07-23 13:52:55 -07:00
stumpylog 4d956fa13b Feature: add export compression policy module 2026-07-23 13:27:06 -07:00
stumpylog 581fbab8f3 Polish: atomic zip commit via Path.replace, widen sink params to ExportSink
Path.rename() raises FileExistsError on Windows when the destination
already exists; Path.replace() is atomic cross-platform. Also widen
document_exporter's sink parameters from the concrete
DirectoryExportSink | ZipExportSink union to the ExportSink ABC, so a
future sink implementation is a pure addition rather than requiring
every call site's annotation to change.
2026-07-23 12:59:27 -07:00
stumpylog 7044d8ae5f Refactor: de-duplicate BLAKE2b compare and simplify zip dir-marker loop
DirectoryExportSink.add_json and _commit_streamed_file each inlined the
same hash-and-compare logic; extracted _content_unchanged(). Replaced
ZipExportSink._ensure_dirs's repeated slice/join with a prefix
accumulator and hoisted the _zip-is-open assertion out of the loop. No
behavioral change.
2026-07-23 12:27:15 -07:00
stumpylog 5e21ba891d Fix: annotate ExportSink.stream's return type for pyrefly
The abstract method had no return annotation, so pyrefly inferred -> None
and flagged both DirectoryExportSink.stream and ZipExportSink.stream as
incompatible overrides.
2026-07-23 12:20:29 -07:00
stumpylog c2c30541bb Test: guard --zip combined with --compare-* flags 2026-07-23 12:04:01 -07:00
stumpylogandClaude Sonnet 5 1ea9775ab4 Refactor: route document_exporter through ExportSink, direct-to-zip
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-23 11:53:45 -07:00
stumpylog 02b97fbaaf Fix: de-duplicate source_file fixture across sink test classes
TestDirectoryExportSink and TestZipExportSink each defined an
identical source_file fixture; hoist it to module scope.
2026-07-22 15:38:55 -07:00
stumpylog 0e538acdef Feature: add ZipExportSink with atomic finalize and manifest spooling 2026-07-22 15:36:50 -07:00
stumpylog 7f58d16758 Fix: make ExportSink a real ABC per design spec
The implementation plan for Task 2 diverged from the design spec
(export-sink-architecture-design.md), leaving ExportSink as a plain
class with NotImplementedError bodies instead of the specified
AbstractContextManager subclass. Use abc.ABC + @abstractmethod so a
concrete sink missing a required method fails at instantiation
rather than at first call.
2026-07-22 15:07:24 -07:00
stumpylog 344414bb47 Feature: add ExportSink ABC and DirectoryExportSink 2026-07-22 14:57:48 -07:00
stumpylog 8263f75643 Feature: add export package with StreamingManifestWriter and _dumps 2026-07-22 14:34:55 -07:00
81795bc93a Security (beta): enforce current permissions in autocomplete (#13188)
Co-authored-by: stumpylog <797416+stumpylog@users.noreply.github.com>
2026-07-22 08:07:43 -07:00
shamoonandGitHub 8aab34ea73 Security (beta): two small permission check fixes (#13186) 2026-07-21 16:50:28 +00:00
8404198ec8 Fix (beta): short-circuit ObjectOwnedOrGrantedPermissionsFilter for superusers (#13183)
Mirrors the existing short-circuit already present in its sibling class,
ObjectOwnedPermissionsFilter, which this one lacked.

Benchmarked as a superuser against a 100k-document corpus: no measurable
difference. django-guardian's own get_objects_for_user() (called via
super().filter_queryset()) already returns the queryset unrestricted for
superusers before this change, so the guardian permission tables were
never actually queried for superusers either way. This change only skips
building a redundant 3-way OR of conditions that are all subsets of the
already-unrestricted queryset -- worth keeping for clarity/consistency
with the sibling filter, not as a performance claim.

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-21 07:31:58 -07:00
d8d8872414 Fix (beta): compute num_notes via a subquery instead of Count()+distinct() (#13182)
DocumentViewSet.get_queryset() annotated num_notes via a LEFT JOIN to
documents_note plus Count(), which requires the database to aggregate
every matching document's note count before it can even sort or paginate
the result -- on every list request, not just filtered ones.

Switched to the same correlated-subquery pattern already used two lines
above for effective_content (Subquery + OuterRef), which Django compiles
to portable SQL across sqlite/postgresql/mariadb rather than a join-based
aggregate.

Benchmarked against a 100k synthetic document corpus (Postgres): the
plain document list request dropped from ~2.2-2.4s to ~0.9-1.0s. Note
that a separate, larger cost remains in the same queryset's plain
.distinct() call, which still forces a full sort over every matching
row regardless of this fix -- tracked separately, not addressed here.

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-20 13:59:44 -07:00
shamoonandGitHub ff609c2987 Change: update root modified timestamp on version changes (#13170) 2026-07-20 15:45:24 +00:00
Trenton HandGitHub 2b784e709b Fix: prevent tag assignment from reverting other pending workflow assignments (#13178)
apply_assignment_to_document() mutated tags directly on the shared document instance via add_nested_tags(), whose m2m_changed signal triggers update_filename_and_move_files() -> instance.refresh_from_db(), discarding any not-yet-saved fields (e.g. storage_path) staged by an earlier-ordered action in the same workflow. Apply tag changes to a freshly-fetched instance instead, matching the pattern already used in apply_removal_to_document().
2026-07-20 08:22:30 -07:00
Trenton HandGitHub c9716252f0 Performance: Add DB indexes for common query/sort patterns (#13167)
Adds indexes on Document.checksum, Document.page_count, and a composite
(owner, created) index on Document, plus composite (field, value_*)
indexes on CustomFieldInstance for each typed value column.

Benchmarked against a 100k-document / 200k-custom-field-instance SQLite
dataset: Document.checksum lookups ~69x faster, page_count sort ~40x,
owner+created list queries ~6x. CustomFieldInstance composite indexes
show large gains (10-24x) on realistic narrow-selectivity queries
matching the workflow scheduler and custom-field search code paths.
2026-07-20 13:12:54 +00:00
shamoon 5cf9152a40 Merge branch 'dev' into beta 2026-07-19 20:20:48 -07:00
4e52dd5710 Fix: cache per-request effective-document resolution for thumb/metadata/preview (#13166)
Django's condition() decorator invokes etag_func and last_modified_func
separately, and the view itself may resolve again -- each call to
resolve_effective_document_by_pk() was redoing the same root/version
DB lookups. Memoize the resolution on the request object so a single
thumb/metadata/preview request resolves the effective document once
instead of up to three times.

Related to paperless-ngx/paperless-ngx#13161.

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-19 18:04:16 -07:00
shamoonandGitHub 4f6d9fa93f Fix: dont allow archive version for share links when one doesnt exist (#13163) 2026-07-19 20:54:58 +00:00
Trenton HandGitHub 255c214d0b Fix: avoid HAVING clause conflicts in custom field query filter on MariaDB (#13143) 2026-07-17 13:44:58 -07:00
df1ddb15cc Performance: Tantivy indexing optimization (#13053)
* Tantivy: get permissions by chunks

-40% indexing time compared to previous commit

* Make progress bar process one by one with chunk

-15% indexing time compared to previous commit

* Prefetch FK + iterate over chunk from SQL

Prefetch additional needed data (note user, custom field content)

-20% indexing time compared to previous commit

* Reindex: increase Tantivy heap size from 128 to 512MB

Gains probably vary depending on the machine,
but it seems a sweet spot compatible with low-end hardware.

* Reindex: optimization on permission fetching and autocomplete word set

-10% indexing time compared to previous commit

* Autocomplete analyzer python->rust

Splits words with underscore compared to the python analyzer.
E.g.: "blue_print" -> ["blue", "print"]
It can still be found with the "blue_print" keyword,
as the search string is also split in two words.

-50% indexing time compared to previous commit (indexing is twice faster!)

* Index bigram for CJK content only

Inedxing time slightly longer (~3%),
but since the non-CJK content is not indexed,
bigram searchs will be slightly optimized.

* Fix group-based view_document permissions missing from bulk rebuild

_bulk_get_viewer_ids only queried UserObjectPermission, dropping the
group-permission expansion that get_users_with_perms(with_group_users=True)
performs for the non-batched per-document indexing path. A user who could
only see a document via group membership would lose search access to it
after any full reindex.

Also query GroupObjectPermission and expand group membership to user ids,
matching the existing single-document behavior.

* Yield (document, viewer_ids) pairs from _DocumentViewerStream

Previously _DocumentViewerStream.__iter__ yielded plain Document objects
while the matching viewer ids were exposed through a separate mutable
attribute (viewer_ids_by_pk), overwritten each time the generator crossed
a chunk boundary. rebuild() read that attribute out-of-band per document.

This only worked because the current iter_wrapper (a plain progress-bar
passthrough) happens to consume the stream in strict lock-step with no
lookahead. Any wrapper that buffers, batches, or reorders would silently
pair a document with the wrong chunk's viewer ids. Yield the pair directly
so the association travels with the document regardless of how iter_wrapper
consumes the stream, and drop the now-unneeded viewer_ids_by_pk attribute.

* Add --heap-size-mb CLI arg to document_index reindex

writer_heap_bytes was hardcoded at 512MB with no way to tune it. Expose it
as a manual-rebuild-only CLI arg rather than a settings/env var, per review
feedback, so lower-memory hosts can reduce it without a wider config
surface. Defaults to unset so TantivyBackend.rebuild's own default stays
the single source of truth.

---------

Co-authored-by: stumpylog <797416+stumpylog@users.noreply.github.com>
2026-07-17 11:33:09 -07:00
shamoon 19960cc4fa Merge branch 'dev' into beta 2026-07-10 00:44:47 -07:00
shamoonandGitHub f244442c65 Fixhancement: dont assign empty title in workflow with broken template (#13112) 2026-07-09 08:35:05 -07:00
shamoonandGitHub fce55a1609 Fix: use latest document version for bulk download (#13103) 2026-07-07 10:55:40 -07:00
shamoonandGitHub 1dabd2601d Fix: handle relative date with comma query (#13087) 2026-07-03 12:44:33 -07:00
shamoon 5b8fbdcec7 Merge branch 'dev' into beta 2026-06-27 15:56:20 -07:00
67972a0740 Fix (beta): fix sanity checker affected document counts (#13052)
Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
2026-06-27 15:55:38 -07:00
shamoonandGitHub b33d11778a Fix: change tantivy token limit to title field max length (#13046) 2026-06-23 08:02:26 -07:00
shamoonandGitHub e75946847e Fix: include last-modified in doc etag (#13044) 2026-06-22 18:18:57 -07:00
shamoonandGitHub 262183e848 Enhancement (beta): support LLM timeout config (#13002) 2026-06-18 08:35:11 -07:00
Trenton HandGitHub b8f10269a7 Fix(beta): Workaround a Tantivy panic in more like this searching (#13026) 2026-06-17 15:43:48 +00:00
Trenton HandGitHub ad1b54ce88 Fix (beta): Catch consumer files created during watcher re-creations (#13013) 2026-06-15 19:23:54 -07:00
f4fa916579 Fix (beta): restore v2 (Whoosh) advanced-search query compatibility (#13010)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 15:32:44 -07:00
shamoonandGitHub 75f0c4c92e Fix (beta): retry celery ping and report warning on no response (#13012) 2026-06-15 15:05:43 -07:00
Trenton HandGitHub 8ed4bf2011 Fix: Apply unicode normalization to all paths and path components (#12993) 2026-06-13 12:45:54 +00:00
shamoonandGitHub fb3816486c Fix (beta): avoid DRF update calling save on all fields (#12992) 2026-06-12 11:14:26 -07:00
Trenton HandGitHub f188d308eb Fix: health-check pooled DB connections and close the pool on worker shutdown (#12977) 2026-06-11 05:49:10 -07:00
shamoonandGitHub c3459d8f62 Fix (beta): move task filtering to backend fully (#12956) 2026-06-07 22:45:15 +00:00
shamoonandGitHub 6f8e39c2e0 Fix: avoid unnecessary creating new PDF with pw removal workflow (#12948) 2026-06-07 20:30:08 +00:00
eb292baa69 Enhancement (beta): Switch the AI vector store to LanceDB (#12944)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: shamoon <shamoon@users.noreply.github.com>
2026-06-07 11:31:26 -07:00