Coverage for documents/management/commands/document_index.py: 0%
37 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-10 09:07 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-10 09:07 +0000
1import logging
3from django.conf import settings
4from django.db import transaction
6from documents.management.commands.base import PaperlessCommand
7from documents.models import Document
8from documents.search import get_backend
9from documents.search import needs_rebuild
10from documents.search import reset_backend
11from documents.search import wipe_index
13logger = logging.getLogger("paperless.management.document_index")
16class Command(PaperlessCommand):
17 """
18 Django management command for search index operations.
20 Provides subcommands for reindexing documents and optimizing the search index.
21 Supports conditional reindexing based on schema version and language changes.
22 """
24 help = "Manages the document index."
26 supports_progress_bar = True
27 supports_multiprocessing = False
29 def add_arguments(self, parser):
30 super().add_arguments(parser)
31 parser.add_argument("command", choices=["reindex", "optimize"])
32 parser.add_argument(
33 "--recreate",
34 action="store_true",
35 default=False,
36 help="Wipe and recreate the index from scratch (only used with reindex).",
37 )
38 parser.add_argument(
39 "--if-needed",
40 action="store_true",
41 default=False,
42 help=(
43 "Skip reindex if the index is already up to date. "
44 "Checks schema version and search language sentinels. "
45 "Safe to run on every startup or upgrade."
46 ),
47 )
48 parser.add_argument(
49 "--heap-size-mb",
50 type=int,
51 default=None,
52 help=(
53 "Tantivy writer memory budget in MB for a full reindex "
54 "(split across writer threads). Defaults to 512MB; lower "
55 "this on memory-constrained hosts. Larger values buffer "
56 "more documents before flushing a segment, deferring "
57 "merge work rather than avoiding it."
58 ),
59 )
61 def handle(self, *args, **options):
62 with transaction.atomic():
63 if options["command"] == "reindex":
64 if options.get("if_needed") and not needs_rebuild(settings.INDEX_DIR):
65 self.stdout.write("Search index is up to date.")
66 return
67 if options.get("recreate"):
68 wipe_index(settings.INDEX_DIR)
70 documents = Document.objects.select_related(
71 "correspondent",
72 "document_type",
73 "storage_path",
74 "owner",
75 ).prefetch_related(
76 "tags",
77 "notes__user",
78 "custom_fields__field",
79 "versions",
80 "barcodes",
81 "versions__barcodes",
82 )
83 total = documents.count()
84 rebuild_kwargs = {}
85 if options.get("heap_size_mb") is not None:
86 rebuild_kwargs["writer_heap_bytes"] = (
87 options["heap_size_mb"] * 1_000_000
88 )
89 get_backend().rebuild(
90 documents,
91 iter_wrapper=lambda pairs: self.track(
92 pairs,
93 description="Indexing documents...",
94 total=total,
95 ),
96 **rebuild_kwargs,
97 )
98 reset_backend()
100 elif options["command"] == "optimize":
101 logger.info(
102 "document_index optimize is a no-op — Tantivy manages "
103 "segment merging automatically.",
104 )