mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2026-11-12 17:20:48 +00:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5e4936ee83 | ||
|
|
119a779629 |
No files matched your search
+6
-6
@@ -86,9 +86,9 @@ mariadb = [
|
||||
"mysqlclient>=2.2.7,<2.4",
|
||||
]
|
||||
postgres = [
|
||||
"psycopg[c,pool]==3.3.8",
|
||||
"psycopg[c,pool]==3.3.4",
|
||||
# Direct dependency for proper resolution of the pre-built wheels
|
||||
"psycopg-c==3.3.8",
|
||||
"psycopg-c==3.3.4",
|
||||
"psycopg-pool==3.3.3",
|
||||
]
|
||||
webserver = [
|
||||
@@ -158,10 +158,10 @@ package = false
|
||||
[tool.uv.sources]
|
||||
# Markers are chosen to select these almost exclusively when building the Docker image
|
||||
psycopg-c = [
|
||||
{ url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-trixie-3.3.8/psycopg_c-3.3.8-cp312-cp312-manylinux_2_38_x86_64.whl", marker = "sys_platform == 'linux' and platform_machine == 'x86_64' and python_version == '3.12'" },
|
||||
{ url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-trixie-3.3.8/psycopg_c-3.3.8-cp312-cp312-manylinux_2_38_aarch64.whl", marker = "sys_platform == 'linux' and platform_machine == 'aarch64' and python_version == '3.12'" },
|
||||
{ url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-trixie-3.3.8/psycopg_c-3.3.8-cp314-cp314-manylinux_2_38_x86_64.whl", marker = "sys_platform == 'linux' and platform_machine == 'x86_64' and python_version == '3.14'" },
|
||||
{ url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-trixie-3.3.8/psycopg_c-3.3.8-cp314-cp314-manylinux_2_38_aarch64.whl", marker = "sys_platform == 'linux' and platform_machine == 'aarch64' and python_version == '3.14'" },
|
||||
{ url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-trixie-3.3.4/psycopg_c-3.3.4-cp312-cp312-linux_x86_64.whl", marker = "sys_platform == 'linux' and platform_machine == 'x86_64' and python_version == '3.12'" },
|
||||
{ url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-trixie-3.3.4/psycopg_c-3.3.4-cp312-cp312-linux_aarch64.whl", marker = "sys_platform == 'linux' and platform_machine == 'aarch64' and python_version == '3.12'" },
|
||||
{ url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-trixie-3.3.4/psycopg_c-3.3.4-cp314-cp314-linux_x86_64.whl", marker = "sys_platform == 'linux' and platform_machine == 'x86_64' and python_version == '3.14'" },
|
||||
{ url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-trixie-3.3.4/psycopg_c-3.3.4-cp314-cp314-linux_aarch64.whl", marker = "sys_platform == 'linux' and platform_machine == 'aarch64' and python_version == '3.14'" },
|
||||
]
|
||||
torch = [
|
||||
{ index = "pytorch-cpu" },
|
||||
|
||||
@@ -46,6 +46,16 @@ def _build_document_reference(
|
||||
}
|
||||
|
||||
|
||||
def _permitted_documents_filter(store, permitted_ids: set[int]):
|
||||
"""Scope retrieval to ``permitted_ids`` using whichever of an inclusion or
|
||||
exclusion list is shorter, since a long inclusion list is much slower for
|
||||
the vector store than a long exclusion list."""
|
||||
denied_ids = store.indexed_document_ids() - permitted_ids
|
||||
if len(permitted_ids) <= len(denied_ids):
|
||||
return document_id_filters(str(pk) for pk in permitted_ids)
|
||||
return exclude_document_ids_filter(str(pk) for pk in denied_ids)
|
||||
|
||||
|
||||
def _get_document_references(
|
||||
documents: QuerySet[Document],
|
||||
top_nodes: list,
|
||||
@@ -128,24 +138,23 @@ def _stream_chat_with_documents(
|
||||
from llama_index.core.retrievers import VectorIndexRetriever
|
||||
|
||||
config = AIConfig()
|
||||
permitted_ids: set[int] | None = None
|
||||
if unrestricted:
|
||||
# Exclude trashed ids (usually few) instead of an IN filter over the
|
||||
# full permitted set, which risks the vector store's bound parameter
|
||||
# limit (_MAX_IN_VALUES) on large installs. Trashed documents stay
|
||||
# indexed until permanent deletion (delete_document_from_llm_index
|
||||
# hangs off post_delete, not trash), so must be excluded explicitly.
|
||||
# Exclude trashed ids (usually few) rather than send every permitted
|
||||
# id on each query. Trashed documents stay indexed until permanent
|
||||
# deletion (post_delete, not trash), so must be excluded explicitly.
|
||||
trashed_ids = Document.deleted_objects.values_list("pk", flat=True)
|
||||
filters = exclude_document_ids_filter(str(pk) for pk in trashed_ids)
|
||||
else:
|
||||
filters = document_id_filters(
|
||||
str(pk) for pk in documents.values_list("pk", flat=True)
|
||||
)
|
||||
permitted_ids = set(documents.values_list("pk", flat=True))
|
||||
|
||||
# Hold the shared read lock only while reading the index, so the compaction
|
||||
# swap cannot replace the database file while this connection is open.
|
||||
# Everything after retrieval works on the nodes already in memory, so a slow
|
||||
# LLM stream never holds the lock.
|
||||
with read_store() as store:
|
||||
if permitted_ids is not None:
|
||||
filters = _permitted_documents_filter(store, permitted_ids)
|
||||
index = load_or_build_index(config, store)
|
||||
retriever = VectorIndexRetriever(
|
||||
index=index,
|
||||
@@ -159,6 +168,15 @@ def _stream_chat_with_documents(
|
||||
with db_connection_released():
|
||||
top_nodes = retriever.retrieve(query_str)
|
||||
|
||||
if permitted_ids is not None:
|
||||
# An exclusion filter fails open for a document indexed after the
|
||||
# permitted set was read, so drop anything not explicitly permitted.
|
||||
top_nodes = [
|
||||
node
|
||||
for node in top_nodes
|
||||
if int(node.metadata["document_id"]) in permitted_ids
|
||||
]
|
||||
|
||||
if not top_nodes:
|
||||
logger.warning("No nodes found for the given documents.")
|
||||
yield CHAT_NO_CONTENT_MESSAGE
|
||||
|
||||
@@ -141,6 +141,15 @@ class DocumentMetaTable:
|
||||
(DocumentMetaRow(r["document_id"], r["modified"]) for r in batch),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def all_document_ids(conn: sqlite3.Connection) -> set[int]:
|
||||
"""Every indexed document_id. document_meta has one row per document,
|
||||
so this is a cheap primary-key scan, unlike reading the vec0 table."""
|
||||
return {
|
||||
row["document_id"]
|
||||
for row in conn.execute("SELECT document_id FROM document_meta")
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def all_modified_times(conn: sqlite3.Connection) -> dict[str, str]:
|
||||
"""Full document_id -> modified map, for get_modified_times()'s
|
||||
|
||||
@@ -13,6 +13,7 @@ from django.utils import timezone
|
||||
from llama_index.core import settings as llama_settings
|
||||
from llama_index.core.embeddings.mock_embed_model import MockEmbedding
|
||||
from llama_index.core.schema import TextNode
|
||||
from llama_index.core.vector_stores.types import FilterOperator
|
||||
|
||||
from documents.models import Document
|
||||
from paperless_ai import chat
|
||||
@@ -412,10 +413,8 @@ class TestStreamChatRetrieval:
|
||||
WHEN:
|
||||
- stream_chat_with_documents is called with unrestricted=True
|
||||
THEN:
|
||||
- The retriever receives a NOT IN filter excluding zero ids, so
|
||||
the whole index is effectively searched -- and no IN-list is
|
||||
built from the full permitted set, which is what risks the
|
||||
vector store's safety limit on large installs
|
||||
- The retriever receives a NOT IN filter excluding zero ids
|
||||
rather than an IN list of every permitted id
|
||||
"""
|
||||
document = DocumentFactory.create(content="indexed document content")
|
||||
indexing.llm_index_add_or_update_document(document)
|
||||
@@ -468,6 +467,70 @@ class TestStreamChatRetrieval:
|
||||
assert str(trashed.pk) in filter_values
|
||||
assert str(kept.pk) not in filter_values
|
||||
|
||||
def test_restricted_chat_excludes_denied_ids_when_few_are_denied(
|
||||
self,
|
||||
temp_llm_index_dir: Path,
|
||||
mock_embed_model: pytest_mock.MockType,
|
||||
captured_filters: list[Any],
|
||||
) -> None:
|
||||
"""
|
||||
GIVEN:
|
||||
- Three indexed documents, one of which the caller may not see
|
||||
WHEN:
|
||||
- A restricted caller chats
|
||||
THEN:
|
||||
- The retriever receives a NOT IN filter on the denied id, not an
|
||||
IN list of the permitted ids
|
||||
"""
|
||||
docs = DocumentFactory.create_batch(3, content="indexed document content")
|
||||
for doc in docs:
|
||||
indexing.llm_index_add_or_update_document(doc)
|
||||
permitted = Document.objects.filter(pk__in=[docs[0].pk, docs[1].pk])
|
||||
|
||||
list(chat.stream_chat_with_documents("question?", permitted))
|
||||
|
||||
metadata_filter = captured_filters[0].filters[0]
|
||||
assert metadata_filter.operator == FilterOperator.NIN
|
||||
assert metadata_filter.value == [str(docs[2].pk)]
|
||||
|
||||
def test_restricted_chat_drops_retrieved_nodes_of_unpermitted_documents(
|
||||
self,
|
||||
temp_llm_index_dir: Path,
|
||||
mock_embed_model: pytest_mock.MockType,
|
||||
mocker: pytest_mock.MockerFixture,
|
||||
) -> None:
|
||||
"""
|
||||
GIVEN:
|
||||
- A retriever that returns a node for a document the caller may
|
||||
not see, as when it is indexed after the permitted set was read
|
||||
WHEN:
|
||||
- A restricted caller chats
|
||||
THEN:
|
||||
- Only the node of the permitted document reaches the synthesizer
|
||||
"""
|
||||
permitted_doc = DocumentFactory.create(content="permitted content")
|
||||
denied_doc = DocumentFactory.create(content="denied content")
|
||||
permitted_node = MagicMock(metadata={"document_id": str(permitted_doc.pk)})
|
||||
denied_node = MagicMock(metadata={"document_id": str(denied_doc.pk)})
|
||||
mocker.patch("paperless_ai.chat.AIClient")
|
||||
retriever = mocker.patch(
|
||||
"llama_index.core.retrievers.VectorIndexRetriever",
|
||||
).return_value
|
||||
retriever.retrieve.return_value = [denied_node, permitted_node]
|
||||
synthesizer = mocker.patch(
|
||||
"llama_index.core.response_synthesizers.get_response_synthesizer",
|
||||
).return_value
|
||||
synthesizer.synthesize.return_value.response_gen = iter(["chunk"])
|
||||
|
||||
list(
|
||||
chat.stream_chat_with_documents(
|
||||
"question?",
|
||||
Document.objects.filter(pk=permitted_doc.pk),
|
||||
),
|
||||
)
|
||||
|
||||
synthesizer.synthesize.assert_called_once_with("question?", [permitted_node])
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_get_document_references_only_queries_referenced_documents(
|
||||
self,
|
||||
|
||||
@@ -18,7 +18,7 @@ from paperless_ai.migrations import Migration
|
||||
from paperless_ai.migrations import m0001_v1_to_v2
|
||||
from paperless_ai.tables import DocumentChunksTable
|
||||
from paperless_ai.tables import DocumentMetaTable
|
||||
from paperless_ai.vector_store import _MAX_IN_VALUES
|
||||
from paperless_ai.vector_store import _MAX_INLINE_IN_VALUES
|
||||
from paperless_ai.vector_store import DB_FILENAME
|
||||
from paperless_ai.vector_store import DB_PAGE_SIZE
|
||||
from paperless_ai.vector_store import DEFAULT_TABLE_NAME
|
||||
@@ -354,37 +354,39 @@ class TestBuildWhere:
|
||||
assert params == []
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"build_filter",
|
||||
[_in_filter, _nin_filter],
|
||||
ids=["in", "nin"],
|
||||
("build_filter", "expected_ids"),
|
||||
[
|
||||
pytest.param(_in_filter, ["a", "c"], id="in"),
|
||||
pytest.param(_nin_filter, ["b"], id="nin"),
|
||||
],
|
||||
)
|
||||
def test_fails_closed_when_filter_exceeds_max_values(
|
||||
def test_query_with_oversized_filter_scopes_results(
|
||||
self,
|
||||
store: PaperlessSqliteVecVectorStore,
|
||||
build_filter: Callable[[list[str]], MetadataFilters],
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
expected_ids: list[str],
|
||||
) -> None:
|
||||
"""
|
||||
GIVEN:
|
||||
- An IN or NOT IN filter with more values than _MAX_IN_VALUES
|
||||
(SQLite's own bound-parameter limit is 32766; this guard sits
|
||||
below that with headroom for the query's other bound parameters)
|
||||
- A store holding documents 1, 2 and 40000
|
||||
- An IN or NOT IN filter naming documents 1 and 40000, padded
|
||||
past _MAX_INLINE_IN_VALUES with unindexed ids
|
||||
WHEN:
|
||||
- _build_where() translates it to SQL
|
||||
- The store is queried and get_nodes() is called with that filter
|
||||
THEN:
|
||||
- It fails closed ("1 = 0", no params) instead of building a
|
||||
clause SQLite would reject, and logs a warning -- this filter
|
||||
scopes document access, so refusing to build it must never
|
||||
widen the scope to "everything" by accident. Failing open on
|
||||
a NOT IN would surface exactly the excluded rows
|
||||
- Both return exactly the documents the filter allows
|
||||
"""
|
||||
oversized = build_filter([str(i) for i in range(_MAX_IN_VALUES + 1)])
|
||||
store.add(
|
||||
[make_node("a", 1), make_node("b", 2), make_node("c", 40000)],
|
||||
)
|
||||
padding = range(100, 100 + _MAX_INLINE_IN_VALUES)
|
||||
filters = build_filter([str(i) for i in (1, 40000, *padding)])
|
||||
|
||||
with caplog.at_level("WARNING"):
|
||||
where, params = _build_where(oversized)
|
||||
result = _query(store, [0.0] * DIM, top_k=5, filters=filters)
|
||||
nodes = store.get_nodes(filters=filters)
|
||||
|
||||
assert where == "(1 = 0)"
|
||||
assert params == []
|
||||
assert "document_id" in caplog.text
|
||||
assert sorted(result.ids) == expected_ids
|
||||
assert sorted(n.node_id for n in nodes) == expected_ids
|
||||
|
||||
def test_query_with_untranslatable_filter_returns_no_rows(
|
||||
self,
|
||||
|
||||
@@ -89,15 +89,10 @@ class _Row(NamedTuple):
|
||||
embedding: bytes
|
||||
|
||||
|
||||
# _build_where(): the largest IN value list translated into bound SQL
|
||||
# parameters. SQLite's own hard limit (SQLITE_MAX_VARIABLE_NUMBER) is 32766
|
||||
# by default; this leaves headroom below that for the query's other bound
|
||||
# parameters (the embedding blob, k, and any NE clause) and for the limit
|
||||
# itself to move. An IN filter this large should not happen in practice --
|
||||
# callers are expected to pass None (no filter) rather than every id when
|
||||
# the filter would not actually narrow anything -- so this is a guard
|
||||
# against a future regression, not a normal code path.
|
||||
_MAX_IN_VALUES = 32700
|
||||
# _build_where(): the largest IN / NOT IN list bound as one parameter per value,
|
||||
# leaving headroom under SQLite's 32766-variable limit for the query's other
|
||||
# parameters. Longer lists are bound as a single JSON array via json_each().
|
||||
_MAX_INLINE_IN_VALUES = 32700
|
||||
|
||||
|
||||
def _pack(embedding: Sequence[float]) -> bytes:
|
||||
@@ -120,7 +115,7 @@ def _vec0_params(rows: list[_Row]) -> list[tuple[str, int, str, bytes]]:
|
||||
return [(r.chunk_id, r.document_id, r.node_content, r.embedding) for r in rows]
|
||||
|
||||
|
||||
def _build_where(filters: MetadataFilters | None) -> tuple[str, list[int]]:
|
||||
def _build_where(filters: MetadataFilters | None) -> tuple[str, list[int | str]]:
|
||||
"""Translate the EQ / IN / NIN / NE filters we use into a parameterized
|
||||
SQL clause on vec0 metadata columns. Returns ("", []) when there is
|
||||
nothing to filter. document_id is vec0's only filterable column and is
|
||||
@@ -132,7 +127,7 @@ def _build_where(filters: MetadataFilters | None) -> tuple[str, list[int]]:
|
||||
if filters is None or not filters.filters:
|
||||
return "", []
|
||||
clauses: list[str] = []
|
||||
params: list[int] = []
|
||||
params: list[int | str] = []
|
||||
for f in filters.filters:
|
||||
# filters.filters is Union[MetadataFilter, ExactMatchFilter, MetadataFilters];
|
||||
# we only build MetadataFilter entries, so skip anything else at runtime.
|
||||
@@ -149,21 +144,11 @@ def _build_where(filters: MetadataFilters | None) -> tuple[str, list[int]]:
|
||||
# excludes nothing, so it matches everything.
|
||||
clauses.append("1 = 0" if is_in else "1 = 1")
|
||||
continue
|
||||
if len(values) > _MAX_IN_VALUES:
|
||||
# Refuse rather than risk SQLite's own bound-parameter limit
|
||||
# ("too many SQL variables"): a list this large must match no
|
||||
# rows, never widen the scope to "everything" -- true for
|
||||
# NOT IN too, where failing open would surface every
|
||||
# excluded row.
|
||||
logger.warning(
|
||||
"Refusing to build a %s filter on %r with %d values "
|
||||
"(over the %d-value safety limit); returning no rows.",
|
||||
sql_op,
|
||||
f.key,
|
||||
len(values),
|
||||
_MAX_IN_VALUES,
|
||||
if len(values) > _MAX_INLINE_IN_VALUES:
|
||||
clauses.append(
|
||||
f"{f.key} {sql_op} (SELECT value FROM json_each(?))",
|
||||
)
|
||||
clauses.append("1 = 0")
|
||||
params.append(json.dumps(values))
|
||||
continue
|
||||
placeholders = ",".join("?" for _ in values)
|
||||
clauses.append(f"{f.key} {sql_op} ({placeholders})")
|
||||
@@ -558,6 +543,12 @@ class PaperlessSqliteVecVectorStore(BasePydanticVectorStore):
|
||||
return {}
|
||||
return DocumentMetaTable.all_modified_times(self._conn)
|
||||
|
||||
def indexed_document_ids(self) -> set[int]:
|
||||
"""Return the ids of all indexed documents."""
|
||||
if not self.table_exists():
|
||||
return set()
|
||||
return DocumentMetaTable.all_document_ids(self._conn)
|
||||
|
||||
@property
|
||||
def _db_path(self) -> str:
|
||||
return str(Path(self._uri) / DB_FILENAME)
|
||||
|
||||
@@ -3149,11 +3149,11 @@ mariadb = [
|
||||
]
|
||||
postgres = [
|
||||
{ name = "psycopg", extra = ["c", "pool"] },
|
||||
{ name = "psycopg-c", version = "3.3.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12' or python_full_version == '3.13.*' or python_full_version >= '3.15' or (platform_machine != 'aarch64' and platform_machine != 'x86_64') or sys_platform != 'linux'" },
|
||||
{ name = "psycopg-c", version = "3.3.8", source = { url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-trixie-3.3.8/psycopg_c-3.3.8-cp312-cp312-manylinux_2_38_aarch64.whl" }, marker = "python_full_version == '3.12.*' and platform_machine == 'aarch64' and sys_platform == 'linux'" },
|
||||
{ name = "psycopg-c", version = "3.3.8", source = { url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-trixie-3.3.8/psycopg_c-3.3.8-cp312-cp312-manylinux_2_38_x86_64.whl" }, marker = "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'linux'" },
|
||||
{ name = "psycopg-c", version = "3.3.8", source = { url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-trixie-3.3.8/psycopg_c-3.3.8-cp314-cp314-manylinux_2_38_aarch64.whl" }, marker = "python_full_version == '3.14.*' and platform_machine == 'aarch64' and sys_platform == 'linux'" },
|
||||
{ name = "psycopg-c", version = "3.3.8", source = { url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-trixie-3.3.8/psycopg_c-3.3.8-cp314-cp314-manylinux_2_38_x86_64.whl" }, marker = "python_full_version == '3.14.*' and platform_machine == 'x86_64' and sys_platform == 'linux'" },
|
||||
{ name = "psycopg-c", version = "3.3.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12' or python_full_version == '3.13.*' or python_full_version >= '3.15' or (platform_machine != 'aarch64' and platform_machine != 'x86_64') or sys_platform != 'linux'" },
|
||||
{ name = "psycopg-c", version = "3.3.4", source = { url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-trixie-3.3.4/psycopg_c-3.3.4-cp312-cp312-linux_aarch64.whl" }, marker = "python_full_version == '3.12.*' and platform_machine == 'aarch64' and sys_platform == 'linux'" },
|
||||
{ name = "psycopg-c", version = "3.3.4", source = { url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-trixie-3.3.4/psycopg_c-3.3.4-cp312-cp312-linux_x86_64.whl" }, marker = "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'linux'" },
|
||||
{ name = "psycopg-c", version = "3.3.4", source = { url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-trixie-3.3.4/psycopg_c-3.3.4-cp314-cp314-linux_aarch64.whl" }, marker = "python_full_version == '3.14.*' and platform_machine == 'aarch64' and sys_platform == 'linux'" },
|
||||
{ name = "psycopg-c", version = "3.3.4", source = { url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-trixie-3.3.4/psycopg_c-3.3.4-cp314-cp314-linux_x86_64.whl" }, marker = "python_full_version == '3.14.*' and platform_machine == 'x86_64' and sys_platform == 'linux'" },
|
||||
{ name = "psycopg-pool" },
|
||||
]
|
||||
webserver = [
|
||||
@@ -3272,12 +3272,12 @@ requires-dist = [
|
||||
{ name = "openai", specifier = ">=2.48" },
|
||||
{ name = "pathvalidate", specifier = "~=3.3.1" },
|
||||
{ name = "pdf2image", specifier = "~=1.17.0" },
|
||||
{ name = "psycopg", extras = ["c", "pool"], marker = "extra == 'postgres'", specifier = "==3.3.8" },
|
||||
{ name = "psycopg-c", marker = "python_full_version == '3.12.*' and platform_machine == 'aarch64' and sys_platform == 'linux' and extra == 'postgres'", url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-trixie-3.3.8/psycopg_c-3.3.8-cp312-cp312-manylinux_2_38_aarch64.whl" },
|
||||
{ name = "psycopg-c", marker = "python_full_version == '3.14.*' and platform_machine == 'aarch64' and sys_platform == 'linux' and extra == 'postgres'", url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-trixie-3.3.8/psycopg_c-3.3.8-cp314-cp314-manylinux_2_38_aarch64.whl" },
|
||||
{ name = "psycopg-c", marker = "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'linux' and extra == 'postgres'", url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-trixie-3.3.8/psycopg_c-3.3.8-cp312-cp312-manylinux_2_38_x86_64.whl" },
|
||||
{ name = "psycopg-c", marker = "python_full_version == '3.14.*' and platform_machine == 'x86_64' and sys_platform == 'linux' and extra == 'postgres'", url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-trixie-3.3.8/psycopg_c-3.3.8-cp314-cp314-manylinux_2_38_x86_64.whl" },
|
||||
{ name = "psycopg-c", marker = "(python_full_version < '3.12' and extra == 'postgres') or (python_full_version == '3.13.*' and extra == 'postgres') or (python_full_version >= '3.15' and extra == 'postgres') or (platform_machine != 'aarch64' and platform_machine != 'x86_64' and extra == 'postgres') or (sys_platform != 'linux' and extra == 'postgres')", specifier = "==3.3.8" },
|
||||
{ name = "psycopg", extras = ["c", "pool"], marker = "extra == 'postgres'", specifier = "==3.3.4" },
|
||||
{ name = "psycopg-c", marker = "python_full_version == '3.12.*' and platform_machine == 'aarch64' and sys_platform == 'linux' and extra == 'postgres'", url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-trixie-3.3.4/psycopg_c-3.3.4-cp312-cp312-linux_aarch64.whl" },
|
||||
{ name = "psycopg-c", marker = "python_full_version == '3.14.*' and platform_machine == 'aarch64' and sys_platform == 'linux' and extra == 'postgres'", url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-trixie-3.3.4/psycopg_c-3.3.4-cp314-cp314-linux_aarch64.whl" },
|
||||
{ name = "psycopg-c", marker = "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'linux' and extra == 'postgres'", url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-trixie-3.3.4/psycopg_c-3.3.4-cp312-cp312-linux_x86_64.whl" },
|
||||
{ name = "psycopg-c", marker = "python_full_version == '3.14.*' and platform_machine == 'x86_64' and sys_platform == 'linux' and extra == 'postgres'", url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-trixie-3.3.4/psycopg_c-3.3.4-cp314-cp314-linux_x86_64.whl" },
|
||||
{ name = "psycopg-c", marker = "(python_full_version < '3.12' and extra == 'postgres') or (python_full_version == '3.13.*' and extra == 'postgres') or (python_full_version >= '3.15' and extra == 'postgres') or (platform_machine != 'aarch64' and platform_machine != 'x86_64' and extra == 'postgres') or (sys_platform != 'linux' and extra == 'postgres')", specifier = "==3.3.4" },
|
||||
{ name = "psycopg-pool", marker = "extra == 'postgres'", specifier = "==3.3.3" },
|
||||
{ name = "python-dateutil", specifier = "~=2.9.0" },
|
||||
{ name = "python-dotenv", specifier = "~=1.2.1" },
|
||||
@@ -3723,23 +3723,23 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "psycopg"
|
||||
version = "3.3.8"
|
||||
version = "3.3.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/11/c8/3bd7e09770a5829eb26667df5e49f084d78bd106d5949ade9447288a8e18/psycopg-3.3.8.tar.gz", hash = "sha256:86c135a9a6f5e98404975c5787e620bf436a7807e088baebd1b20fa48d3b4b4f", size = 168902, upload-time = "2026-11-04T09:44:26.142Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/db/2f/cb91e5502ec9de1de6f1b76cfbf69531932725361168bb06963620c77e2e/psycopg-3.3.4.tar.gz", hash = "sha256:e21207764952cff81b6b8bdacad9a3939f2793367fdac2987b3aac36a651b5bc", size = 165799, upload-time = "2026-05-01T23:31:55.179Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/7b/5c/50a0b0796ccb69b11934f7c5758051c08019bda2fafb417501109fffd569/psycopg-3.3.8-py3-none-any.whl", hash = "sha256:ca50af6072ba9ea6866f33021975230c2555d7bfdb9d8f94ca3af5c3d1bf2167", size = 216209, upload-time = "2026-11-04T09:37:35.491Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5c/e0/7b3dee031daae7743609ce3c746565d4a3ed7c2c186479eb48e34e838c64/psycopg-3.3.4-py3-none-any.whl", hash = "sha256:b6bbc25ccf05c8fad3b061d9db2ef0909a555171b84b07f29458a447253d679a", size = 213001, upload-time = "2026-05-01T23:20:50.816Z" },
|
||||
]
|
||||
|
||||
[package.optional-dependencies]
|
||||
c = [
|
||||
{ name = "psycopg-c", version = "3.3.8", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.12' and implementation_name != 'pypy') or (python_full_version == '3.13.*' and implementation_name != 'pypy') or (python_full_version >= '3.15' and implementation_name != 'pypy') or (implementation_name != 'pypy' and platform_machine != 'aarch64' and platform_machine != 'x86_64') or (implementation_name != 'pypy' and sys_platform != 'linux')" },
|
||||
{ name = "psycopg-c", version = "3.3.8", source = { url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-trixie-3.3.8/psycopg_c-3.3.8-cp312-cp312-manylinux_2_38_aarch64.whl" }, marker = "python_full_version == '3.12.*' and implementation_name != 'pypy' and platform_machine == 'aarch64' and sys_platform == 'linux'" },
|
||||
{ name = "psycopg-c", version = "3.3.8", source = { url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-trixie-3.3.8/psycopg_c-3.3.8-cp312-cp312-manylinux_2_38_x86_64.whl" }, marker = "python_full_version == '3.12.*' and implementation_name != 'pypy' and platform_machine == 'x86_64' and sys_platform == 'linux'" },
|
||||
{ name = "psycopg-c", version = "3.3.8", source = { url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-trixie-3.3.8/psycopg_c-3.3.8-cp314-cp314-manylinux_2_38_aarch64.whl" }, marker = "python_full_version == '3.14.*' and implementation_name != 'pypy' and platform_machine == 'aarch64' and sys_platform == 'linux'" },
|
||||
{ name = "psycopg-c", version = "3.3.8", source = { url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-trixie-3.3.8/psycopg_c-3.3.8-cp314-cp314-manylinux_2_38_x86_64.whl" }, marker = "python_full_version == '3.14.*' and implementation_name != 'pypy' and platform_machine == 'x86_64' and sys_platform == 'linux'" },
|
||||
{ name = "psycopg-c", version = "3.3.4", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.12' and implementation_name != 'pypy') or (python_full_version == '3.13.*' and implementation_name != 'pypy') or (python_full_version >= '3.15' and implementation_name != 'pypy') or (implementation_name != 'pypy' and platform_machine != 'aarch64' and platform_machine != 'x86_64') or (implementation_name != 'pypy' and sys_platform != 'linux')" },
|
||||
{ name = "psycopg-c", version = "3.3.4", source = { url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-trixie-3.3.4/psycopg_c-3.3.4-cp312-cp312-linux_aarch64.whl" }, marker = "python_full_version == '3.12.*' and implementation_name != 'pypy' and platform_machine == 'aarch64' and sys_platform == 'linux'" },
|
||||
{ name = "psycopg-c", version = "3.3.4", source = { url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-trixie-3.3.4/psycopg_c-3.3.4-cp312-cp312-linux_x86_64.whl" }, marker = "python_full_version == '3.12.*' and implementation_name != 'pypy' and platform_machine == 'x86_64' and sys_platform == 'linux'" },
|
||||
{ name = "psycopg-c", version = "3.3.4", source = { url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-trixie-3.3.4/psycopg_c-3.3.4-cp314-cp314-linux_aarch64.whl" }, marker = "python_full_version == '3.14.*' and implementation_name != 'pypy' and platform_machine == 'aarch64' and sys_platform == 'linux'" },
|
||||
{ name = "psycopg-c", version = "3.3.4", source = { url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-trixie-3.3.4/psycopg_c-3.3.4-cp314-cp314-linux_x86_64.whl" }, marker = "python_full_version == '3.14.*' and implementation_name != 'pypy' and platform_machine == 'x86_64' and sys_platform == 'linux'" },
|
||||
]
|
||||
pool = [
|
||||
{ name = "psycopg-pool" },
|
||||
@@ -3747,7 +3747,7 @@ pool = [
|
||||
|
||||
[[package]]
|
||||
name = "psycopg-c"
|
||||
version = "3.3.8"
|
||||
version = "3.3.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
resolution-markers = [
|
||||
"python_full_version >= '3.15' and sys_platform == 'darwin'",
|
||||
@@ -3757,50 +3757,50 @@ resolution-markers = [
|
||||
"python_full_version < '3.12' and sys_platform == 'darwin'",
|
||||
"python_full_version < '3.12' and sys_platform == 'linux'",
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/5b/23/0074f50899d773aae857af496745cd898da868f8f2d9415591d51c391cec/psycopg_c-3.3.8.tar.gz", hash = "sha256:af81a99b02600e6c2cdf468509c04bb8208500ea60d63f70616e32ea9cb2758f", size = 700571, upload-time = "2026-11-04T09:44:27.694Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/21/7c/c08364f2eab2913e4068b3b955d963e7a3491986a85429990969525def30/psycopg_c-3.3.4.tar.gz", hash = "sha256:ed8106128b2d04359c185fc9641b4409abfce4d0b6fb1d1ff6800646e27f1a22", size = 647111, upload-time = "2026-05-01T23:31:58.032Z" }
|
||||
|
||||
[[package]]
|
||||
name = "psycopg-c"
|
||||
version = "3.3.8"
|
||||
source = { url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-trixie-3.3.8/psycopg_c-3.3.8-cp312-cp312-manylinux_2_38_aarch64.whl" }
|
||||
version = "3.3.4"
|
||||
source = { url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-trixie-3.3.4/psycopg_c-3.3.4-cp312-cp312-linux_aarch64.whl" }
|
||||
resolution-markers = [
|
||||
"python_full_version == '3.12.*' and platform_machine == 'aarch64' and sys_platform == 'linux'",
|
||||
]
|
||||
wheels = [
|
||||
{ url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-trixie-3.3.8/psycopg_c-3.3.8-cp312-cp312-manylinux_2_38_aarch64.whl", hash = "sha256:be9f81019ec26eb09b8852ec9cb156a41d91067bbc2093cee93daabc935068e8" },
|
||||
{ url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-trixie-3.3.4/psycopg_c-3.3.4-cp312-cp312-linux_aarch64.whl", hash = "sha256:44ea0e800dc2126a0d3ce23fac7b96b64db132ed67df3e9ceb011965351ae8dc" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "psycopg-c"
|
||||
version = "3.3.8"
|
||||
source = { url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-trixie-3.3.8/psycopg_c-3.3.8-cp312-cp312-manylinux_2_38_x86_64.whl" }
|
||||
version = "3.3.4"
|
||||
source = { url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-trixie-3.3.4/psycopg_c-3.3.4-cp312-cp312-linux_x86_64.whl" }
|
||||
resolution-markers = [
|
||||
"python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'linux'",
|
||||
]
|
||||
wheels = [
|
||||
{ url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-trixie-3.3.8/psycopg_c-3.3.8-cp312-cp312-manylinux_2_38_x86_64.whl", hash = "sha256:72a24b31600ec991cb75852038de012054b6031b355c95ccfc38680e439559cc" },
|
||||
{ url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-trixie-3.3.4/psycopg_c-3.3.4-cp312-cp312-linux_x86_64.whl", hash = "sha256:c66169fef7be6b46e701dd361036bd88e346e6b8b15dd25528287f807654f097" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "psycopg-c"
|
||||
version = "3.3.8"
|
||||
source = { url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-trixie-3.3.8/psycopg_c-3.3.8-cp314-cp314-manylinux_2_38_aarch64.whl" }
|
||||
version = "3.3.4"
|
||||
source = { url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-trixie-3.3.4/psycopg_c-3.3.4-cp314-cp314-linux_aarch64.whl" }
|
||||
resolution-markers = [
|
||||
"python_full_version == '3.14.*' and platform_machine == 'aarch64' and sys_platform == 'linux'",
|
||||
]
|
||||
wheels = [
|
||||
{ url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-trixie-3.3.8/psycopg_c-3.3.8-cp314-cp314-manylinux_2_38_aarch64.whl", hash = "sha256:dc22c1b4eeee9a2a5e20a8ffbd192e320a2da6fdef5a72f7fa76ed9792de4353" },
|
||||
{ url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-trixie-3.3.4/psycopg_c-3.3.4-cp314-cp314-linux_aarch64.whl", hash = "sha256:00d1cc7b82899046acd68e11c801bbb94d109313ab02333f76c2b59dd531cdc1" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "psycopg-c"
|
||||
version = "3.3.8"
|
||||
source = { url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-trixie-3.3.8/psycopg_c-3.3.8-cp314-cp314-manylinux_2_38_x86_64.whl" }
|
||||
version = "3.3.4"
|
||||
source = { url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-trixie-3.3.4/psycopg_c-3.3.4-cp314-cp314-linux_x86_64.whl" }
|
||||
resolution-markers = [
|
||||
"python_full_version == '3.14.*' and platform_machine == 'x86_64' and sys_platform == 'linux'",
|
||||
]
|
||||
wheels = [
|
||||
{ url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-trixie-3.3.8/psycopg_c-3.3.8-cp314-cp314-manylinux_2_38_x86_64.whl", hash = "sha256:d6298166033ad1024d2ba6d0805352ba7042ccd7619771a0c164d1e370cf7976" },
|
||||
{ url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-trixie-3.3.4/psycopg_c-3.3.4-cp314-cp314-linux_x86_64.whl", hash = "sha256:d02a564c6a3e2c1957b3b5d2dff77e34a274f8551f6bdbcef47523eba59a627a" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
Reference in new issue
Block a user