mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2026-01-26 22:49:01 -06:00
Compare commits
5 Commits
chore/pyte
...
feature-au
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
496a4035cd | ||
|
|
761044c0d3 | ||
|
|
1b7e4cc286 | ||
|
|
6997a2ab8b | ||
|
|
f82f31f383 |
@@ -114,16 +114,15 @@ testing = [
|
|||||||
"daphne",
|
"daphne",
|
||||||
"factory-boy~=3.3.1",
|
"factory-boy~=3.3.1",
|
||||||
"imagehash",
|
"imagehash",
|
||||||
"pytest~=9.0.0",
|
"pytest~=8.4.1",
|
||||||
"pytest-cov~=7.0.0",
|
"pytest-cov~=7.0.0",
|
||||||
"pytest-django~=4.11.1",
|
"pytest-django~=4.11.1",
|
||||||
"pytest-env~=1.2.0",
|
"pytest-env",
|
||||||
"pytest-httpx",
|
"pytest-httpx",
|
||||||
"pytest-mock~=3.15.1",
|
"pytest-mock",
|
||||||
#"pytest-randomly~=4.0.1",
|
"pytest-rerunfailures",
|
||||||
"pytest-rerunfailures~=16.1",
|
|
||||||
"pytest-sugar",
|
"pytest-sugar",
|
||||||
"pytest-xdist~=3.8.0",
|
"pytest-xdist",
|
||||||
]
|
]
|
||||||
|
|
||||||
lint = [
|
lint = [
|
||||||
@@ -261,15 +260,11 @@ write-changes = true
|
|||||||
ignore-words-list = "criterias,afterall,valeu,ureue,equest,ure,assertIn,Oktober,commitish"
|
ignore-words-list = "criterias,afterall,valeu,ureue,equest,ure,assertIn,Oktober,commitish"
|
||||||
skip = "src-ui/src/locale/*,src-ui/pnpm-lock.yaml,src-ui/e2e/*,src/paperless_mail/tests/samples/*,src/documents/tests/samples/*,*.po,*.json"
|
skip = "src-ui/src/locale/*,src-ui/pnpm-lock.yaml,src-ui/e2e/*,src/paperless_mail/tests/samples/*,src/documents/tests/samples/*,*.po,*.json"
|
||||||
|
|
||||||
[tool.pytest]
|
[tool.pytest.ini_options]
|
||||||
minversion = "9.0"
|
minversion = "8.0"
|
||||||
pythonpath = [ "src" ]
|
pythonpath = [
|
||||||
|
"src",
|
||||||
strict_config = true
|
]
|
||||||
strict_markers = true
|
|
||||||
strict_parametrization_ids = true
|
|
||||||
strict_xfail = true
|
|
||||||
|
|
||||||
testpaths = [
|
testpaths = [
|
||||||
"src/documents/tests/",
|
"src/documents/tests/",
|
||||||
"src/paperless/tests/",
|
"src/paperless/tests/",
|
||||||
@@ -280,7 +275,6 @@ testpaths = [
|
|||||||
"src/paperless_remote/tests/",
|
"src/paperless_remote/tests/",
|
||||||
"src/paperless_ai/tests",
|
"src/paperless_ai/tests",
|
||||||
]
|
]
|
||||||
|
|
||||||
addopts = [
|
addopts = [
|
||||||
"--pythonwarnings=all",
|
"--pythonwarnings=all",
|
||||||
"--cov",
|
"--cov",
|
||||||
@@ -288,14 +282,11 @@ addopts = [
|
|||||||
"--cov-report=xml",
|
"--cov-report=xml",
|
||||||
"--numprocesses=auto",
|
"--numprocesses=auto",
|
||||||
"--maxprocesses=16",
|
"--maxprocesses=16",
|
||||||
"--dist=loadscope",
|
"--quiet",
|
||||||
"--durations=50",
|
"--durations=50",
|
||||||
"--durations-min=0.5",
|
|
||||||
"--junitxml=junit.xml",
|
"--junitxml=junit.xml",
|
||||||
"-o",
|
"-o junit_family=legacy",
|
||||||
"junit_family=legacy",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
norecursedirs = [ "src/locale/", ".venv/", "src-ui/" ]
|
norecursedirs = [ "src/locale/", ".venv/", "src-ui/" ]
|
||||||
|
|
||||||
DJANGO_SETTINGS_MODULE = "paperless.settings"
|
DJANGO_SETTINGS_MODULE = "paperless.settings"
|
||||||
|
|||||||
@@ -3444,7 +3444,7 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.ts</context>
|
<context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.ts</context>
|
||||||
<context context-type="linenumber">111</context>
|
<context context-type="linenumber">113</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/input/date/date.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/input/date/date.component.html</context>
|
||||||
@@ -3704,14 +3704,14 @@
|
|||||||
<source>This month</source>
|
<source>This month</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.ts</context>
|
<context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.ts</context>
|
||||||
<context context-type="linenumber">106</context>
|
<context context-type="linenumber">107</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="4498682414491138092" datatype="html">
|
<trans-unit id="4498682414491138092" datatype="html">
|
||||||
<source>Yesterday</source>
|
<source>Yesterday</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.ts</context>
|
<context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.ts</context>
|
||||||
<context context-type="linenumber">116</context>
|
<context context-type="linenumber">118</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/pipes/custom-date.pipe.ts</context>
|
<context context-type="sourcefile">src/app/pipes/custom-date.pipe.ts</context>
|
||||||
@@ -3722,28 +3722,28 @@
|
|||||||
<source>Previous week</source>
|
<source>Previous week</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.ts</context>
|
<context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.ts</context>
|
||||||
<context context-type="linenumber">121</context>
|
<context context-type="linenumber">123</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="8586908745456864217" datatype="html">
|
<trans-unit id="8586908745456864217" datatype="html">
|
||||||
<source>Previous month</source>
|
<source>Previous month</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.ts</context>
|
<context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.ts</context>
|
||||||
<context context-type="linenumber">135</context>
|
<context context-type="linenumber">137</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="357608474534295480" datatype="html">
|
<trans-unit id="357608474534295480" datatype="html">
|
||||||
<source>Previous quarter</source>
|
<source>Previous quarter</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.ts</context>
|
<context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.ts</context>
|
||||||
<context context-type="linenumber">141</context>
|
<context context-type="linenumber">143</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="100513227838842152" datatype="html">
|
<trans-unit id="100513227838842152" datatype="html">
|
||||||
<source>Previous year</source>
|
<source>Previous year</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.ts</context>
|
<context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.ts</context>
|
||||||
<context context-type="linenumber">155</context>
|
<context context-type="linenumber">157</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="8743659855412792665" datatype="html">
|
<trans-unit id="8743659855412792665" datatype="html">
|
||||||
|
|||||||
@@ -164,9 +164,11 @@
|
|||||||
{{ item.name }}
|
{{ item.name }}
|
||||||
<span class="ms-auto text-muted small">
|
<span class="ms-auto text-muted small">
|
||||||
@if (item.dateEnd) {
|
@if (item.dateEnd) {
|
||||||
{{ item.date | customDate:'MMM d' }} – {{ item.dateEnd | customDate:'mediumDate' }}
|
{{ item.date | customDate:'mediumDate' }} – {{ item.dateEnd | customDate:'mediumDate' }}
|
||||||
|
} @else if (item.dateTilNow) {
|
||||||
|
{{ item.dateTilNow | customDate:'mediumDate' }} – <ng-container i18n>now</ng-container>
|
||||||
} @else {
|
} @else {
|
||||||
{{ item.date | customDate:'mediumDate' }} – <ng-container i18n>now</ng-container>
|
{{ item.date | customDate:'mediumDate' }}
|
||||||
}
|
}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -79,32 +79,34 @@ export class DatesDropdownComponent implements OnInit, OnDestroy {
|
|||||||
{
|
{
|
||||||
id: RelativeDate.WITHIN_1_WEEK,
|
id: RelativeDate.WITHIN_1_WEEK,
|
||||||
name: $localize`Within 1 week`,
|
name: $localize`Within 1 week`,
|
||||||
date: new Date().setDate(new Date().getDate() - 7),
|
dateTilNow: new Date().setDate(new Date().getDate() - 7),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: RelativeDate.WITHIN_1_MONTH,
|
id: RelativeDate.WITHIN_1_MONTH,
|
||||||
name: $localize`Within 1 month`,
|
name: $localize`Within 1 month`,
|
||||||
date: new Date().setMonth(new Date().getMonth() - 1),
|
dateTilNow: new Date().setMonth(new Date().getMonth() - 1),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: RelativeDate.WITHIN_3_MONTHS,
|
id: RelativeDate.WITHIN_3_MONTHS,
|
||||||
name: $localize`Within 3 months`,
|
name: $localize`Within 3 months`,
|
||||||
date: new Date().setMonth(new Date().getMonth() - 3),
|
dateTilNow: new Date().setMonth(new Date().getMonth() - 3),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: RelativeDate.WITHIN_1_YEAR,
|
id: RelativeDate.WITHIN_1_YEAR,
|
||||||
name: $localize`Within 1 year`,
|
name: $localize`Within 1 year`,
|
||||||
date: new Date().setFullYear(new Date().getFullYear() - 1),
|
dateTilNow: new Date().setFullYear(new Date().getFullYear() - 1),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: RelativeDate.THIS_YEAR,
|
id: RelativeDate.THIS_YEAR,
|
||||||
name: $localize`This year`,
|
name: $localize`This year`,
|
||||||
date: new Date('1/1/' + new Date().getFullYear()),
|
date: new Date('1/1/' + new Date().getFullYear()),
|
||||||
|
dateEnd: new Date('12/31/' + new Date().getFullYear()),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: RelativeDate.THIS_MONTH,
|
id: RelativeDate.THIS_MONTH,
|
||||||
name: $localize`This month`,
|
name: $localize`This month`,
|
||||||
date: new Date().setDate(1),
|
date: new Date().setDate(1),
|
||||||
|
dateEnd: new Date(new Date().getFullYear(), new Date().getMonth() + 1, 0),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: RelativeDate.TODAY,
|
id: RelativeDate.TODAY,
|
||||||
|
|||||||
@@ -224,18 +224,17 @@ class TestDoubleSided(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
|
|||||||
THEN:
|
THEN:
|
||||||
- The collated file gets put into foo/bar
|
- The collated file gets put into foo/bar
|
||||||
"""
|
"""
|
||||||
# TODO: parameterize this instead
|
|
||||||
for path in [
|
for path in [
|
||||||
Path("foo") / "bar" / "double-sided",
|
Path("foo") / "bar" / "double-sided",
|
||||||
Path("double-sided") / "foo" / "bar",
|
Path("double-sided") / "foo" / "bar",
|
||||||
]:
|
]:
|
||||||
with self.subTest(path=str(path)):
|
with self.subTest(path=path):
|
||||||
# Ensure we get fresh directories for each run
|
# Ensure we get fresh directories for each run
|
||||||
self.tearDown()
|
self.tearDown()
|
||||||
self.setUp()
|
self.setUp()
|
||||||
|
|
||||||
self.create_staging_file()
|
self.create_staging_file()
|
||||||
self.consume_file("double-sided-odd.pdf", Path(path) / "foo.pdf")
|
self.consume_file("double-sided-odd.pdf", path / "foo.pdf")
|
||||||
self.assertIsFile(
|
self.assertIsFile(
|
||||||
self.dirs.consumption_dir / "foo" / "bar" / "foo-collated.pdf",
|
self.dirs.consumption_dir / "foo" / "bar" / "foo-collated.pdf",
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
import logging
|
import logging
|
||||||
import shutil
|
import shutil
|
||||||
|
from datetime import timedelta
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import faiss
|
import faiss
|
||||||
import llama_index.core.settings as llama_settings
|
import llama_index.core.settings as llama_settings
|
||||||
import tqdm
|
import tqdm
|
||||||
|
from celery import states
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.utils import timezone
|
||||||
from llama_index.core import Document as LlamaDocument
|
from llama_index.core import Document as LlamaDocument
|
||||||
from llama_index.core import StorageContext
|
from llama_index.core import StorageContext
|
||||||
from llama_index.core import VectorStoreIndex
|
from llama_index.core import VectorStoreIndex
|
||||||
@@ -21,6 +24,7 @@ from llama_index.core.text_splitter import TokenTextSplitter
|
|||||||
from llama_index.vector_stores.faiss import FaissVectorStore
|
from llama_index.vector_stores.faiss import FaissVectorStore
|
||||||
|
|
||||||
from documents.models import Document
|
from documents.models import Document
|
||||||
|
from documents.models import PaperlessTask
|
||||||
from paperless_ai.embedding import build_llm_index_text
|
from paperless_ai.embedding import build_llm_index_text
|
||||||
from paperless_ai.embedding import get_embedding_dim
|
from paperless_ai.embedding import get_embedding_dim
|
||||||
from paperless_ai.embedding import get_embedding_model
|
from paperless_ai.embedding import get_embedding_model
|
||||||
@@ -28,6 +32,29 @@ from paperless_ai.embedding import get_embedding_model
|
|||||||
logger = logging.getLogger("paperless_ai.indexing")
|
logger = logging.getLogger("paperless_ai.indexing")
|
||||||
|
|
||||||
|
|
||||||
|
def queue_llm_index_update_if_needed(*, rebuild: bool, reason: str) -> bool:
|
||||||
|
from documents.tasks import llmindex_index
|
||||||
|
|
||||||
|
has_running = PaperlessTask.objects.filter(
|
||||||
|
task_name=PaperlessTask.TaskName.LLMINDEX_UPDATE,
|
||||||
|
status__in=[states.PENDING, states.STARTED],
|
||||||
|
).exists()
|
||||||
|
has_recent = PaperlessTask.objects.filter(
|
||||||
|
task_name=PaperlessTask.TaskName.LLMINDEX_UPDATE,
|
||||||
|
date_created__gte=(timezone.now() - timedelta(minutes=5)),
|
||||||
|
).exists()
|
||||||
|
if has_running or has_recent:
|
||||||
|
return False
|
||||||
|
|
||||||
|
llmindex_index.delay(rebuild=rebuild, scheduled=False, auto=True)
|
||||||
|
logger.warning(
|
||||||
|
"Queued LLM index update%s: %s",
|
||||||
|
" (rebuild)" if rebuild else "",
|
||||||
|
reason,
|
||||||
|
)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
def get_or_create_storage_context(*, rebuild=False):
|
def get_or_create_storage_context(*, rebuild=False):
|
||||||
"""
|
"""
|
||||||
Loads or creates the StorageContext (vector store, docstore, index store).
|
Loads or creates the StorageContext (vector store, docstore, index store).
|
||||||
@@ -93,6 +120,10 @@ def load_or_build_index(nodes=None):
|
|||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
logger.warning("Failed to load index from storage: %s", e)
|
logger.warning("Failed to load index from storage: %s", e)
|
||||||
if not nodes:
|
if not nodes:
|
||||||
|
queue_llm_index_update_if_needed(
|
||||||
|
rebuild=vector_store_file_exists(),
|
||||||
|
reason="LLM index missing or invalid while loading.",
|
||||||
|
)
|
||||||
logger.info("No nodes provided for index creation.")
|
logger.info("No nodes provided for index creation.")
|
||||||
raise
|
raise
|
||||||
return VectorStoreIndex(
|
return VectorStoreIndex(
|
||||||
@@ -250,6 +281,13 @@ def query_similar_documents(
|
|||||||
"""
|
"""
|
||||||
Runs a similarity query and returns top-k similar Document objects.
|
Runs a similarity query and returns top-k similar Document objects.
|
||||||
"""
|
"""
|
||||||
|
if not vector_store_file_exists():
|
||||||
|
queue_llm_index_update_if_needed(
|
||||||
|
rebuild=False,
|
||||||
|
reason="LLM index not found for similarity query.",
|
||||||
|
)
|
||||||
|
return []
|
||||||
|
|
||||||
index = load_or_build_index()
|
index = load_or_build_index()
|
||||||
|
|
||||||
# constrain only the node(s) that match the document IDs, if given
|
# constrain only the node(s) that match the document IDs, if given
|
||||||
|
|||||||
@@ -3,11 +3,13 @@ from unittest.mock import MagicMock
|
|||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
from celery import states
|
||||||
from django.test import override_settings
|
from django.test import override_settings
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from llama_index.core.base.embeddings.base import BaseEmbedding
|
from llama_index.core.base.embeddings.base import BaseEmbedding
|
||||||
|
|
||||||
from documents.models import Document
|
from documents.models import Document
|
||||||
|
from documents.models import PaperlessTask
|
||||||
from paperless_ai import indexing
|
from paperless_ai import indexing
|
||||||
|
|
||||||
|
|
||||||
@@ -288,6 +290,36 @@ def test_update_llm_index_no_documents(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_queue_llm_index_update_if_needed_enqueues_when_idle_or_skips_recent():
|
||||||
|
# No existing tasks
|
||||||
|
with patch("documents.tasks.llmindex_index") as mock_task:
|
||||||
|
result = indexing.queue_llm_index_update_if_needed(
|
||||||
|
rebuild=True,
|
||||||
|
reason="test enqueue",
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result is True
|
||||||
|
mock_task.delay.assert_called_once_with(rebuild=True, scheduled=False, auto=True)
|
||||||
|
|
||||||
|
PaperlessTask.objects.create(
|
||||||
|
task_id="task-1",
|
||||||
|
task_name=PaperlessTask.TaskName.LLMINDEX_UPDATE,
|
||||||
|
status=states.STARTED,
|
||||||
|
date_created=timezone.now(),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Existing running task
|
||||||
|
with patch("documents.tasks.llmindex_index") as mock_task:
|
||||||
|
result = indexing.queue_llm_index_update_if_needed(
|
||||||
|
rebuild=False,
|
||||||
|
reason="should skip",
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result is False
|
||||||
|
mock_task.delay.assert_not_called()
|
||||||
|
|
||||||
|
|
||||||
@override_settings(
|
@override_settings(
|
||||||
LLM_EMBEDDING_BACKEND="huggingface",
|
LLM_EMBEDDING_BACKEND="huggingface",
|
||||||
LLM_BACKEND="ollama",
|
LLM_BACKEND="ollama",
|
||||||
@@ -299,11 +331,15 @@ def test_query_similar_documents(
|
|||||||
with (
|
with (
|
||||||
patch("paperless_ai.indexing.get_or_create_storage_context") as mock_storage,
|
patch("paperless_ai.indexing.get_or_create_storage_context") as mock_storage,
|
||||||
patch("paperless_ai.indexing.load_or_build_index") as mock_load_or_build_index,
|
patch("paperless_ai.indexing.load_or_build_index") as mock_load_or_build_index,
|
||||||
|
patch(
|
||||||
|
"paperless_ai.indexing.vector_store_file_exists",
|
||||||
|
) as mock_vector_store_exists,
|
||||||
patch("paperless_ai.indexing.VectorIndexRetriever") as mock_retriever_cls,
|
patch("paperless_ai.indexing.VectorIndexRetriever") as mock_retriever_cls,
|
||||||
patch("paperless_ai.indexing.Document.objects.filter") as mock_filter,
|
patch("paperless_ai.indexing.Document.objects.filter") as mock_filter,
|
||||||
):
|
):
|
||||||
mock_storage.return_value = MagicMock()
|
mock_storage.return_value = MagicMock()
|
||||||
mock_storage.return_value.persist_dir = temp_llm_index_dir
|
mock_storage.return_value.persist_dir = temp_llm_index_dir
|
||||||
|
mock_vector_store_exists.return_value = True
|
||||||
|
|
||||||
mock_index = MagicMock()
|
mock_index = MagicMock()
|
||||||
mock_load_or_build_index.return_value = mock_index
|
mock_load_or_build_index.return_value = mock_index
|
||||||
@@ -332,3 +368,31 @@ def test_query_similar_documents(
|
|||||||
mock_filter.assert_called_once_with(pk__in=[1, 2])
|
mock_filter.assert_called_once_with(pk__in=[1, 2])
|
||||||
|
|
||||||
assert result == mock_filtered_docs
|
assert result == mock_filtered_docs
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_query_similar_documents_triggers_update_when_index_missing(
|
||||||
|
temp_llm_index_dir,
|
||||||
|
real_document,
|
||||||
|
):
|
||||||
|
with (
|
||||||
|
patch(
|
||||||
|
"paperless_ai.indexing.vector_store_file_exists",
|
||||||
|
return_value=False,
|
||||||
|
),
|
||||||
|
patch(
|
||||||
|
"paperless_ai.indexing.queue_llm_index_update_if_needed",
|
||||||
|
) as mock_queue,
|
||||||
|
patch("paperless_ai.indexing.load_or_build_index") as mock_load,
|
||||||
|
):
|
||||||
|
result = indexing.query_similar_documents(
|
||||||
|
real_document,
|
||||||
|
top_k=2,
|
||||||
|
)
|
||||||
|
|
||||||
|
mock_queue.assert_called_once_with(
|
||||||
|
rebuild=False,
|
||||||
|
reason="LLM index not found for similarity query.",
|
||||||
|
)
|
||||||
|
mock_load.assert_not_called()
|
||||||
|
assert result == []
|
||||||
|
|||||||
74
uv.lock
generated
74
uv.lock
generated
@@ -3152,15 +3152,15 @@ dev = [
|
|||||||
{ name = "mkdocs-material", specifier = "~=9.7.0" },
|
{ name = "mkdocs-material", specifier = "~=9.7.0" },
|
||||||
{ name = "pre-commit", specifier = "~=4.5.1" },
|
{ name = "pre-commit", specifier = "~=4.5.1" },
|
||||||
{ name = "pre-commit-uv", specifier = "~=4.2.0" },
|
{ name = "pre-commit-uv", specifier = "~=4.2.0" },
|
||||||
{ name = "pytest", specifier = "~=9.0.0" },
|
{ name = "pytest", specifier = "~=8.4.1" },
|
||||||
{ name = "pytest-cov", specifier = "~=7.0.0" },
|
{ name = "pytest-cov", specifier = "~=7.0.0" },
|
||||||
{ name = "pytest-django", specifier = "~=4.11.1" },
|
{ name = "pytest-django", specifier = "~=4.11.1" },
|
||||||
{ name = "pytest-env", specifier = "~=1.2.0" },
|
{ name = "pytest-env" },
|
||||||
{ name = "pytest-httpx" },
|
{ name = "pytest-httpx" },
|
||||||
{ name = "pytest-mock", specifier = "~=3.15.1" },
|
{ name = "pytest-mock" },
|
||||||
{ name = "pytest-rerunfailures", specifier = "~=16.1" },
|
{ name = "pytest-rerunfailures" },
|
||||||
{ name = "pytest-sugar" },
|
{ name = "pytest-sugar" },
|
||||||
{ name = "pytest-xdist", specifier = "~=3.8.0" },
|
{ name = "pytest-xdist" },
|
||||||
{ name = "ruff", specifier = "~=0.14.0" },
|
{ name = "ruff", specifier = "~=0.14.0" },
|
||||||
]
|
]
|
||||||
docs = [
|
docs = [
|
||||||
@@ -3176,15 +3176,15 @@ testing = [
|
|||||||
{ name = "daphne" },
|
{ name = "daphne" },
|
||||||
{ name = "factory-boy", specifier = "~=3.3.1" },
|
{ name = "factory-boy", specifier = "~=3.3.1" },
|
||||||
{ name = "imagehash" },
|
{ name = "imagehash" },
|
||||||
{ name = "pytest", specifier = "~=9.0.0" },
|
{ name = "pytest", specifier = "~=8.4.1" },
|
||||||
{ name = "pytest-cov", specifier = "~=7.0.0" },
|
{ name = "pytest-cov", specifier = "~=7.0.0" },
|
||||||
{ name = "pytest-django", specifier = "~=4.11.1" },
|
{ name = "pytest-django", specifier = "~=4.11.1" },
|
||||||
{ name = "pytest-env", specifier = "~=1.2.0" },
|
{ name = "pytest-env" },
|
||||||
{ name = "pytest-httpx" },
|
{ name = "pytest-httpx" },
|
||||||
{ name = "pytest-mock", specifier = "~=3.15.1" },
|
{ name = "pytest-mock" },
|
||||||
{ name = "pytest-rerunfailures", specifier = "~=16.1" },
|
{ name = "pytest-rerunfailures" },
|
||||||
{ name = "pytest-sugar" },
|
{ name = "pytest-sugar" },
|
||||||
{ name = "pytest-xdist", specifier = "~=3.8.0" },
|
{ name = "pytest-xdist" },
|
||||||
]
|
]
|
||||||
typing = [
|
typing = [
|
||||||
{ name = "celery-types" },
|
{ name = "celery-types" },
|
||||||
@@ -3841,7 +3841,7 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pytest"
|
name = "pytest"
|
||||||
version = "9.0.2"
|
version = "8.4.2"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "exceptiongroup", marker = "(python_full_version < '3.11' and sys_platform == 'darwin') or (python_full_version < '3.11' and sys_platform == 'linux')" },
|
{ name = "exceptiongroup", marker = "(python_full_version < '3.11' and sys_platform == 'darwin') or (python_full_version < '3.11' and sys_platform == 'linux')" },
|
||||||
@@ -3851,9 +3851,9 @@ dependencies = [
|
|||||||
{ name = "pygments", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
{ name = "pygments", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
||||||
{ name = "tomli", marker = "(python_full_version < '3.11' and sys_platform == 'darwin') or (python_full_version < '3.11' and sys_platform == 'linux')" },
|
{ name = "tomli", marker = "(python_full_version < '3.11' and sys_platform == 'darwin') or (python_full_version < '3.11' and sys_platform == 'linux')" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" },
|
{ url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3897,15 +3897,15 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pytest-httpx"
|
name = "pytest-httpx"
|
||||||
version = "0.36.0"
|
version = "0.35.0"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "httpx", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
{ name = "httpx", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
||||||
{ name = "pytest", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
{ name = "pytest", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/5574834da9499066fa1a5ea9c336f94dba2eae02298d36dab192fcf95c86/pytest_httpx-0.36.0.tar.gz", hash = "sha256:9edb66a5fd4388ce3c343189bc67e7e1cb50b07c2e3fc83b97d511975e8a831b", size = 56793, upload-time = "2025-12-02T16:34:57.414Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/1f/89/5b12b7b29e3d0af3a4b9c071ee92fa25a9017453731a38f08ba01c280f4c/pytest_httpx-0.35.0.tar.gz", hash = "sha256:d619ad5d2e67734abfbb224c3d9025d64795d4b8711116b1a13f72a251ae511f", size = 54146, upload-time = "2024-11-28T19:16:54.237Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/e2/d2/1eb1ea9c84f0d2033eb0b49675afdc71aa4ea801b74615f00f3c33b725e3/pytest_httpx-0.36.0-py3-none-any.whl", hash = "sha256:bd4c120bb80e142df856e825ec9f17981effb84d159f9fa29ed97e2357c3a9c8", size = 20229, upload-time = "2025-12-02T16:34:56.45Z" },
|
{ url = "https://files.pythonhosted.org/packages/b0/ed/026d467c1853dd83102411a78126b4842618e86c895f93528b0528c7a620/pytest_httpx-0.35.0-py3-none-any.whl", hash = "sha256:ee11a00ffcea94a5cbff47af2114d34c5b231c326902458deed73f9c459fd744", size = 19442, upload-time = "2024-11-28T19:16:52.787Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -5108,13 +5108,13 @@ dependencies = [
|
|||||||
{ name = "typing-extensions", marker = "sys_platform == 'darwin'" },
|
{ name = "typing-extensions", marker = "sys_platform == 'darwin'" },
|
||||||
]
|
]
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://download.pytorch.org/whl/cpu/torch-2.9.1-cp310-none-macosx_11_0_arm64.whl", hash = "sha256:bf1e68cfb935ae2046374ff02a7aa73dda70351b46342846f557055b3a540bf0" },
|
{ url = "https://download.pytorch.org/whl/cpu/torch-2.9.1-cp310-none-macosx_11_0_arm64.whl" },
|
||||||
{ url = "https://download.pytorch.org/whl/cpu/torch-2.9.1-cp311-none-macosx_11_0_arm64.whl", hash = "sha256:a52952a8c90a422c14627ea99b9826b7557203b46b4d0772d3ca5c7699692425" },
|
{ url = "https://download.pytorch.org/whl/cpu/torch-2.9.1-cp311-none-macosx_11_0_arm64.whl" },
|
||||||
{ url = "https://download.pytorch.org/whl/cpu/torch-2.9.1-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:287242dd1f830846098b5eca847f817aa5c6015ea57ab4c1287809efea7b77eb" },
|
{ url = "https://download.pytorch.org/whl/cpu/torch-2.9.1-cp312-none-macosx_11_0_arm64.whl" },
|
||||||
{ url = "https://download.pytorch.org/whl/cpu/torch-2.9.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8924d10d36eac8fe0652a060a03fc2ae52980841850b9a1a2ddb0f27a4f181cd" },
|
{ url = "https://download.pytorch.org/whl/cpu/torch-2.9.1-cp313-cp313t-macosx_11_0_arm64.whl" },
|
||||||
{ url = "https://download.pytorch.org/whl/cpu/torch-2.9.1-cp313-none-macosx_11_0_arm64.whl", hash = "sha256:bcee64ae7aa65876ceeae6dcaebe75109485b213528c74939602208a20706e3f" },
|
{ url = "https://download.pytorch.org/whl/cpu/torch-2.9.1-cp313-none-macosx_11_0_arm64.whl" },
|
||||||
{ url = "https://download.pytorch.org/whl/cpu/torch-2.9.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:defadbeb055cfcf5def58f70937145aecbd7a4bc295238ded1d0e85ae2cf0e1d" },
|
{ url = "https://download.pytorch.org/whl/cpu/torch-2.9.1-cp314-cp314-macosx_11_0_arm64.whl" },
|
||||||
{ url = "https://download.pytorch.org/whl/cpu/torch-2.9.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:886f84b181f766f53265ba0a1d503011e60f53fff9d569563ef94f24160e1072" },
|
{ url = "https://download.pytorch.org/whl/cpu/torch-2.9.1-cp314-cp314t-macosx_11_0_arm64.whl" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -5138,20 +5138,20 @@ dependencies = [
|
|||||||
{ name = "typing-extensions", marker = "sys_platform == 'linux'" },
|
{ name = "typing-extensions", marker = "sys_platform == 'linux'" },
|
||||||
]
|
]
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://download.pytorch.org/whl/cpu/torch-2.9.1%2Bcpu-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:10866c8a48c4aa5ae3f48538dc8a055b99c57d9c6af2bf5dd715374d9d6ddca3" },
|
{ url = "https://download.pytorch.org/whl/cpu/torch-2.9.1%2Bcpu-cp310-cp310-manylinux_2_28_aarch64.whl" },
|
||||||
{ url = "https://download.pytorch.org/whl/cpu/torch-2.9.1%2Bcpu-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:7210713b66943fdbfcc237b2e782871b649123ac5d29f548ce8c85be4223ab38" },
|
{ url = "https://download.pytorch.org/whl/cpu/torch-2.9.1%2Bcpu-cp310-cp310-manylinux_2_28_x86_64.whl" },
|
||||||
{ url = "https://download.pytorch.org/whl/cpu/torch-2.9.1%2Bcpu-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:0e611cfb16724e62252b67d31073bc5c490cb83e92ecdc1192762535e0e44487" },
|
{ url = "https://download.pytorch.org/whl/cpu/torch-2.9.1%2Bcpu-cp311-cp311-manylinux_2_28_aarch64.whl" },
|
||||||
{ url = "https://download.pytorch.org/whl/cpu/torch-2.9.1%2Bcpu-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:3de2adb9b4443dc9210ef1f1b16da3647ace53553166d6360bbbd7edd6f16e4d" },
|
{ url = "https://download.pytorch.org/whl/cpu/torch-2.9.1%2Bcpu-cp311-cp311-manylinux_2_28_x86_64.whl" },
|
||||||
{ url = "https://download.pytorch.org/whl/cpu/torch-2.9.1%2Bcpu-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:3bf9b442a51a2948e41216a76d7ab00f0694cfcaaa51b6f9bcab57b7f89843e6" },
|
{ url = "https://download.pytorch.org/whl/cpu/torch-2.9.1%2Bcpu-cp312-cp312-manylinux_2_28_aarch64.whl" },
|
||||||
{ url = "https://download.pytorch.org/whl/cpu/torch-2.9.1%2Bcpu-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:7417d8c565f219d3455654cb431c6d892a3eb40246055e14d645422de13b9ea1" },
|
{ url = "https://download.pytorch.org/whl/cpu/torch-2.9.1%2Bcpu-cp312-cp312-manylinux_2_28_x86_64.whl" },
|
||||||
{ url = "https://download.pytorch.org/whl/cpu/torch-2.9.1%2Bcpu-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:3e532e553b37ee859205a9b2d1c7977fd6922f53bbb1b9bfdd5bdc00d1a60ed4" },
|
{ url = "https://download.pytorch.org/whl/cpu/torch-2.9.1%2Bcpu-cp313-cp313-manylinux_2_28_aarch64.whl" },
|
||||||
{ url = "https://download.pytorch.org/whl/cpu/torch-2.9.1%2Bcpu-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:39b3dff6d8fba240ae0d1bede4ca11c2531ae3b47329206512d99e17907ff74b" },
|
{ url = "https://download.pytorch.org/whl/cpu/torch-2.9.1%2Bcpu-cp313-cp313-manylinux_2_28_x86_64.whl" },
|
||||||
{ url = "https://download.pytorch.org/whl/cpu/torch-2.9.1%2Bcpu-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:01b1884f724977a20c7da2f640f1c7b37f4a2c117a7f4a6c1c0424d14cb86322" },
|
{ url = "https://download.pytorch.org/whl/cpu/torch-2.9.1%2Bcpu-cp313-cp313t-manylinux_2_28_aarch64.whl" },
|
||||||
{ url = "https://download.pytorch.org/whl/cpu/torch-2.9.1%2Bcpu-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:031a597147fa81b1e6d79ccf1ad3ccc7fafa27941d6cf26ff5caaa384fb20e92" },
|
{ url = "https://download.pytorch.org/whl/cpu/torch-2.9.1%2Bcpu-cp313-cp313t-manylinux_2_28_x86_64.whl" },
|
||||||
{ url = "https://download.pytorch.org/whl/cpu/torch-2.9.1%2Bcpu-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:65010ab4aacce6c9a1ddfc935f986c003ca8638ded04348fd326c3e74346237c" },
|
{ url = "https://download.pytorch.org/whl/cpu/torch-2.9.1%2Bcpu-cp314-cp314-manylinux_2_28_aarch64.whl" },
|
||||||
{ url = "https://download.pytorch.org/whl/cpu/torch-2.9.1%2Bcpu-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:88adf5157db5da1d54b1c9fe4a6c1d20ceef00e75d854e206a87dbf69e3037dc" },
|
{ url = "https://download.pytorch.org/whl/cpu/torch-2.9.1%2Bcpu-cp314-cp314-manylinux_2_28_x86_64.whl" },
|
||||||
{ url = "https://download.pytorch.org/whl/cpu/torch-2.9.1%2Bcpu-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:3ac2b8df2c55430e836dcda31940d47f1f5f94b8731057b6f20300ebea394dd9" },
|
{ url = "https://download.pytorch.org/whl/cpu/torch-2.9.1%2Bcpu-cp314-cp314t-manylinux_2_28_aarch64.whl" },
|
||||||
{ url = "https://download.pytorch.org/whl/cpu/torch-2.9.1%2Bcpu-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:5b688445f928f13563b7418b17c57e97bf955ab559cf73cd8f2b961f8572dbb3" },
|
{ url = "https://download.pytorch.org/whl/cpu/torch-2.9.1%2Bcpu-cp314-cp314t-manylinux_2_28_x86_64.whl" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
Reference in New Issue
Block a user