mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2026-02-24 00:59:35 -06:00
Compare commits
1 Commits
dev
...
dependabot
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6cb5f48366 |
2
.github/workflows/ci-backend.yml
vendored
2
.github/workflows/ci-backend.yml
vendored
@@ -129,7 +129,6 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
uv pip list
|
uv pip list
|
||||||
- name: Check typing (pyrefly)
|
- name: Check typing (pyrefly)
|
||||||
continue-on-error: true
|
|
||||||
run: |
|
run: |
|
||||||
uv run pyrefly \
|
uv run pyrefly \
|
||||||
check \
|
check \
|
||||||
@@ -144,7 +143,6 @@ jobs:
|
|||||||
${{ runner.os }}-mypy-py${{ env.DEFAULT_PYTHON }}-
|
${{ runner.os }}-mypy-py${{ env.DEFAULT_PYTHON }}-
|
||||||
${{ runner.os }}-mypy-
|
${{ runner.os }}-mypy-
|
||||||
- name: Check typing (mypy)
|
- name: Check typing (mypy)
|
||||||
continue-on-error: true
|
|
||||||
run: |
|
run: |
|
||||||
uv run mypy \
|
uv run mypy \
|
||||||
--show-error-codes \
|
--show-error-codes \
|
||||||
|
|||||||
@@ -700,11 +700,15 @@ src/documents/signals/handlers.py:0: error: Function is missing a type annotatio
|
|||||||
src/documents/signals/handlers.py:0: error: Function is missing a type annotation for one or more arguments [no-untyped-def]
|
src/documents/signals/handlers.py:0: error: Function is missing a type annotation for one or more arguments [no-untyped-def]
|
||||||
src/documents/signals/handlers.py:0: error: Function is missing a type annotation for one or more arguments [no-untyped-def]
|
src/documents/signals/handlers.py:0: error: Function is missing a type annotation for one or more arguments [no-untyped-def]
|
||||||
src/documents/signals/handlers.py:0: error: Function is missing a type annotation for one or more arguments [no-untyped-def]
|
src/documents/signals/handlers.py:0: error: Function is missing a type annotation for one or more arguments [no-untyped-def]
|
||||||
|
src/documents/signals/handlers.py:0: error: Function is missing a type annotation for one or more arguments [no-untyped-def]
|
||||||
|
src/documents/signals/handlers.py:0: error: Incompatible return value type (got "tuple[DocumentMetadataOverrides | None, str]", expected "tuple[DocumentMetadataOverrides, str] | None") [return-value]
|
||||||
src/documents/signals/handlers.py:0: error: Incompatible types in assignment (expression has type "list[Tag]", variable has type "set[Tag]") [assignment]
|
src/documents/signals/handlers.py:0: error: Incompatible types in assignment (expression has type "list[Tag]", variable has type "set[Tag]") [assignment]
|
||||||
src/documents/signals/handlers.py:0: error: Incompatible types in assignment (expression has type "tuple[Any, Any, Any]", variable has type "tuple[Any, Any]") [assignment]
|
src/documents/signals/handlers.py:0: error: Incompatible types in assignment (expression has type "tuple[Any, Any, Any]", variable has type "tuple[Any, Any]") [assignment]
|
||||||
|
src/documents/signals/handlers.py:0: error: Item "ConsumableDocument" of "Document | ConsumableDocument" has no attribute "refresh_from_db" [union-attr]
|
||||||
src/documents/signals/handlers.py:0: error: Item "ConsumableDocument" of "Document | ConsumableDocument" has no attribute "save" [union-attr]
|
src/documents/signals/handlers.py:0: error: Item "ConsumableDocument" of "Document | ConsumableDocument" has no attribute "save" [union-attr]
|
||||||
src/documents/signals/handlers.py:0: error: Item "ConsumableDocument" of "Document | ConsumableDocument" has no attribute "source_path" [union-attr]
|
src/documents/signals/handlers.py:0: error: Item "ConsumableDocument" of "Document | ConsumableDocument" has no attribute "source_path" [union-attr]
|
||||||
src/documents/signals/handlers.py:0: error: Item "ConsumableDocument" of "Document | ConsumableDocument" has no attribute "tags" [union-attr]
|
src/documents/signals/handlers.py:0: error: Item "ConsumableDocument" of "Document | ConsumableDocument" has no attribute "tags" [union-attr]
|
||||||
|
src/documents/signals/handlers.py:0: error: Item "ConsumableDocument" of "Document | ConsumableDocument" has no attribute "tags" [union-attr]
|
||||||
src/documents/signals/handlers.py:0: error: Item "ConsumableDocument" of "Document | ConsumableDocument" has no attribute "title" [union-attr]
|
src/documents/signals/handlers.py:0: error: Item "ConsumableDocument" of "Document | ConsumableDocument" has no attribute "title" [union-attr]
|
||||||
src/documents/signals/handlers.py:0: error: Item "None" of "Any | None" has no attribute "get" [union-attr]
|
src/documents/signals/handlers.py:0: error: Item "None" of "Any | None" has no attribute "get" [union-attr]
|
||||||
src/documents/signals/handlers.py:0: error: Item "None" of "Any | None" has no attribute "get" [union-attr]
|
src/documents/signals/handlers.py:0: error: Item "None" of "Any | None" has no attribute "get" [union-attr]
|
||||||
|
|||||||
@@ -784,17 +784,9 @@ below.
|
|||||||
|
|
||||||
### Document Splitting {#document-splitting}
|
### Document Splitting {#document-splitting}
|
||||||
|
|
||||||
If document splitting is enabled, Paperless splits _after_ a separator barcode by default.
|
When enabled, Paperless will look for a barcode with the configured value and create a new document
|
||||||
This means:
|
starting from the next page. The page with the barcode on it will _not_ be retained. It
|
||||||
|
is expected to be a page existing only for triggering the split.
|
||||||
- any page containing the configured separator barcode starts a new document, starting with the **next** page
|
|
||||||
- pages containing the separator barcode are discarded
|
|
||||||
|
|
||||||
This is intended for dedicated separator sheets such as PATCH-T pages.
|
|
||||||
|
|
||||||
If [`PAPERLESS_CONSUMER_BARCODE_RETAIN_SPLIT_PAGES`](configuration.md#PAPERLESS_CONSUMER_BARCODE_RETAIN_SPLIT_PAGES)
|
|
||||||
is enabled, the page containing the separator barcode is retained instead. In this mode,
|
|
||||||
each page containing the separator barcode becomes the **first** page of a new document.
|
|
||||||
|
|
||||||
### Archive Serial Number Assignment
|
### Archive Serial Number Assignment
|
||||||
|
|
||||||
@@ -803,9 +795,8 @@ archive serial number, allowing quick reference back to the original, paper docu
|
|||||||
|
|
||||||
If document splitting via barcode is also enabled, documents will be split when an ASN
|
If document splitting via barcode is also enabled, documents will be split when an ASN
|
||||||
barcode is located. However, differing from the splitting, the page with the
|
barcode is located. However, differing from the splitting, the page with the
|
||||||
barcode _will_ be retained. Each detected ASN barcode starts a new document _starting with
|
barcode _will_ be retained. This allows application of a barcode to any page, including
|
||||||
that page_. This allows placing ASN barcodes on content pages that should remain part of
|
one which holds data to keep in the document.
|
||||||
the document.
|
|
||||||
|
|
||||||
### Tag Assignment
|
### Tag Assignment
|
||||||
|
|
||||||
|
|||||||
@@ -564,18 +564,6 @@ For security reasons, webhooks can be limited to specific ports and disallowed f
|
|||||||
[configuration settings](configuration.md#workflow-webhooks) to change this behavior. If you are allowing non-admins to create workflows,
|
[configuration settings](configuration.md#workflow-webhooks) to change this behavior. If you are allowing non-admins to create workflows,
|
||||||
you may want to adjust these settings to prevent abuse.
|
you may want to adjust these settings to prevent abuse.
|
||||||
|
|
||||||
##### Move to Trash {#workflow-action-move-to-trash}
|
|
||||||
|
|
||||||
"Move to Trash" actions move the document to the trash. The document can be restored
|
|
||||||
from the trash until the trash is emptied (after the configured delay or manually).
|
|
||||||
|
|
||||||
The "Move to Trash" action will always be executed at the end of the workflow run,
|
|
||||||
regardless of its position in the action list. After a "Move to Trash" action is executed
|
|
||||||
no other workflow will be executed on the document.
|
|
||||||
|
|
||||||
If a "Move to Trash" action is executed in a consume pipeline, the consumption
|
|
||||||
will be aborted and the file will be deleted.
|
|
||||||
|
|
||||||
#### Workflow placeholders
|
#### Workflow placeholders
|
||||||
|
|
||||||
Titles and webhook payloads can be generated by workflows using [Jinja templates](https://jinja.palletsprojects.com/en/3.1.x/templates/).
|
Titles and webhook payloads can be generated by workflows using [Jinja templates](https://jinja.palletsprojects.com/en/3.1.x/templates/).
|
||||||
|
|||||||
@@ -5355,13 +5355,6 @@
|
|||||||
<context context-type="linenumber">445</context>
|
<context context-type="linenumber">445</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="7902569198692046993" datatype="html">
|
|
||||||
<source>The document will be moved to the trash at the end of the workflow run.</source>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
|
||||||
<context context-type="linenumber">454</context>
|
|
||||||
</context-group>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="4626030417479279989" datatype="html">
|
<trans-unit id="4626030417479279989" datatype="html">
|
||||||
<source>Consume Folder</source>
|
<source>Consume Folder</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
@@ -5464,124 +5457,109 @@
|
|||||||
<context context-type="linenumber">144</context>
|
<context context-type="linenumber">144</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="2048798344356757326" datatype="html">
|
|
||||||
<source>Move to trash</source>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
|
|
||||||
<context context-type="linenumber">148</context>
|
|
||||||
</context-group>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
|
||||||
<context context-type="linenumber">1087</context>
|
|
||||||
</context-group>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
|
||||||
<context context-type="linenumber">760</context>
|
|
||||||
</context-group>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="4522609911791833187" datatype="html">
|
<trans-unit id="4522609911791833187" datatype="html">
|
||||||
<source>Has any of these tags</source>
|
<source>Has any of these tags</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
|
||||||
<context context-type="linenumber">217</context>
|
<context context-type="linenumber">213</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="4166903555074156852" datatype="html">
|
<trans-unit id="4166903555074156852" datatype="html">
|
||||||
<source>Has all of these tags</source>
|
<source>Has all of these tags</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
|
||||||
<context context-type="linenumber">224</context>
|
<context context-type="linenumber">220</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="6624363795312783141" datatype="html">
|
<trans-unit id="6624363795312783141" datatype="html">
|
||||||
<source>Does not have these tags</source>
|
<source>Does not have these tags</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
|
||||||
<context context-type="linenumber">231</context>
|
<context context-type="linenumber">227</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="7168528512669831184" datatype="html">
|
<trans-unit id="7168528512669831184" datatype="html">
|
||||||
<source>Has any of these correspondents</source>
|
<source>Has any of these correspondents</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
|
||||||
<context context-type="linenumber">238</context>
|
<context context-type="linenumber">234</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="5281365940563983618" datatype="html">
|
<trans-unit id="5281365940563983618" datatype="html">
|
||||||
<source>Has correspondent</source>
|
<source>Has correspondent</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
|
||||||
<context context-type="linenumber">246</context>
|
<context context-type="linenumber">242</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="6884498632428600393" datatype="html">
|
<trans-unit id="6884498632428600393" datatype="html">
|
||||||
<source>Does not have correspondents</source>
|
<source>Does not have correspondents</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
|
||||||
<context context-type="linenumber">254</context>
|
<context context-type="linenumber">250</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="4806713133917046341" datatype="html">
|
<trans-unit id="4806713133917046341" datatype="html">
|
||||||
<source>Has document type</source>
|
<source>Has document type</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
|
||||||
<context context-type="linenumber">262</context>
|
<context context-type="linenumber">258</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="8801397520369995032" datatype="html">
|
<trans-unit id="8801397520369995032" datatype="html">
|
||||||
<source>Has any of these document types</source>
|
<source>Has any of these document types</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
|
||||||
<context context-type="linenumber">270</context>
|
<context context-type="linenumber">266</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="1507843981661822403" datatype="html">
|
<trans-unit id="1507843981661822403" datatype="html">
|
||||||
<source>Does not have document types</source>
|
<source>Does not have document types</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
|
||||||
<context context-type="linenumber">278</context>
|
<context context-type="linenumber">274</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="4277260190522078330" datatype="html">
|
<trans-unit id="4277260190522078330" datatype="html">
|
||||||
<source>Has storage path</source>
|
<source>Has storage path</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
|
||||||
<context context-type="linenumber">286</context>
|
<context context-type="linenumber">282</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="8858580062214623097" datatype="html">
|
<trans-unit id="8858580062214623097" datatype="html">
|
||||||
<source>Has any of these storage paths</source>
|
<source>Has any of these storage paths</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
|
||||||
<context context-type="linenumber">294</context>
|
<context context-type="linenumber">290</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="6070943364927280151" datatype="html">
|
<trans-unit id="6070943364927280151" datatype="html">
|
||||||
<source>Does not have storage paths</source>
|
<source>Does not have storage paths</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
|
||||||
<context context-type="linenumber">302</context>
|
<context context-type="linenumber">298</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="6250799006816371860" datatype="html">
|
<trans-unit id="6250799006816371860" datatype="html">
|
||||||
<source>Matches custom field query</source>
|
<source>Matches custom field query</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
|
||||||
<context context-type="linenumber">310</context>
|
<context context-type="linenumber">306</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="3138206142174978019" datatype="html">
|
<trans-unit id="3138206142174978019" datatype="html">
|
||||||
<source>Create new workflow</source>
|
<source>Create new workflow</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
|
||||||
<context context-type="linenumber">539</context>
|
<context context-type="linenumber">535</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="5996779210524133604" datatype="html">
|
<trans-unit id="5996779210524133604" datatype="html">
|
||||||
<source>Edit workflow</source>
|
<source>Edit workflow</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
|
||||||
<context context-type="linenumber">543</context>
|
<context context-type="linenumber">539</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="5457837313196342910" datatype="html">
|
<trans-unit id="5457837313196342910" datatype="html">
|
||||||
@@ -7795,6 +7773,17 @@
|
|||||||
<context context-type="linenumber">758</context>
|
<context context-type="linenumber">758</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="2048798344356757326" datatype="html">
|
||||||
|
<source>Move to trash</source>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
||||||
|
<context context-type="linenumber">1087</context>
|
||||||
|
</context-group>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||||
|
<context context-type="linenumber">760</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
<trans-unit id="7295637485862454066" datatype="html">
|
<trans-unit id="7295637485862454066" datatype="html">
|
||||||
<source>Error deleting document</source>
|
<source>Error deleting document</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
@@ -8501,7 +8490,7 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context>
|
||||||
<context context-type="linenumber">323</context>
|
<context context-type="linenumber">315</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/manage/document-attributes/document-attributes.component.html</context>
|
<context context-type="sourcefile">src/app/components/manage/document-attributes/document-attributes.component.html</context>
|
||||||
@@ -8516,7 +8505,7 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context>
|
||||||
<context context-type="linenumber">316</context>
|
<context context-type="linenumber">308</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/manage/document-attributes/document-attributes.component.html</context>
|
<context context-type="sourcefile">src/app/components/manage/document-attributes/document-attributes.component.html</context>
|
||||||
@@ -8782,49 +8771,49 @@
|
|||||||
<source>Reset filters / selection</source>
|
<source>Reset filters / selection</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context>
|
||||||
<context context-type="linenumber">304</context>
|
<context context-type="linenumber">296</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="4135055128446167640" datatype="html">
|
<trans-unit id="4135055128446167640" datatype="html">
|
||||||
<source>Open first [selected] document</source>
|
<source>Open first [selected] document</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context>
|
||||||
<context context-type="linenumber">332</context>
|
<context context-type="linenumber">324</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="3629960544875360046" datatype="html">
|
<trans-unit id="3629960544875360046" datatype="html">
|
||||||
<source>Previous page</source>
|
<source>Previous page</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context>
|
||||||
<context context-type="linenumber">348</context>
|
<context context-type="linenumber">340</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="3337301694210287595" datatype="html">
|
<trans-unit id="3337301694210287595" datatype="html">
|
||||||
<source>Next page</source>
|
<source>Next page</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context>
|
||||||
<context context-type="linenumber">360</context>
|
<context context-type="linenumber">352</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="2155249406916744630" datatype="html">
|
<trans-unit id="2155249406916744630" datatype="html">
|
||||||
<source>View "<x id="PH" equiv-text="this.list.activeSavedViewTitle"/>" saved successfully.</source>
|
<source>View "<x id="PH" equiv-text="this.list.activeSavedViewTitle"/>" saved successfully.</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context>
|
||||||
<context context-type="linenumber">393</context>
|
<context context-type="linenumber">385</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="4646273665293421938" datatype="html">
|
<trans-unit id="4646273665293421938" datatype="html">
|
||||||
<source>Failed to save view "<x id="PH" equiv-text="this.list.activeSavedViewTitle"/>".</source>
|
<source>Failed to save view "<x id="PH" equiv-text="this.list.activeSavedViewTitle"/>".</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context>
|
||||||
<context context-type="linenumber">399</context>
|
<context context-type="linenumber">391</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="6837554170707123455" datatype="html">
|
<trans-unit id="6837554170707123455" datatype="html">
|
||||||
<source>View "<x id="PH" equiv-text="savedView.name"/>" created successfully.</source>
|
<source>View "<x id="PH" equiv-text="savedView.name"/>" created successfully.</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context>
|
||||||
<context context-type="linenumber">445</context>
|
<context context-type="linenumber">437</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="739880801667335279" datatype="html">
|
<trans-unit id="739880801667335279" datatype="html">
|
||||||
|
|||||||
@@ -448,13 +448,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@case (WorkflowActionType.MoveToTrash) {
|
|
||||||
<div class="row">
|
|
||||||
<div class="col">
|
|
||||||
<p class="text-muted small" i18n>The document will be moved to the trash at the end of the workflow run.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|||||||
@@ -143,10 +143,6 @@ export const WORKFLOW_ACTION_OPTIONS = [
|
|||||||
id: WorkflowActionType.PasswordRemoval,
|
id: WorkflowActionType.PasswordRemoval,
|
||||||
name: $localize`Password removal`,
|
name: $localize`Password removal`,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
id: WorkflowActionType.MoveToTrash,
|
|
||||||
name: $localize`Move to trash`,
|
|
||||||
},
|
|
||||||
]
|
]
|
||||||
|
|
||||||
export enum TriggerFilterType {
|
export enum TriggerFilterType {
|
||||||
|
|||||||
@@ -117,7 +117,7 @@
|
|||||||
</pngx-page-header>
|
</pngx-page-header>
|
||||||
|
|
||||||
<div class="row sticky-top py-3 mt-n2 mt-md-n3 bg-body">
|
<div class="row sticky-top py-3 mt-n2 mt-md-n3 bg-body">
|
||||||
<pngx-filter-editor [hidden]="isBulkEditing" [disabled]="isBulkEditing" [filterRules]="list.filterRules" (filterRulesChange)="onFilterRulesChange($event)" (resetFilterRules)="onFilterRulesReset($event)" [unmodifiedFilterRules]="unmodifiedFilterRules" [selectionData]="list.selectionData" #filterEditor></pngx-filter-editor>
|
<pngx-filter-editor [hidden]="isBulkEditing" [disabled]="isBulkEditing" [(filterRules)]="list.filterRules" [unmodifiedFilterRules]="unmodifiedFilterRules" [selectionData]="list.selectionData" #filterEditor></pngx-filter-editor>
|
||||||
<pngx-bulk-editor [hidden]="!isBulkEditing" [disabled]="!isBulkEditing"></pngx-bulk-editor>
|
<pngx-bulk-editor [hidden]="!isBulkEditing" [disabled]="!isBulkEditing"></pngx-bulk-editor>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -147,21 +147,21 @@ describe('DocumentListComponent', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should show score sort fields on fulltext queries', () => {
|
it('should show score sort fields on fulltext queries', () => {
|
||||||
documentListService.setFilterRules([
|
documentListService.filterRules = [
|
||||||
{
|
{
|
||||||
rule_type: FILTER_HAS_TAGS_ANY,
|
rule_type: FILTER_HAS_TAGS_ANY,
|
||||||
value: '10',
|
value: '10',
|
||||||
},
|
},
|
||||||
])
|
]
|
||||||
fixture.detectChanges()
|
fixture.detectChanges()
|
||||||
expect(component.getSortFields()).toEqual(documentListService.sortFields)
|
expect(component.getSortFields()).toEqual(documentListService.sortFields)
|
||||||
|
|
||||||
documentListService.setFilterRules([
|
documentListService.filterRules = [
|
||||||
{
|
{
|
||||||
rule_type: FILTER_FULLTEXT_QUERY,
|
rule_type: FILTER_FULLTEXT_QUERY,
|
||||||
value: 'foo',
|
value: 'foo',
|
||||||
},
|
},
|
||||||
])
|
]
|
||||||
fixture.detectChanges()
|
fixture.detectChanges()
|
||||||
expect(component.getSortFields()).toEqual(
|
expect(component.getSortFields()).toEqual(
|
||||||
documentListService.sortFieldsFullText
|
documentListService.sortFieldsFullText
|
||||||
@@ -170,12 +170,12 @@ describe('DocumentListComponent', () => {
|
|||||||
|
|
||||||
it('should determine if filtered, support reset', () => {
|
it('should determine if filtered, support reset', () => {
|
||||||
fixture.detectChanges()
|
fixture.detectChanges()
|
||||||
documentListService.setFilterRules([
|
documentListService.filterRules = [
|
||||||
{
|
{
|
||||||
rule_type: FILTER_HAS_TAGS_ANY,
|
rule_type: FILTER_HAS_TAGS_ANY,
|
||||||
value: '10',
|
value: '10',
|
||||||
},
|
},
|
||||||
])
|
]
|
||||||
documentListService.isReloading = false
|
documentListService.isReloading = false
|
||||||
fixture.detectChanges()
|
fixture.detectChanges()
|
||||||
expect(component.isFiltered).toBeTruthy()
|
expect(component.isFiltered).toBeTruthy()
|
||||||
@@ -185,20 +185,6 @@ describe('DocumentListComponent', () => {
|
|||||||
expect(fixture.nativeElement.textContent.match(/Reset/g)).toHaveLength(1)
|
expect(fixture.nativeElement.textContent.match(/Reset/g)).toHaveLength(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should apply filter rule changes via list service', () => {
|
|
||||||
const setFilterRulesSpy = jest.spyOn(documentListService, 'setFilterRules')
|
|
||||||
const rules = [{ rule_type: FILTER_HAS_TAGS_ANY, value: '10' }]
|
|
||||||
component.onFilterRulesChange(rules)
|
|
||||||
expect(setFilterRulesSpy).toHaveBeenCalledWith(rules)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should reset filter rules to page one via list service', () => {
|
|
||||||
const setFilterRulesSpy = jest.spyOn(documentListService, 'setFilterRules')
|
|
||||||
const rules = [{ rule_type: FILTER_HAS_TAGS_ANY, value: '10' }]
|
|
||||||
component.onFilterRulesReset(rules)
|
|
||||||
expect(setFilterRulesSpy).toHaveBeenCalledWith(rules, true)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should load saved view from URL', () => {
|
it('should load saved view from URL', () => {
|
||||||
const view: SavedView = {
|
const view: SavedView = {
|
||||||
id: 10,
|
id: 10,
|
||||||
@@ -231,7 +217,7 @@ describe('DocumentListComponent', () => {
|
|||||||
.spyOn(activatedRoute, 'paramMap', 'get')
|
.spyOn(activatedRoute, 'paramMap', 'get')
|
||||||
.mockReturnValue(of(convertToParamMap(queryParams)))
|
.mockReturnValue(of(convertToParamMap(queryParams)))
|
||||||
activatedRoute.snapshot.queryParams = queryParams
|
activatedRoute.snapshot.queryParams = queryParams
|
||||||
component.ngOnInit()
|
fixture.detectChanges()
|
||||||
expect(getSavedViewSpy).toHaveBeenCalledWith(view.id)
|
expect(getSavedViewSpy).toHaveBeenCalledWith(view.id)
|
||||||
expect(activateSavedViewSpy).toHaveBeenCalledWith(
|
expect(activateSavedViewSpy).toHaveBeenCalledWith(
|
||||||
view,
|
view,
|
||||||
|
|||||||
@@ -212,14 +212,6 @@ export class DocumentListComponent
|
|||||||
this.list.setSort(event.column, event.reverse)
|
this.list.setSort(event.column, event.reverse)
|
||||||
}
|
}
|
||||||
|
|
||||||
onFilterRulesChange(filterRules: FilterRule[]) {
|
|
||||||
this.list.setFilterRules(filterRules)
|
|
||||||
}
|
|
||||||
|
|
||||||
onFilterRulesReset(filterRules: FilterRule[]) {
|
|
||||||
this.list.setFilterRules(filterRules, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
get isBulkEditing(): boolean {
|
get isBulkEditing(): boolean {
|
||||||
return this.list.selected.size > 0
|
return this.list.selected.size > 0
|
||||||
}
|
}
|
||||||
@@ -308,7 +300,7 @@ export class DocumentListComponent
|
|||||||
if (this.list.selected.size > 0) {
|
if (this.list.selected.size > 0) {
|
||||||
this.list.selectNone()
|
this.list.selectNone()
|
||||||
} else if (this.isFiltered) {
|
} else if (this.isFiltered) {
|
||||||
this.resetFilters()
|
this.filterEditor.resetSelected()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -2107,22 +2107,6 @@ describe('FilterEditorComponent', () => {
|
|||||||
expect(component.filterRules).toEqual(rules)
|
expect(component.filterRules).toEqual(rules)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should emit reset filter rules when resetting', () => {
|
|
||||||
const rules = [{ rule_type: FILTER_HAS_TAGS_ANY, value: '2' }]
|
|
||||||
component.unmodifiedFilterRules = rules
|
|
||||||
component.filterRules = [
|
|
||||||
{ rule_type: FILTER_DOES_NOT_HAVE_TAG, value: '2' },
|
|
||||||
]
|
|
||||||
|
|
||||||
const resetFilterRulesSpy = jest.spyOn(component.resetFilterRules, 'next')
|
|
||||||
const filterRulesChangeSpy = jest.spyOn(component.filterRulesChange, 'next')
|
|
||||||
|
|
||||||
component.resetSelected()
|
|
||||||
|
|
||||||
expect(resetFilterRulesSpy).toHaveBeenCalledWith(rules)
|
|
||||||
expect(filterRulesChangeSpy).not.toHaveBeenCalled()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should support resetting text field', () => {
|
it('should support resetting text field', () => {
|
||||||
component.textFilter = 'foo'
|
component.textFilter = 'foo'
|
||||||
component.resetTextField()
|
component.resetTextField()
|
||||||
|
|||||||
@@ -1101,9 +1101,6 @@ export class FilterEditorComponent
|
|||||||
@Output()
|
@Output()
|
||||||
filterRulesChange = new EventEmitter<FilterRule[]>()
|
filterRulesChange = new EventEmitter<FilterRule[]>()
|
||||||
|
|
||||||
@Output()
|
|
||||||
resetFilterRules = new EventEmitter<FilterRule[]>()
|
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
set selectionData(selectionData: SelectionData) {
|
set selectionData(selectionData: SelectionData) {
|
||||||
this.tagDocumentCounts = selectionData?.selected_tags ?? null
|
this.tagDocumentCounts = selectionData?.selected_tags ?? null
|
||||||
@@ -1247,7 +1244,7 @@ export class FilterEditorComponent
|
|||||||
this.textFilterTarget = TEXT_FILTER_TARGET_TITLE_CONTENT
|
this.textFilterTarget = TEXT_FILTER_TARGET_TITLE_CONTENT
|
||||||
this.documentService.searchQuery = ''
|
this.documentService.searchQuery = ''
|
||||||
this.filterRules = this._unmodifiedFilterRules
|
this.filterRules = this._unmodifiedFilterRules
|
||||||
this.resetFilterRules.next(this.filterRules)
|
this.updateRules()
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleTag(tagId: number) {
|
toggleTag(tagId: number) {
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ export enum WorkflowActionType {
|
|||||||
Email = 3,
|
Email = 3,
|
||||||
Webhook = 4,
|
Webhook = 4,
|
||||||
PasswordRemoval = 5,
|
PasswordRemoval = 5,
|
||||||
MoveToTrash = 6,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface WorkflowActionEmail extends ObjectWithId {
|
export interface WorkflowActionEmail extends ObjectWithId {
|
||||||
|
|||||||
@@ -164,7 +164,7 @@ describe('DocumentListViewService', () => {
|
|||||||
value: tags__id__in,
|
value: tags__id__in,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
documentListViewService.setFilterRules(filterRulesAny)
|
documentListViewService.filterRules = filterRulesAny
|
||||||
let req = httpTestingController.expectOne(
|
let req = httpTestingController.expectOne(
|
||||||
`${environment.apiBaseUrl}documents/?page=1&page_size=50&ordering=-created&truncate_content=true&tags__id__in=${tags__id__in}`
|
`${environment.apiBaseUrl}documents/?page=1&page_size=50&ordering=-created&truncate_content=true&tags__id__in=${tags__id__in}`
|
||||||
)
|
)
|
||||||
@@ -178,7 +178,7 @@ describe('DocumentListViewService', () => {
|
|||||||
)
|
)
|
||||||
expect(req.request.method).toEqual('GET')
|
expect(req.request.method).toEqual('GET')
|
||||||
// reset the list
|
// reset the list
|
||||||
documentListViewService.setFilterRules([])
|
documentListViewService.filterRules = []
|
||||||
req = httpTestingController.expectOne(
|
req = httpTestingController.expectOne(
|
||||||
`${environment.apiBaseUrl}documents/?page=1&page_size=50&ordering=-created&truncate_content=true`
|
`${environment.apiBaseUrl}documents/?page=1&page_size=50&ordering=-created&truncate_content=true`
|
||||||
)
|
)
|
||||||
@@ -210,7 +210,7 @@ describe('DocumentListViewService', () => {
|
|||||||
value: tags__id__in,
|
value: tags__id__in,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
documentListViewService.setFilterRules(filterRulesAny)
|
documentListViewService.filterRules = filterRulesAny
|
||||||
let req = httpTestingController.expectOne(
|
let req = httpTestingController.expectOne(
|
||||||
`${environment.apiBaseUrl}documents/?page=1&page_size=50&ordering=-created&truncate_content=true&tags__id__in=${tags__id__in}`
|
`${environment.apiBaseUrl}documents/?page=1&page_size=50&ordering=-created&truncate_content=true&tags__id__in=${tags__id__in}`
|
||||||
)
|
)
|
||||||
@@ -218,7 +218,7 @@ describe('DocumentListViewService', () => {
|
|||||||
req.flush('Generic error', { status: 404, statusText: 'Unexpected error' })
|
req.flush('Generic error', { status: 404, statusText: 'Unexpected error' })
|
||||||
expect(documentListViewService.error).toEqual('Generic error')
|
expect(documentListViewService.error).toEqual('Generic error')
|
||||||
// reset the list
|
// reset the list
|
||||||
documentListViewService.setFilterRules([])
|
documentListViewService.filterRules = []
|
||||||
req = httpTestingController.expectOne(
|
req = httpTestingController.expectOne(
|
||||||
`${environment.apiBaseUrl}documents/?page=1&page_size=50&ordering=-created&truncate_content=true`
|
`${environment.apiBaseUrl}documents/?page=1&page_size=50&ordering=-created&truncate_content=true`
|
||||||
)
|
)
|
||||||
@@ -295,41 +295,13 @@ describe('DocumentListViewService', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should use filter rules to update query params', () => {
|
it('should use filter rules to update query params', () => {
|
||||||
documentListViewService.setFilterRules(filterRules)
|
documentListViewService.filterRules = filterRules
|
||||||
const req = httpTestingController.expectOne(
|
const req = httpTestingController.expectOne(
|
||||||
`${environment.apiBaseUrl}documents/?page=${documentListViewService.currentPage}&page_size=${documentListViewService.pageSize}&ordering=-created&truncate_content=true&tags__id__all=${tags__id__all}`
|
`${environment.apiBaseUrl}documents/?page=${documentListViewService.currentPage}&page_size=${documentListViewService.pageSize}&ordering=-created&truncate_content=true&tags__id__all=${tags__id__all}`
|
||||||
)
|
)
|
||||||
expect(req.request.method).toEqual('GET')
|
expect(req.request.method).toEqual('GET')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should support setting filter rules and resetting to page one', () => {
|
|
||||||
documentListViewService.currentPage = 2
|
|
||||||
let req = httpTestingController.expectOne((request) =>
|
|
||||||
request.urlWithParams.startsWith(
|
|
||||||
`${environment.apiBaseUrl}documents/?page=2&page_size=50&ordering=-created&truncate_content=true`
|
|
||||||
)
|
|
||||||
)
|
|
||||||
expect(req.request.method).toEqual('GET')
|
|
||||||
req.flush(full_results)
|
|
||||||
req = httpTestingController.expectOne(
|
|
||||||
`${environment.apiBaseUrl}documents/selection_data/`
|
|
||||||
)
|
|
||||||
req.flush([])
|
|
||||||
|
|
||||||
documentListViewService.setFilterRules(filterRules, true)
|
|
||||||
|
|
||||||
const filteredReqs = httpTestingController.match(
|
|
||||||
`${environment.apiBaseUrl}documents/?page=1&page_size=50&ordering=-created&truncate_content=true&tags__id__all=${tags__id__all}`
|
|
||||||
)
|
|
||||||
expect(filteredReqs).toHaveLength(1)
|
|
||||||
filteredReqs[0].flush(full_results)
|
|
||||||
req = httpTestingController.expectOne(
|
|
||||||
`${environment.apiBaseUrl}documents/selection_data/`
|
|
||||||
)
|
|
||||||
req.flush([])
|
|
||||||
expect(documentListViewService.currentPage).toEqual(1)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should support quick filter', () => {
|
it('should support quick filter', () => {
|
||||||
documentListViewService.quickFilter(filterRules)
|
documentListViewService.quickFilter(filterRules)
|
||||||
const req = httpTestingController.expectOne(
|
const req = httpTestingController.expectOne(
|
||||||
@@ -364,7 +336,7 @@ describe('DocumentListViewService', () => {
|
|||||||
req = httpTestingController.expectOne(
|
req = httpTestingController.expectOne(
|
||||||
`${environment.apiBaseUrl}documents/?page=1&page_size=50&ordering=-added&truncate_content=true&tags__id__all=9`
|
`${environment.apiBaseUrl}documents/?page=1&page_size=50&ordering=-added&truncate_content=true&tags__id__all=9`
|
||||||
)
|
)
|
||||||
documentListViewService.setFilterRules([])
|
documentListViewService.filterRules = []
|
||||||
req = httpTestingController.expectOne(
|
req = httpTestingController.expectOne(
|
||||||
`${environment.apiBaseUrl}documents/?page=1&page_size=50&ordering=-added&truncate_content=true`
|
`${environment.apiBaseUrl}documents/?page=1&page_size=50&ordering=-added&truncate_content=true`
|
||||||
)
|
)
|
||||||
@@ -376,7 +348,7 @@ describe('DocumentListViewService', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should support navigating next / previous', () => {
|
it('should support navigating next / previous', () => {
|
||||||
documentListViewService.setFilterRules([])
|
documentListViewService.filterRules = []
|
||||||
let req = httpTestingController.expectOne(
|
let req = httpTestingController.expectOne(
|
||||||
`${environment.apiBaseUrl}documents/?page=1&page_size=50&ordering=-created&truncate_content=true`
|
`${environment.apiBaseUrl}documents/?page=1&page_size=50&ordering=-created&truncate_content=true`
|
||||||
)
|
)
|
||||||
@@ -586,7 +558,7 @@ describe('DocumentListViewService', () => {
|
|||||||
req.flush(full_results)
|
req.flush(full_results)
|
||||||
expect(documentListViewService.selected.size).toEqual(6)
|
expect(documentListViewService.selected.size).toEqual(6)
|
||||||
|
|
||||||
documentListViewService.setFilterRules(filterRules)
|
documentListViewService.filterRules = filterRules
|
||||||
httpTestingController.expectOne(
|
httpTestingController.expectOne(
|
||||||
`${environment.apiBaseUrl}documents/?page=1&page_size=50&ordering=-created&truncate_content=true&tags__id__all=9`
|
`${environment.apiBaseUrl}documents/?page=1&page_size=50&ordering=-created&truncate_content=true&tags__id__all=9`
|
||||||
)
|
)
|
||||||
@@ -620,7 +592,7 @@ describe('DocumentListViewService', () => {
|
|||||||
|
|
||||||
documentListViewService.loadSavedView(view2)
|
documentListViewService.loadSavedView(view2)
|
||||||
expect(documentListViewService.sortField).toEqual('score')
|
expect(documentListViewService.sortField).toEqual('score')
|
||||||
documentListViewService.setFilterRules([])
|
documentListViewService.filterRules = []
|
||||||
expect(documentListViewService.sortField).toEqual('created')
|
expect(documentListViewService.sortField).toEqual('created')
|
||||||
httpTestingController.expectOne(
|
httpTestingController.expectOne(
|
||||||
`${environment.apiBaseUrl}documents/?page=1&page_size=50&ordering=-created&truncate_content=true`
|
`${environment.apiBaseUrl}documents/?page=1&page_size=50&ordering=-created&truncate_content=true`
|
||||||
|
|||||||
@@ -342,7 +342,7 @@ export class DocumentListViewService {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
setFilterRules(filterRules: FilterRule[], resetPage: boolean = false) {
|
set filterRules(filterRules: FilterRule[]) {
|
||||||
if (
|
if (
|
||||||
!isFullTextFilterRule(filterRules) &&
|
!isFullTextFilterRule(filterRules) &&
|
||||||
this.activeListViewState.sortField == 'score'
|
this.activeListViewState.sortField == 'score'
|
||||||
@@ -350,9 +350,6 @@ export class DocumentListViewService {
|
|||||||
this.activeListViewState.sortField = 'created'
|
this.activeListViewState.sortField = 'created'
|
||||||
}
|
}
|
||||||
this.activeListViewState.filterRules = filterRules
|
this.activeListViewState.filterRules = filterRules
|
||||||
if (resetPage) {
|
|
||||||
this.activeListViewState.currentPage = 1
|
|
||||||
}
|
|
||||||
this.reload()
|
this.reload()
|
||||||
this.reduceSelectionToFilter()
|
this.reduceSelectionToFilter()
|
||||||
this.saveDocumentListView()
|
this.saveDocumentListView()
|
||||||
@@ -482,7 +479,7 @@ export class DocumentListViewService {
|
|||||||
|
|
||||||
quickFilter(filterRules: FilterRule[]) {
|
quickFilter(filterRules: FilterRule[]) {
|
||||||
this._activeSavedViewId = null
|
this._activeSavedViewId = null
|
||||||
this.setFilterRules(filterRules)
|
this.filterRules = filterRules
|
||||||
this.router.navigate(['documents'])
|
this.router.navigate(['documents'])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
# Generated by Django 5.2.11 on 2026-02-14 19:19
|
|
||||||
|
|
||||||
from django.db import migrations
|
|
||||||
from django.db import models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
dependencies = [
|
|
||||||
("documents", "0011_optimize_integer_field_sizes"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name="workflowaction",
|
|
||||||
name="type",
|
|
||||||
field=models.PositiveSmallIntegerField(
|
|
||||||
choices=[
|
|
||||||
(1, "Assignment"),
|
|
||||||
(2, "Removal"),
|
|
||||||
(3, "Email"),
|
|
||||||
(4, "Webhook"),
|
|
||||||
(5, "Password removal"),
|
|
||||||
(6, "Move to trash"),
|
|
||||||
],
|
|
||||||
default=1,
|
|
||||||
verbose_name="Workflow Action Type",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1409,10 +1409,6 @@ class WorkflowAction(models.Model):
|
|||||||
5,
|
5,
|
||||||
_("Password removal"),
|
_("Password removal"),
|
||||||
)
|
)
|
||||||
MOVE_TO_TRASH = (
|
|
||||||
6,
|
|
||||||
_("Move to trash"),
|
|
||||||
)
|
|
||||||
|
|
||||||
type = models.PositiveSmallIntegerField(
|
type = models.PositiveSmallIntegerField(
|
||||||
_("Workflow Action Type"),
|
_("Workflow Action Type"),
|
||||||
|
|||||||
@@ -48,7 +48,6 @@ from documents.permissions import get_objects_for_user_owner_aware
|
|||||||
from documents.templating.utils import convert_format_str_to_template_format
|
from documents.templating.utils import convert_format_str_to_template_format
|
||||||
from documents.workflows.actions import build_workflow_action_context
|
from documents.workflows.actions import build_workflow_action_context
|
||||||
from documents.workflows.actions import execute_email_action
|
from documents.workflows.actions import execute_email_action
|
||||||
from documents.workflows.actions import execute_move_to_trash_action
|
|
||||||
from documents.workflows.actions import execute_password_removal_action
|
from documents.workflows.actions import execute_password_removal_action
|
||||||
from documents.workflows.actions import execute_webhook_action
|
from documents.workflows.actions import execute_webhook_action
|
||||||
from documents.workflows.mutations import apply_assignment_to_document
|
from documents.workflows.mutations import apply_assignment_to_document
|
||||||
@@ -59,8 +58,6 @@ from documents.workflows.utils import get_workflows_for_trigger
|
|||||||
from paperless.config import AIConfig
|
from paperless.config import AIConfig
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
import uuid
|
|
||||||
|
|
||||||
from documents.classifier import DocumentClassifier
|
from documents.classifier import DocumentClassifier
|
||||||
from documents.data_models import ConsumableDocument
|
from documents.data_models import ConsumableDocument
|
||||||
from documents.data_models import DocumentMetadataOverrides
|
from documents.data_models import DocumentMetadataOverrides
|
||||||
@@ -730,7 +727,7 @@ def add_to_index(sender, document, **kwargs) -> None:
|
|||||||
def run_workflows_added(
|
def run_workflows_added(
|
||||||
sender,
|
sender,
|
||||||
document: Document,
|
document: Document,
|
||||||
logging_group: uuid.UUID | None = None,
|
logging_group=None,
|
||||||
original_file=None,
|
original_file=None,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
) -> None:
|
) -> None:
|
||||||
@@ -746,7 +743,7 @@ def run_workflows_added(
|
|||||||
def run_workflows_updated(
|
def run_workflows_updated(
|
||||||
sender,
|
sender,
|
||||||
document: Document,
|
document: Document,
|
||||||
logging_group: uuid.UUID | None = None,
|
logging_group=None,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
) -> None:
|
) -> None:
|
||||||
run_workflows(
|
run_workflows(
|
||||||
@@ -760,7 +757,7 @@ def run_workflows(
|
|||||||
trigger_type: WorkflowTrigger.WorkflowTriggerType,
|
trigger_type: WorkflowTrigger.WorkflowTriggerType,
|
||||||
document: Document | ConsumableDocument,
|
document: Document | ConsumableDocument,
|
||||||
workflow_to_run: Workflow | None = None,
|
workflow_to_run: Workflow | None = None,
|
||||||
logging_group: uuid.UUID | None = None,
|
logging_group=None,
|
||||||
overrides: DocumentMetadataOverrides | None = None,
|
overrides: DocumentMetadataOverrides | None = None,
|
||||||
original_file: Path | None = None,
|
original_file: Path | None = None,
|
||||||
) -> tuple[DocumentMetadataOverrides, str] | None:
|
) -> tuple[DocumentMetadataOverrides, str] | None:
|
||||||
@@ -786,33 +783,14 @@ def run_workflows(
|
|||||||
|
|
||||||
for workflow in workflows:
|
for workflow in workflows:
|
||||||
if not use_overrides:
|
if not use_overrides:
|
||||||
if TYPE_CHECKING:
|
|
||||||
assert isinstance(document, Document)
|
|
||||||
try:
|
|
||||||
# This can be called from bulk_update_documents, which may be running multiple times
|
# This can be called from bulk_update_documents, which may be running multiple times
|
||||||
# Refresh this so the matching data is fresh and instance fields are re-freshed
|
# Refresh this so the matching data is fresh and instance fields are re-freshed
|
||||||
# Otherwise, this instance might be behind and overwrite the work another process did
|
# Otherwise, this instance might be behind and overwrite the work another process did
|
||||||
document.refresh_from_db()
|
document.refresh_from_db()
|
||||||
doc_tag_ids = list(document.tags.values_list("pk", flat=True))
|
doc_tag_ids = list(document.tags.values_list("pk", flat=True))
|
||||||
except Document.DoesNotExist:
|
|
||||||
# Document was hard deleted by a previous workflow or another process
|
|
||||||
logger.info(
|
|
||||||
"Document no longer exists, skipping remaining workflows",
|
|
||||||
extra={"group": logging_group},
|
|
||||||
)
|
|
||||||
break
|
|
||||||
|
|
||||||
# Check if document was soft deleted (moved to trash)
|
|
||||||
if document.is_deleted:
|
|
||||||
logger.info(
|
|
||||||
"Document was moved to trash, skipping remaining workflows",
|
|
||||||
extra={"group": logging_group},
|
|
||||||
)
|
|
||||||
break
|
|
||||||
|
|
||||||
if matching.document_matches_workflow(document, workflow, trigger_type):
|
if matching.document_matches_workflow(document, workflow, trigger_type):
|
||||||
action: WorkflowAction
|
action: WorkflowAction
|
||||||
has_move_to_trash_action = False
|
|
||||||
for action in workflow.actions.order_by("order", "pk"):
|
for action in workflow.actions.order_by("order", "pk"):
|
||||||
message = f"Applying {action} from {workflow}"
|
message = f"Applying {action} from {workflow}"
|
||||||
if not use_overrides:
|
if not use_overrides:
|
||||||
@@ -856,8 +834,6 @@ def run_workflows(
|
|||||||
)
|
)
|
||||||
elif action.type == WorkflowAction.WorkflowActionType.PASSWORD_REMOVAL:
|
elif action.type == WorkflowAction.WorkflowActionType.PASSWORD_REMOVAL:
|
||||||
execute_password_removal_action(action, document, logging_group)
|
execute_password_removal_action(action, document, logging_group)
|
||||||
elif action.type == WorkflowAction.WorkflowActionType.MOVE_TO_TRASH:
|
|
||||||
has_move_to_trash_action = True
|
|
||||||
|
|
||||||
if not use_overrides:
|
if not use_overrides:
|
||||||
# limit title to 128 characters
|
# limit title to 128 characters
|
||||||
@@ -872,12 +848,7 @@ def run_workflows(
|
|||||||
document=document if not use_overrides else None,
|
document=document if not use_overrides else None,
|
||||||
)
|
)
|
||||||
|
|
||||||
if has_move_to_trash_action:
|
|
||||||
execute_move_to_trash_action(action, document, logging_group)
|
|
||||||
|
|
||||||
if use_overrides:
|
if use_overrides:
|
||||||
if TYPE_CHECKING:
|
|
||||||
assert overrides is not None
|
|
||||||
return overrides, "\n".join(messages)
|
return overrides, "\n".join(messages)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -896,210 +896,3 @@ class TestApiWorkflows(DirectoriesMixin, APITestCase):
|
|||||||
"Passwords are required",
|
"Passwords are required",
|
||||||
str(response.data["non_field_errors"][0]),
|
str(response.data["non_field_errors"][0]),
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_trash_action_validation(self) -> None:
|
|
||||||
"""
|
|
||||||
GIVEN:
|
|
||||||
- API request to create a workflow with a trash action
|
|
||||||
WHEN:
|
|
||||||
- API is called
|
|
||||||
THEN:
|
|
||||||
- Correct HTTP response
|
|
||||||
"""
|
|
||||||
response = self.client.post(
|
|
||||||
self.ENDPOINT,
|
|
||||||
json.dumps(
|
|
||||||
{
|
|
||||||
"name": "Workflow 2",
|
|
||||||
"order": 1,
|
|
||||||
"triggers": [
|
|
||||||
{
|
|
||||||
"type": WorkflowTrigger.WorkflowTriggerType.CONSUMPTION,
|
|
||||||
"sources": [DocumentSource.ApiUpload],
|
|
||||||
"filter_filename": "*",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"actions": [
|
|
||||||
{
|
|
||||||
"type": WorkflowAction.WorkflowActionType.MOVE_TO_TRASH,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
),
|
|
||||||
content_type="application/json",
|
|
||||||
)
|
|
||||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
|
||||||
|
|
||||||
response = self.client.post(
|
|
||||||
self.ENDPOINT,
|
|
||||||
json.dumps(
|
|
||||||
{
|
|
||||||
"name": "Workflow 3",
|
|
||||||
"order": 2,
|
|
||||||
"triggers": [
|
|
||||||
{
|
|
||||||
"type": WorkflowTrigger.WorkflowTriggerType.CONSUMPTION,
|
|
||||||
"sources": [DocumentSource.ApiUpload],
|
|
||||||
"filter_filename": "*",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"actions": [
|
|
||||||
{
|
|
||||||
"type": WorkflowAction.WorkflowActionType.MOVE_TO_TRASH,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
),
|
|
||||||
content_type="application/json",
|
|
||||||
)
|
|
||||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
|
||||||
|
|
||||||
def test_trash_action_as_last_action_valid(self) -> None:
|
|
||||||
"""
|
|
||||||
GIVEN:
|
|
||||||
- API request to create a workflow with multiple actions
|
|
||||||
- Move to trash action is the last action
|
|
||||||
WHEN:
|
|
||||||
- API is called
|
|
||||||
THEN:
|
|
||||||
- Workflow is created successfully
|
|
||||||
"""
|
|
||||||
response = self.client.post(
|
|
||||||
self.ENDPOINT,
|
|
||||||
json.dumps(
|
|
||||||
{
|
|
||||||
"name": "Workflow with Move to Trash Last",
|
|
||||||
"order": 1,
|
|
||||||
"triggers": [
|
|
||||||
{
|
|
||||||
"type": WorkflowTrigger.WorkflowTriggerType.CONSUMPTION,
|
|
||||||
"sources": [DocumentSource.ApiUpload],
|
|
||||||
"filter_filename": "*",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"actions": [
|
|
||||||
{
|
|
||||||
"type": WorkflowAction.WorkflowActionType.ASSIGNMENT,
|
|
||||||
"assign_title": "Assigned Title",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": WorkflowAction.WorkflowActionType.REMOVAL,
|
|
||||||
"remove_all_tags": True,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": WorkflowAction.WorkflowActionType.MOVE_TO_TRASH,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
),
|
|
||||||
content_type="application/json",
|
|
||||||
)
|
|
||||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
|
||||||
|
|
||||||
def test_update_workflow_add_trash_at_end_valid(self) -> None:
|
|
||||||
"""
|
|
||||||
GIVEN:
|
|
||||||
- Existing workflow without trash action
|
|
||||||
WHEN:
|
|
||||||
- PATCH to add trash action at end
|
|
||||||
THEN:
|
|
||||||
- HTTP 200 success
|
|
||||||
"""
|
|
||||||
response = self.client.post(
|
|
||||||
self.ENDPOINT,
|
|
||||||
json.dumps(
|
|
||||||
{
|
|
||||||
"name": "Workflow to Add Move to Trash",
|
|
||||||
"order": 1,
|
|
||||||
"triggers": [
|
|
||||||
{
|
|
||||||
"type": WorkflowTrigger.WorkflowTriggerType.CONSUMPTION,
|
|
||||||
"sources": [DocumentSource.ApiUpload],
|
|
||||||
"filter_filename": "*",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"actions": [
|
|
||||||
{
|
|
||||||
"type": WorkflowAction.WorkflowActionType.ASSIGNMENT,
|
|
||||||
"assign_title": "First Action",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
),
|
|
||||||
content_type="application/json",
|
|
||||||
)
|
|
||||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
|
||||||
workflow_id = response.data["id"]
|
|
||||||
|
|
||||||
response = self.client.patch(
|
|
||||||
f"{self.ENDPOINT}{workflow_id}/",
|
|
||||||
json.dumps(
|
|
||||||
{
|
|
||||||
"actions": [
|
|
||||||
{
|
|
||||||
"type": WorkflowAction.WorkflowActionType.ASSIGNMENT,
|
|
||||||
"assign_title": "First Action",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": WorkflowAction.WorkflowActionType.MOVE_TO_TRASH,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
),
|
|
||||||
content_type="application/json",
|
|
||||||
)
|
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
||||||
|
|
||||||
def test_update_workflow_remove_trash_action_valid(self) -> None:
|
|
||||||
"""
|
|
||||||
GIVEN:
|
|
||||||
- Existing workflow with trash action
|
|
||||||
WHEN:
|
|
||||||
- PATCH to remove trash action
|
|
||||||
THEN:
|
|
||||||
- HTTP 200 success
|
|
||||||
"""
|
|
||||||
response = self.client.post(
|
|
||||||
self.ENDPOINT,
|
|
||||||
json.dumps(
|
|
||||||
{
|
|
||||||
"name": "Workflow to Remove move to trash",
|
|
||||||
"order": 1,
|
|
||||||
"triggers": [
|
|
||||||
{
|
|
||||||
"type": WorkflowTrigger.WorkflowTriggerType.CONSUMPTION,
|
|
||||||
"sources": [DocumentSource.ApiUpload],
|
|
||||||
"filter_filename": "*",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"actions": [
|
|
||||||
{
|
|
||||||
"type": WorkflowAction.WorkflowActionType.ASSIGNMENT,
|
|
||||||
"assign_title": "First Action",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": WorkflowAction.WorkflowActionType.MOVE_TO_TRASH,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
),
|
|
||||||
content_type="application/json",
|
|
||||||
)
|
|
||||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
|
||||||
workflow_id = response.data["id"]
|
|
||||||
|
|
||||||
response = self.client.patch(
|
|
||||||
f"{self.ENDPOINT}{workflow_id}/",
|
|
||||||
json.dumps(
|
|
||||||
{
|
|
||||||
"actions": [
|
|
||||||
{
|
|
||||||
"type": WorkflowAction.WorkflowActionType.ASSIGNMENT,
|
|
||||||
"assign_title": "Only Action",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
),
|
|
||||||
content_type="application/json",
|
|
||||||
)
|
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
||||||
|
|||||||
@@ -3,11 +3,9 @@ import json
|
|||||||
import shutil
|
import shutil
|
||||||
import socket
|
import socket
|
||||||
import tempfile
|
import tempfile
|
||||||
from collections.abc import Callable
|
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
from typing import Any
|
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
@@ -57,7 +55,6 @@ from documents.models import WorkflowActionEmail
|
|||||||
from documents.models import WorkflowActionWebhook
|
from documents.models import WorkflowActionWebhook
|
||||||
from documents.models import WorkflowRun
|
from documents.models import WorkflowRun
|
||||||
from documents.models import WorkflowTrigger
|
from documents.models import WorkflowTrigger
|
||||||
from documents.plugins.base import StopConsumeTaskError
|
|
||||||
from documents.serialisers import WorkflowTriggerSerializer
|
from documents.serialisers import WorkflowTriggerSerializer
|
||||||
from documents.signals import document_consumption_finished
|
from documents.signals import document_consumption_finished
|
||||||
from documents.tests.utils import DirectoriesMixin
|
from documents.tests.utils import DirectoriesMixin
|
||||||
@@ -3917,427 +3914,6 @@ class TestWorkflows(
|
|||||||
)
|
)
|
||||||
assert mock_remove_password.call_count == 2
|
assert mock_remove_password.call_count == 2
|
||||||
|
|
||||||
def test_workflow_trash_action_soft_delete(self):
|
|
||||||
"""
|
|
||||||
GIVEN:
|
|
||||||
- Document updated workflow with delete action
|
|
||||||
WHEN:
|
|
||||||
- Document that matches is updated
|
|
||||||
THEN:
|
|
||||||
- Document is moved to trash (soft deleted)
|
|
||||||
"""
|
|
||||||
trigger = WorkflowTrigger.objects.create(
|
|
||||||
type=WorkflowTrigger.WorkflowTriggerType.DOCUMENT_UPDATED,
|
|
||||||
)
|
|
||||||
action = WorkflowAction.objects.create(
|
|
||||||
type=WorkflowAction.WorkflowActionType.MOVE_TO_TRASH,
|
|
||||||
)
|
|
||||||
w = Workflow.objects.create(
|
|
||||||
name="Workflow 1",
|
|
||||||
order=0,
|
|
||||||
)
|
|
||||||
w.triggers.add(trigger)
|
|
||||||
w.actions.add(action)
|
|
||||||
w.save()
|
|
||||||
|
|
||||||
doc = Document.objects.create(
|
|
||||||
title="sample test",
|
|
||||||
correspondent=self.c,
|
|
||||||
original_filename="sample.pdf",
|
|
||||||
)
|
|
||||||
|
|
||||||
self.assertEqual(Document.objects.count(), 1)
|
|
||||||
self.assertEqual(Document.deleted_objects.count(), 0)
|
|
||||||
|
|
||||||
run_workflows(WorkflowTrigger.WorkflowTriggerType.DOCUMENT_UPDATED, doc)
|
|
||||||
|
|
||||||
self.assertEqual(Document.objects.count(), 0)
|
|
||||||
self.assertEqual(Document.deleted_objects.count(), 1)
|
|
||||||
|
|
||||||
@override_settings(
|
|
||||||
PAPERLESS_EMAIL_HOST="localhost",
|
|
||||||
EMAIL_ENABLED=True,
|
|
||||||
PAPERLESS_URL="http://localhost:8000",
|
|
||||||
)
|
|
||||||
@mock.patch("django.core.mail.message.EmailMessage.send")
|
|
||||||
def test_workflow_trash_with_email_action(self, mock_email_send):
|
|
||||||
"""
|
|
||||||
GIVEN:
|
|
||||||
- Workflow with email action, then move to trash action
|
|
||||||
WHEN:
|
|
||||||
- Document matches and workflow runs
|
|
||||||
THEN:
|
|
||||||
- Email is sent first
|
|
||||||
- Document is moved to trash (soft deleted)
|
|
||||||
"""
|
|
||||||
mock_email_send.return_value = 1
|
|
||||||
|
|
||||||
trigger = WorkflowTrigger.objects.create(
|
|
||||||
type=WorkflowTrigger.WorkflowTriggerType.DOCUMENT_UPDATED,
|
|
||||||
)
|
|
||||||
email_action = WorkflowActionEmail.objects.create(
|
|
||||||
subject="Document deleted: {doc_title}",
|
|
||||||
body="Document {doc_title} will be deleted",
|
|
||||||
to="user@example.com",
|
|
||||||
include_document=False,
|
|
||||||
)
|
|
||||||
email_workflow_action = WorkflowAction.objects.create(
|
|
||||||
type=WorkflowAction.WorkflowActionType.EMAIL,
|
|
||||||
email=email_action,
|
|
||||||
)
|
|
||||||
trash_workflow_action = WorkflowAction.objects.create(
|
|
||||||
type=WorkflowAction.WorkflowActionType.MOVE_TO_TRASH,
|
|
||||||
)
|
|
||||||
w = Workflow.objects.create(
|
|
||||||
name="Workflow with email then move to trash",
|
|
||||||
order=0,
|
|
||||||
)
|
|
||||||
w.triggers.add(trigger)
|
|
||||||
w.actions.add(email_workflow_action, trash_workflow_action)
|
|
||||||
w.save()
|
|
||||||
|
|
||||||
doc = Document.objects.create(
|
|
||||||
title="sample test",
|
|
||||||
correspondent=self.c,
|
|
||||||
original_filename="sample.pdf",
|
|
||||||
)
|
|
||||||
|
|
||||||
self.assertEqual(Document.objects.count(), 1)
|
|
||||||
self.assertEqual(Document.deleted_objects.count(), 0)
|
|
||||||
|
|
||||||
run_workflows(WorkflowTrigger.WorkflowTriggerType.DOCUMENT_UPDATED, doc)
|
|
||||||
|
|
||||||
mock_email_send.assert_called_once()
|
|
||||||
self.assertEqual(Document.objects.count(), 0)
|
|
||||||
self.assertEqual(Document.deleted_objects.count(), 1)
|
|
||||||
|
|
||||||
@override_settings(
|
|
||||||
PAPERLESS_URL="http://localhost:8000",
|
|
||||||
)
|
|
||||||
@mock.patch("documents.workflows.webhooks.send_webhook.delay")
|
|
||||||
def test_workflow_trash_with_webhook_action(self, mock_webhook_delay):
|
|
||||||
"""
|
|
||||||
GIVEN:
|
|
||||||
- Workflow with webhook action (include_document=True), then move to trash action
|
|
||||||
WHEN:
|
|
||||||
- Document matches and workflow runs
|
|
||||||
THEN:
|
|
||||||
- Webhook .delay() is called with complete data including file bytes
|
|
||||||
- Document is moved to trash (soft deleted)
|
|
||||||
- Webhook task has all necessary data and doesn't rely on document existence
|
|
||||||
"""
|
|
||||||
trigger = WorkflowTrigger.objects.create(
|
|
||||||
type=WorkflowTrigger.WorkflowTriggerType.DOCUMENT_UPDATED,
|
|
||||||
)
|
|
||||||
webhook_action = WorkflowActionWebhook.objects.create(
|
|
||||||
use_params=True,
|
|
||||||
params={
|
|
||||||
"title": "{{doc_title}}",
|
|
||||||
"message": "Document being deleted",
|
|
||||||
},
|
|
||||||
url="https://paperless-ngx.com/webhook",
|
|
||||||
include_document=True,
|
|
||||||
)
|
|
||||||
webhook_workflow_action = WorkflowAction.objects.create(
|
|
||||||
type=WorkflowAction.WorkflowActionType.WEBHOOK,
|
|
||||||
webhook=webhook_action,
|
|
||||||
)
|
|
||||||
trash_workflow_action = WorkflowAction.objects.create(
|
|
||||||
type=WorkflowAction.WorkflowActionType.MOVE_TO_TRASH,
|
|
||||||
)
|
|
||||||
w = Workflow.objects.create(
|
|
||||||
name="Workflow with webhook then move to trash",
|
|
||||||
order=0,
|
|
||||||
)
|
|
||||||
w.triggers.add(trigger)
|
|
||||||
w.actions.add(webhook_workflow_action, trash_workflow_action)
|
|
||||||
w.save()
|
|
||||||
|
|
||||||
test_file = shutil.copy(
|
|
||||||
self.SAMPLE_DIR / "simple.pdf",
|
|
||||||
self.dirs.scratch_dir / "simple.pdf",
|
|
||||||
)
|
|
||||||
|
|
||||||
doc = Document.objects.create(
|
|
||||||
title="sample test",
|
|
||||||
correspondent=self.c,
|
|
||||||
original_filename="simple.pdf",
|
|
||||||
filename=test_file,
|
|
||||||
mime_type="application/pdf",
|
|
||||||
)
|
|
||||||
|
|
||||||
self.assertEqual(Document.objects.count(), 1)
|
|
||||||
self.assertEqual(Document.deleted_objects.count(), 0)
|
|
||||||
|
|
||||||
run_workflows(WorkflowTrigger.WorkflowTriggerType.DOCUMENT_UPDATED, doc)
|
|
||||||
|
|
||||||
mock_webhook_delay.assert_called_once()
|
|
||||||
call_kwargs = mock_webhook_delay.call_args[1]
|
|
||||||
self.assertEqual(call_kwargs["url"], "https://paperless-ngx.com/webhook")
|
|
||||||
self.assertEqual(
|
|
||||||
call_kwargs["data"],
|
|
||||||
{"title": "sample test", "message": "Document being deleted"},
|
|
||||||
)
|
|
||||||
self.assertIsNotNone(call_kwargs["files"])
|
|
||||||
self.assertIn("file", call_kwargs["files"])
|
|
||||||
self.assertEqual(call_kwargs["files"]["file"][0], "simple.pdf")
|
|
||||||
self.assertEqual(call_kwargs["files"]["file"][2], "application/pdf")
|
|
||||||
self.assertIsInstance(call_kwargs["files"]["file"][1], bytes)
|
|
||||||
|
|
||||||
self.assertEqual(Document.objects.count(), 0)
|
|
||||||
self.assertEqual(Document.deleted_objects.count(), 1)
|
|
||||||
|
|
||||||
@override_settings(
|
|
||||||
PAPERLESS_EMAIL_HOST="localhost",
|
|
||||||
EMAIL_ENABLED=True,
|
|
||||||
PAPERLESS_URL="http://localhost:8000",
|
|
||||||
)
|
|
||||||
@mock.patch("django.core.mail.message.EmailMessage.send")
|
|
||||||
def test_workflow_trash_after_email_failure(self, mock_email_send) -> None:
|
|
||||||
"""
|
|
||||||
GIVEN:
|
|
||||||
- Workflow with email action (that fails), then move to trash action
|
|
||||||
WHEN:
|
|
||||||
- Document matches and workflow runs
|
|
||||||
- Email action raises exception
|
|
||||||
THEN:
|
|
||||||
- Email failure is logged
|
|
||||||
- Move to Trash still executes successfully (soft delete)
|
|
||||||
"""
|
|
||||||
mock_email_send.side_effect = Exception("Email server error")
|
|
||||||
|
|
||||||
trigger = WorkflowTrigger.objects.create(
|
|
||||||
type=WorkflowTrigger.WorkflowTriggerType.DOCUMENT_UPDATED,
|
|
||||||
)
|
|
||||||
email_action = WorkflowActionEmail.objects.create(
|
|
||||||
subject="Document deleted: {doc_title}",
|
|
||||||
body="Document {doc_title} will be deleted",
|
|
||||||
to="user@example.com",
|
|
||||||
include_document=False,
|
|
||||||
)
|
|
||||||
email_workflow_action = WorkflowAction.objects.create(
|
|
||||||
type=WorkflowAction.WorkflowActionType.EMAIL,
|
|
||||||
email=email_action,
|
|
||||||
)
|
|
||||||
trash_workflow_action = WorkflowAction.objects.create(
|
|
||||||
type=WorkflowAction.WorkflowActionType.MOVE_TO_TRASH,
|
|
||||||
)
|
|
||||||
w = Workflow.objects.create(
|
|
||||||
name="Workflow with failing email then move to trash",
|
|
||||||
order=0,
|
|
||||||
)
|
|
||||||
w.triggers.add(trigger)
|
|
||||||
w.actions.add(email_workflow_action, trash_workflow_action)
|
|
||||||
w.save()
|
|
||||||
|
|
||||||
doc = Document.objects.create(
|
|
||||||
title="sample test",
|
|
||||||
correspondent=self.c,
|
|
||||||
original_filename="sample.pdf",
|
|
||||||
)
|
|
||||||
|
|
||||||
self.assertEqual(Document.objects.count(), 1)
|
|
||||||
self.assertEqual(Document.deleted_objects.count(), 0)
|
|
||||||
|
|
||||||
with self.assertLogs("paperless.workflows.actions", level="ERROR") as cm:
|
|
||||||
run_workflows(WorkflowTrigger.WorkflowTriggerType.DOCUMENT_UPDATED, doc)
|
|
||||||
|
|
||||||
expected_str = "Error occurred sending notification email"
|
|
||||||
self.assertIn(expected_str, cm.output[0])
|
|
||||||
|
|
||||||
self.assertEqual(Document.objects.count(), 0)
|
|
||||||
self.assertEqual(Document.deleted_objects.count(), 1)
|
|
||||||
|
|
||||||
def test_multiple_workflows_trash_then_assignment(self):
|
|
||||||
"""
|
|
||||||
GIVEN:
|
|
||||||
- Workflow 1 (order=0) with move to trash action
|
|
||||||
- Workflow 2 (order=1) with assignment action
|
|
||||||
- Both workflows match the same document
|
|
||||||
WHEN:
|
|
||||||
- Workflows run sequentially
|
|
||||||
THEN:
|
|
||||||
- First workflow runs and deletes document (soft delete)
|
|
||||||
- Second workflow does not trigger (document no longer exists)
|
|
||||||
- Logs confirm move to trash and skipping of remaining workflows
|
|
||||||
"""
|
|
||||||
trigger1 = WorkflowTrigger.objects.create(
|
|
||||||
type=WorkflowTrigger.WorkflowTriggerType.DOCUMENT_UPDATED,
|
|
||||||
)
|
|
||||||
trash_workflow_action = WorkflowAction.objects.create(
|
|
||||||
type=WorkflowAction.WorkflowActionType.MOVE_TO_TRASH,
|
|
||||||
)
|
|
||||||
w1 = Workflow.objects.create(
|
|
||||||
name="Workflow 1 - Move to Trash",
|
|
||||||
order=0,
|
|
||||||
)
|
|
||||||
w1.triggers.add(trigger1)
|
|
||||||
w1.actions.add(trash_workflow_action)
|
|
||||||
w1.save()
|
|
||||||
|
|
||||||
trigger2 = WorkflowTrigger.objects.create(
|
|
||||||
type=WorkflowTrigger.WorkflowTriggerType.DOCUMENT_UPDATED,
|
|
||||||
)
|
|
||||||
assignment_action = WorkflowAction.objects.create(
|
|
||||||
type=WorkflowAction.WorkflowActionType.ASSIGNMENT,
|
|
||||||
assign_correspondent=self.c2,
|
|
||||||
)
|
|
||||||
w2 = Workflow.objects.create(
|
|
||||||
name="Workflow 2 - Assignment",
|
|
||||||
order=1,
|
|
||||||
)
|
|
||||||
w2.triggers.add(trigger2)
|
|
||||||
w2.actions.add(assignment_action)
|
|
||||||
w2.save()
|
|
||||||
|
|
||||||
doc = Document.objects.create(
|
|
||||||
title="sample test",
|
|
||||||
correspondent=self.c,
|
|
||||||
original_filename="sample.pdf",
|
|
||||||
)
|
|
||||||
|
|
||||||
self.assertEqual(Document.objects.count(), 1)
|
|
||||||
self.assertEqual(Document.deleted_objects.count(), 0)
|
|
||||||
|
|
||||||
with self.assertLogs("paperless", level="DEBUG") as cm:
|
|
||||||
run_workflows(WorkflowTrigger.WorkflowTriggerType.DOCUMENT_UPDATED, doc)
|
|
||||||
|
|
||||||
self.assertEqual(Document.objects.count(), 0)
|
|
||||||
self.assertEqual(Document.deleted_objects.count(), 1)
|
|
||||||
|
|
||||||
# We check logs instead of WorkflowRun.objects.count() because when the document
|
|
||||||
# is soft-deleted, the WorkflowRun is cascade-deleted (hard delete) since it does
|
|
||||||
# not inherit from the SoftDeleteModel. The logs confirm that the first workflow
|
|
||||||
# executed the move to trash and remaining workflows were skipped.
|
|
||||||
log_output = "\n".join(cm.output)
|
|
||||||
self.assertIn("Moved document", log_output)
|
|
||||||
self.assertIn("to trash", log_output)
|
|
||||||
self.assertIn(
|
|
||||||
"Document was moved to trash, skipping remaining workflows",
|
|
||||||
log_output,
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_workflow_delete_action_during_consumption(self):
|
|
||||||
"""
|
|
||||||
GIVEN:
|
|
||||||
- Workflow with consumption trigger and delete action
|
|
||||||
WHEN:
|
|
||||||
- Document is being consumed and workflow runs
|
|
||||||
THEN:
|
|
||||||
- StopConsumeTaskError is raised to halt consumption
|
|
||||||
- Original file is deleted
|
|
||||||
- No document is created
|
|
||||||
"""
|
|
||||||
trigger = WorkflowTrigger.objects.create(
|
|
||||||
type=WorkflowTrigger.WorkflowTriggerType.CONSUMPTION,
|
|
||||||
sources=f"{DocumentSource.ConsumeFolder}",
|
|
||||||
filter_filename="*",
|
|
||||||
)
|
|
||||||
action = WorkflowAction.objects.create(
|
|
||||||
type=WorkflowAction.WorkflowActionType.MOVE_TO_TRASH,
|
|
||||||
)
|
|
||||||
w = Workflow.objects.create(
|
|
||||||
name="Workflow Delete During Consumption",
|
|
||||||
order=0,
|
|
||||||
)
|
|
||||||
w.triggers.add(trigger)
|
|
||||||
w.actions.add(action)
|
|
||||||
w.save()
|
|
||||||
|
|
||||||
# Create a test file to be consumed
|
|
||||||
test_file = shutil.copy(
|
|
||||||
self.SAMPLE_DIR / "simple.pdf",
|
|
||||||
self.dirs.scratch_dir / "simple.pdf",
|
|
||||||
)
|
|
||||||
test_file_path = Path(test_file)
|
|
||||||
self.assertTrue(test_file_path.exists())
|
|
||||||
|
|
||||||
# Create a ConsumableDocument
|
|
||||||
consumable_doc = ConsumableDocument(
|
|
||||||
source=DocumentSource.ConsumeFolder,
|
|
||||||
original_file=test_file_path,
|
|
||||||
)
|
|
||||||
|
|
||||||
self.assertEqual(Document.objects.count(), 0)
|
|
||||||
|
|
||||||
# Run workflows with overrides (consumption flow)
|
|
||||||
with self.assertRaises(StopConsumeTaskError) as context:
|
|
||||||
run_workflows(
|
|
||||||
WorkflowTrigger.WorkflowTriggerType.CONSUMPTION,
|
|
||||||
consumable_doc,
|
|
||||||
overrides=DocumentMetadataOverrides(),
|
|
||||||
)
|
|
||||||
|
|
||||||
self.assertIn("deleted by workflow action", str(context.exception))
|
|
||||||
|
|
||||||
# File should be deleted
|
|
||||||
self.assertFalse(test_file_path.exists())
|
|
||||||
|
|
||||||
# No document should be created
|
|
||||||
self.assertEqual(Document.objects.count(), 0)
|
|
||||||
|
|
||||||
def test_workflow_delete_action_during_consumption_with_assignment(self):
|
|
||||||
"""
|
|
||||||
GIVEN:
|
|
||||||
- Workflow with consumption trigger, assignment action, then delete action
|
|
||||||
WHEN:
|
|
||||||
- Document is being consumed and workflow runs
|
|
||||||
THEN:
|
|
||||||
- StopConsumeTaskError is raised to halt consumption
|
|
||||||
- Original file is deleted
|
|
||||||
- No document is created (even though assignment would have worked)
|
|
||||||
"""
|
|
||||||
trigger = WorkflowTrigger.objects.create(
|
|
||||||
type=WorkflowTrigger.WorkflowTriggerType.CONSUMPTION,
|
|
||||||
sources=f"{DocumentSource.ConsumeFolder}",
|
|
||||||
filter_filename="*",
|
|
||||||
)
|
|
||||||
assignment_action = WorkflowAction.objects.create(
|
|
||||||
type=WorkflowAction.WorkflowActionType.ASSIGNMENT,
|
|
||||||
assign_title="This should not be applied",
|
|
||||||
assign_correspondent=self.c,
|
|
||||||
)
|
|
||||||
trash_workflow_action = WorkflowAction.objects.create(
|
|
||||||
type=WorkflowAction.WorkflowActionType.MOVE_TO_TRASH,
|
|
||||||
)
|
|
||||||
w = Workflow.objects.create(
|
|
||||||
name="Workflow Assignment then Delete During Consumption",
|
|
||||||
order=0,
|
|
||||||
)
|
|
||||||
w.triggers.add(trigger)
|
|
||||||
w.actions.add(assignment_action, trash_workflow_action)
|
|
||||||
w.save()
|
|
||||||
|
|
||||||
# Create a test file to be consumed
|
|
||||||
test_file = shutil.copy(
|
|
||||||
self.SAMPLE_DIR / "simple.pdf",
|
|
||||||
self.dirs.scratch_dir / "simple2.pdf",
|
|
||||||
)
|
|
||||||
test_file_path = Path(test_file)
|
|
||||||
self.assertTrue(test_file_path.exists())
|
|
||||||
|
|
||||||
# Create a ConsumableDocument
|
|
||||||
consumable_doc = ConsumableDocument(
|
|
||||||
source=DocumentSource.ConsumeFolder,
|
|
||||||
original_file=test_file_path,
|
|
||||||
)
|
|
||||||
|
|
||||||
self.assertEqual(Document.objects.count(), 0)
|
|
||||||
|
|
||||||
# Run workflows with overrides (consumption flow)
|
|
||||||
with self.assertRaises(StopConsumeTaskError):
|
|
||||||
run_workflows(
|
|
||||||
WorkflowTrigger.WorkflowTriggerType.CONSUMPTION,
|
|
||||||
consumable_doc,
|
|
||||||
overrides=DocumentMetadataOverrides(),
|
|
||||||
)
|
|
||||||
|
|
||||||
# File should be deleted
|
|
||||||
self.assertFalse(test_file_path.exists())
|
|
||||||
|
|
||||||
# No document should be created
|
|
||||||
self.assertEqual(Document.objects.count(), 0)
|
|
||||||
|
|
||||||
|
|
||||||
class TestWebhookSend:
|
class TestWebhookSend:
|
||||||
def test_send_webhook_data_or_json(
|
def test_send_webhook_data_or_json(
|
||||||
@@ -4380,17 +3956,13 @@ class TestWebhookSend:
|
|||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def resolve_to(monkeypatch: pytest.MonkeyPatch) -> Callable[[str], None]:
|
def resolve_to(monkeypatch):
|
||||||
"""
|
"""
|
||||||
Force DNS resolution to a specific IP for any hostname.
|
Force DNS resolution to a specific IP for any hostname.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def _set(ip: str) -> None:
|
def _set(ip: str):
|
||||||
def fake_getaddrinfo(
|
def fake_getaddrinfo(host, *_args, **_kwargs):
|
||||||
host: str,
|
|
||||||
*_args: object,
|
|
||||||
**_kwargs: object,
|
|
||||||
) -> list[tuple[Any, ...]]:
|
|
||||||
return [(socket.AF_INET, None, None, "", (ip, 0))]
|
return [(socket.AF_INET, None, None, "", (ip, 0))]
|
||||||
|
|
||||||
monkeypatch.setattr(socket, "getaddrinfo", fake_getaddrinfo)
|
monkeypatch.setattr(socket, "getaddrinfo", fake_getaddrinfo)
|
||||||
@@ -4531,7 +4103,7 @@ class TestWebhookSecurity:
|
|||||||
def test_strips_user_supplied_host_header(
|
def test_strips_user_supplied_host_header(
|
||||||
self,
|
self,
|
||||||
httpx_mock: HTTPXMock,
|
httpx_mock: HTTPXMock,
|
||||||
resolve_to: Callable[[str], None],
|
resolve_to,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
GIVEN:
|
GIVEN:
|
||||||
@@ -4597,7 +4169,7 @@ class TestDateWorkflowLocalization(
|
|||||||
self,
|
self,
|
||||||
title_template: str,
|
title_template: str,
|
||||||
expected_title: str,
|
expected_title: str,
|
||||||
) -> None:
|
):
|
||||||
"""
|
"""
|
||||||
GIVEN:
|
GIVEN:
|
||||||
- Document added workflow with title template using localize_date filter
|
- Document added workflow with title template using localize_date filter
|
||||||
@@ -4662,7 +4234,7 @@ class TestDateWorkflowLocalization(
|
|||||||
self,
|
self,
|
||||||
title_template: str,
|
title_template: str,
|
||||||
expected_title: str,
|
expected_title: str,
|
||||||
) -> None:
|
):
|
||||||
"""
|
"""
|
||||||
GIVEN:
|
GIVEN:
|
||||||
- Document updated workflow with title template using localize_date filter
|
- Document updated workflow with title template using localize_date filter
|
||||||
@@ -4738,7 +4310,7 @@ class TestDateWorkflowLocalization(
|
|||||||
settings: SettingsWrapper,
|
settings: SettingsWrapper,
|
||||||
title_template: str,
|
title_template: str,
|
||||||
expected_title: str,
|
expected_title: str,
|
||||||
) -> None:
|
):
|
||||||
trigger = WorkflowTrigger.objects.create(
|
trigger = WorkflowTrigger.objects.create(
|
||||||
type=WorkflowTrigger.WorkflowTriggerType.CONSUMPTION,
|
type=WorkflowTrigger.WorkflowTriggerType.CONSUMPTION,
|
||||||
sources=f"{DocumentSource.ApiUpload}",
|
sources=f"{DocumentSource.ApiUpload}",
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
import uuid
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
@@ -16,7 +15,6 @@ from documents.models import Document
|
|||||||
from documents.models import DocumentType
|
from documents.models import DocumentType
|
||||||
from documents.models import WorkflowAction
|
from documents.models import WorkflowAction
|
||||||
from documents.models import WorkflowTrigger
|
from documents.models import WorkflowTrigger
|
||||||
from documents.plugins.base import StopConsumeTaskError
|
|
||||||
from documents.signals import document_consumption_finished
|
from documents.signals import document_consumption_finished
|
||||||
from documents.templating.workflows import parse_w_workflow_placeholders
|
from documents.templating.workflows import parse_w_workflow_placeholders
|
||||||
from documents.workflows.webhooks import send_webhook
|
from documents.workflows.webhooks import send_webhook
|
||||||
@@ -340,33 +338,3 @@ def execute_password_removal_action(
|
|||||||
document.pk,
|
document.pk,
|
||||||
extra={"group": logging_group},
|
extra={"group": logging_group},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def execute_move_to_trash_action(
|
|
||||||
action: WorkflowAction,
|
|
||||||
document: Document | ConsumableDocument,
|
|
||||||
logging_group: uuid.UUID | None,
|
|
||||||
) -> None:
|
|
||||||
"""
|
|
||||||
Execute a move to trash action for a workflow on an existing document or a
|
|
||||||
document in consumption. In case of an existing document it soft-deletes
|
|
||||||
the document. In case of consumption it aborts consumption and deletes the
|
|
||||||
file.
|
|
||||||
"""
|
|
||||||
if isinstance(document, Document):
|
|
||||||
document.delete()
|
|
||||||
logger.debug(
|
|
||||||
f"Moved document {document} to trash",
|
|
||||||
extra={"group": logging_group},
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
if document.original_file.exists():
|
|
||||||
document.original_file.unlink()
|
|
||||||
logger.info(
|
|
||||||
f"Workflow move to trash action triggered during consumption, "
|
|
||||||
f"deleting file {document.original_file}",
|
|
||||||
extra={"group": logging_group},
|
|
||||||
)
|
|
||||||
raise StopConsumeTaskError(
|
|
||||||
"Document deleted by workflow action during consumption",
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: paperless-ngx\n"
|
"Project-Id-Version: paperless-ngx\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-02-24 00:43+0000\n"
|
"POT-Creation-Date: 2026-02-16 17:32+0000\n"
|
||||||
"PO-Revision-Date: 2022-02-17 04:17\n"
|
"PO-Revision-Date: 2022-02-17 04:17\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: English\n"
|
"Language-Team: English\n"
|
||||||
@@ -89,7 +89,7 @@ msgstr ""
|
|||||||
msgid "Automatic"
|
msgid "Automatic"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:66 documents/models.py:444 documents/models.py:1663
|
#: documents/models.py:66 documents/models.py:444 documents/models.py:1659
|
||||||
#: paperless_mail/models.py:23 paperless_mail/models.py:143
|
#: paperless_mail/models.py:23 paperless_mail/models.py:143
|
||||||
msgid "name"
|
msgid "name"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -252,7 +252,7 @@ msgid "The position of this document in your physical document archive."
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:313 documents/models.py:688 documents/models.py:742
|
#: documents/models.py:313 documents/models.py:688 documents/models.py:742
|
||||||
#: documents/models.py:1706
|
#: documents/models.py:1702
|
||||||
msgid "document"
|
msgid "document"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1093,197 +1093,193 @@ msgid "Password removal"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1414
|
#: documents/models.py:1414
|
||||||
msgid "Move to trash"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: documents/models.py:1418
|
|
||||||
msgid "Workflow Action Type"
|
msgid "Workflow Action Type"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1423 documents/models.py:1665
|
#: documents/models.py:1419 documents/models.py:1661
|
||||||
#: paperless_mail/models.py:145
|
#: paperless_mail/models.py:145
|
||||||
msgid "order"
|
msgid "order"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1426
|
#: documents/models.py:1422
|
||||||
msgid "assign title"
|
msgid "assign title"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1430
|
#: documents/models.py:1426
|
||||||
msgid "Assign a document title, must be a Jinja2 template, see documentation."
|
msgid "Assign a document title, must be a Jinja2 template, see documentation."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1438 paperless_mail/models.py:274
|
#: documents/models.py:1434 paperless_mail/models.py:274
|
||||||
msgid "assign this tag"
|
msgid "assign this tag"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1447 paperless_mail/models.py:282
|
#: documents/models.py:1443 paperless_mail/models.py:282
|
||||||
msgid "assign this document type"
|
msgid "assign this document type"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1456 paperless_mail/models.py:296
|
#: documents/models.py:1452 paperless_mail/models.py:296
|
||||||
msgid "assign this correspondent"
|
msgid "assign this correspondent"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1465
|
#: documents/models.py:1461
|
||||||
msgid "assign this storage path"
|
msgid "assign this storage path"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1474
|
#: documents/models.py:1470
|
||||||
msgid "assign this owner"
|
msgid "assign this owner"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1481
|
#: documents/models.py:1477
|
||||||
msgid "grant view permissions to these users"
|
msgid "grant view permissions to these users"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1488
|
#: documents/models.py:1484
|
||||||
msgid "grant view permissions to these groups"
|
msgid "grant view permissions to these groups"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1495
|
#: documents/models.py:1491
|
||||||
msgid "grant change permissions to these users"
|
msgid "grant change permissions to these users"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1502
|
#: documents/models.py:1498
|
||||||
msgid "grant change permissions to these groups"
|
msgid "grant change permissions to these groups"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1509
|
#: documents/models.py:1505
|
||||||
msgid "assign these custom fields"
|
msgid "assign these custom fields"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1513
|
#: documents/models.py:1509
|
||||||
msgid "custom field values"
|
msgid "custom field values"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1517
|
#: documents/models.py:1513
|
||||||
msgid "Optional values to assign to the custom fields."
|
msgid "Optional values to assign to the custom fields."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1526
|
#: documents/models.py:1522
|
||||||
msgid "remove these tag(s)"
|
msgid "remove these tag(s)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1531
|
#: documents/models.py:1527
|
||||||
msgid "remove all tags"
|
msgid "remove all tags"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1538
|
#: documents/models.py:1534
|
||||||
msgid "remove these document type(s)"
|
msgid "remove these document type(s)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1543
|
#: documents/models.py:1539
|
||||||
msgid "remove all document types"
|
msgid "remove all document types"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1550
|
#: documents/models.py:1546
|
||||||
msgid "remove these correspondent(s)"
|
msgid "remove these correspondent(s)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1555
|
#: documents/models.py:1551
|
||||||
msgid "remove all correspondents"
|
msgid "remove all correspondents"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1562
|
#: documents/models.py:1558
|
||||||
msgid "remove these storage path(s)"
|
msgid "remove these storage path(s)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1567
|
#: documents/models.py:1563
|
||||||
msgid "remove all storage paths"
|
msgid "remove all storage paths"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1574
|
#: documents/models.py:1570
|
||||||
msgid "remove these owner(s)"
|
msgid "remove these owner(s)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1579
|
#: documents/models.py:1575
|
||||||
msgid "remove all owners"
|
msgid "remove all owners"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1586
|
#: documents/models.py:1582
|
||||||
msgid "remove view permissions for these users"
|
msgid "remove view permissions for these users"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1593
|
#: documents/models.py:1589
|
||||||
msgid "remove view permissions for these groups"
|
msgid "remove view permissions for these groups"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1600
|
#: documents/models.py:1596
|
||||||
msgid "remove change permissions for these users"
|
msgid "remove change permissions for these users"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1607
|
#: documents/models.py:1603
|
||||||
msgid "remove change permissions for these groups"
|
msgid "remove change permissions for these groups"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1612
|
#: documents/models.py:1608
|
||||||
msgid "remove all permissions"
|
msgid "remove all permissions"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1619
|
#: documents/models.py:1615
|
||||||
msgid "remove these custom fields"
|
msgid "remove these custom fields"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1624
|
#: documents/models.py:1620
|
||||||
msgid "remove all custom fields"
|
msgid "remove all custom fields"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1633
|
#: documents/models.py:1629
|
||||||
msgid "email"
|
msgid "email"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1642
|
#: documents/models.py:1638
|
||||||
msgid "webhook"
|
msgid "webhook"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1646
|
#: documents/models.py:1642
|
||||||
msgid "passwords"
|
msgid "passwords"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1650
|
#: documents/models.py:1646
|
||||||
msgid ""
|
msgid ""
|
||||||
"Passwords to try when removing PDF protection. Separate with commas or new "
|
"Passwords to try when removing PDF protection. Separate with commas or new "
|
||||||
"lines."
|
"lines."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1655
|
#: documents/models.py:1651
|
||||||
msgid "workflow action"
|
msgid "workflow action"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1656
|
#: documents/models.py:1652
|
||||||
msgid "workflow actions"
|
msgid "workflow actions"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1671
|
#: documents/models.py:1667
|
||||||
msgid "triggers"
|
msgid "triggers"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1678
|
#: documents/models.py:1674
|
||||||
msgid "actions"
|
msgid "actions"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1681 paperless_mail/models.py:154
|
#: documents/models.py:1677 paperless_mail/models.py:154
|
||||||
msgid "enabled"
|
msgid "enabled"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1692
|
#: documents/models.py:1688
|
||||||
msgid "workflow"
|
msgid "workflow"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1696
|
#: documents/models.py:1692
|
||||||
msgid "workflow trigger type"
|
msgid "workflow trigger type"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1710
|
#: documents/models.py:1706
|
||||||
msgid "date run"
|
msgid "date run"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1716
|
#: documents/models.py:1712
|
||||||
msgid "workflow run"
|
msgid "workflow run"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1717
|
#: documents/models.py:1713
|
||||||
msgid "workflow runs"
|
msgid "workflow runs"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ def get_embedding_model() -> BaseEmbedding:
|
|||||||
return OpenAIEmbedding(
|
return OpenAIEmbedding(
|
||||||
model=config.llm_embedding_model or "text-embedding-3-small",
|
model=config.llm_embedding_model or "text-embedding-3-small",
|
||||||
api_key=config.llm_api_key,
|
api_key=config.llm_api_key,
|
||||||
api_base=config.llm_endpoint or None,
|
|
||||||
)
|
)
|
||||||
case LLMEmbeddingBackend.HUGGINGFACE:
|
case LLMEmbeddingBackend.HUGGINGFACE:
|
||||||
return HuggingFaceEmbedding(
|
return HuggingFaceEmbedding(
|
||||||
|
|||||||
@@ -65,14 +65,12 @@ def test_get_embedding_model_openai(mock_ai_config):
|
|||||||
mock_ai_config.return_value.llm_embedding_backend = LLMEmbeddingBackend.OPENAI
|
mock_ai_config.return_value.llm_embedding_backend = LLMEmbeddingBackend.OPENAI
|
||||||
mock_ai_config.return_value.llm_embedding_model = "text-embedding-3-small"
|
mock_ai_config.return_value.llm_embedding_model = "text-embedding-3-small"
|
||||||
mock_ai_config.return_value.llm_api_key = "test_api_key"
|
mock_ai_config.return_value.llm_api_key = "test_api_key"
|
||||||
mock_ai_config.return_value.llm_endpoint = "http://test-url"
|
|
||||||
|
|
||||||
with patch("paperless_ai.embedding.OpenAIEmbedding") as MockOpenAIEmbedding:
|
with patch("paperless_ai.embedding.OpenAIEmbedding") as MockOpenAIEmbedding:
|
||||||
model = get_embedding_model()
|
model = get_embedding_model()
|
||||||
MockOpenAIEmbedding.assert_called_once_with(
|
MockOpenAIEmbedding.assert_called_once_with(
|
||||||
model="text-embedding-3-small",
|
model="text-embedding-3-small",
|
||||||
api_key="test_api_key",
|
api_key="test_api_key",
|
||||||
api_base="http://test-url",
|
|
||||||
)
|
)
|
||||||
assert model == MockOpenAIEmbedding.return_value
|
assert model == MockOpenAIEmbedding.return_value
|
||||||
|
|
||||||
|
|||||||
136
uv.lock
generated
136
uv.lock
generated
@@ -1139,14 +1139,14 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "django-soft-delete"
|
name = "django-soft-delete"
|
||||||
version = "1.0.22"
|
version = "1.0.23"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "django", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
{ name = "django", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/98/d1/c990b731676f93bd4594dee4b5133df52f5d0eee1eb8a969b4030014ac54/django_soft_delete-1.0.22.tar.gz", hash = "sha256:32d0bb95f180c28a40163e78a558acc18901fd56011f91f8ee735c171a6d4244", size = 21982, upload-time = "2025-10-25T13:11:46.199Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/aa/98/c7c52a85b070b1703774df817b6460a7714655302a2d503f6447544f1a29/django_soft_delete-1.0.23.tar.gz", hash = "sha256:814659f0d19d4f2afc58b31ff73f88f0af66715ccef3b4fcd8f6b3a011d59b2a", size = 22458, upload-time = "2026-02-21T17:48:41.345Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/f5/c2/fca2bf69b7ca7e18aed9ac059e89f1043663e207a514e8fb652450e49631/django_soft_delete-1.0.22-py3-none-any.whl", hash = "sha256:81973c541d21452d249151085d617ebbfb5ec463899f47cd6b1306677481e94c", size = 19221, upload-time = "2025-10-25T13:11:44.755Z" },
|
{ url = "https://files.pythonhosted.org/packages/91/9e/77375a163c340fff03d037eac7d970ce006626e6c3aea87b5d159f052f8b/django_soft_delete-1.0.23-py3-none-any.whl", hash = "sha256:dd2133d4925d58308680f389daa2e150abf7b81a4f0abbbf2161a9db3b9f1e74", size = 19308, upload-time = "2026-02-21T17:48:39.974Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2181,7 +2181,7 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "llama-index-core"
|
name = "llama-index-core"
|
||||||
version = "0.14.13"
|
version = "0.14.15"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "aiohttp", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
{ name = "aiohttp", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
||||||
@@ -2209,14 +2209,15 @@ dependencies = [
|
|||||||
{ name = "sqlalchemy", extra = ["asyncio"], marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
{ name = "sqlalchemy", extra = ["asyncio"], marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
||||||
{ name = "tenacity", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
{ name = "tenacity", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
||||||
{ name = "tiktoken", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
{ name = "tiktoken", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
||||||
|
{ name = "tinytag", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
||||||
{ name = "tqdm", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
{ name = "tqdm", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
||||||
{ name = "typing-extensions", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
{ name = "typing-extensions", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
||||||
{ name = "typing-inspect", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
{ name = "typing-inspect", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
||||||
{ name = "wrapt", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
{ name = "wrapt", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/74/54/d6043a088e5e9c1d62300db7ad0ef417c6b9a92f7b4a5cade066aeafdaca/llama_index_core-0.14.13.tar.gz", hash = "sha256:c3b30d20ae0407e5d0a1d35bb3376a98e242661ebfc22da754b5a3da1f8108c0", size = 11589074, upload-time = "2026-01-21T20:44:16.287Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/0c/4f/7c714bdf94dd229707b43e7f8cedf3aed0a99938fd46a9ad8a418c199988/llama_index_core-0.14.15.tar.gz", hash = "sha256:3766aeeb95921b3a2af8c2a51d844f75f404215336e1639098e3652db52c68ce", size = 11593505, upload-time = "2026-02-18T19:05:48.274Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/76/59/9769f03f1cccadcc014b3b65c166de18999b51459a0f0a579d80f6c91d80/llama_index_core-0.14.13-py3-none-any.whl", hash = "sha256:392f0a5a09433e9dea786964ef5fe5ca2a2b10aee9f979a9507c19a14da2a20a", size = 11934761, upload-time = "2026-01-21T20:44:18.892Z" },
|
{ url = "https://files.pythonhosted.org/packages/41/9e/262f6465ee4fffa40698b3cc2177e377ce7d945d3bd8b7d9c6b09448625d/llama_index_core-0.14.15-py3-none-any.whl", hash = "sha256:e02b321c10673871a38aaefdc4a93d5ae8ec324cad4408683189e5a1aa1e3d52", size = 11937002, upload-time = "2026-02-18T19:05:45.855Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2274,27 +2275,27 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "llama-index-llms-openai"
|
name = "llama-index-llms-openai"
|
||||||
version = "0.6.18"
|
version = "0.6.19"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "llama-index-core", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
{ name = "llama-index-core", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
||||||
{ name = "openai", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
{ name = "openai", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/56/78/298de76242aee7f5fdd65a0bffb541b3f81759613de1e8ebc719eec8e8af/llama_index_llms_openai-0.6.18.tar.gz", hash = "sha256:36c0256a7a211bbbc5ecc00d3f2caa9730eea1971ced3b68b7c94025c0448020", size = 25946, upload-time = "2026-02-06T12:01:03.095Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/42/f0/810b09cab0d56de6f9476642d0e016c779f2ac3ec7845eb44ddc12a1796d/llama_index_llms_openai-0.6.19.tar.gz", hash = "sha256:a5e0fcddb7da875759406036e09b949cd64a2bb98da709d933147e41e0e6f78a", size = 25956, upload-time = "2026-02-20T11:18:03.527Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/39/46/5a4b62108fb94febe27d35c8476dea042d7a609ee4bf14f5b61f03d5a75a/llama_index_llms_openai-0.6.18-py3-none-any.whl", hash = "sha256:73bbbf233d38116d48350391a3649884829564f4c8f6168c8fa3f3ae1b557376", size = 26945, upload-time = "2026-02-06T12:01:01.25Z" },
|
{ url = "https://files.pythonhosted.org/packages/3c/dd/a8d4e90dad458c830f364e9e7614fad4d2eb8b61c46974c760b08053d495/llama_index_llms_openai-0.6.19-py3-none-any.whl", hash = "sha256:0e83126158f6eb51c153f2b1f7b729bb4bfb6af0191d65b33754b4512180befd", size = 26958, upload-time = "2026-02-20T11:18:02.545Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "llama-index-vector-stores-faiss"
|
name = "llama-index-vector-stores-faiss"
|
||||||
version = "0.5.2"
|
version = "0.5.3"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "llama-index-core", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
{ name = "llama-index-core", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/2d/5f/c4ae340f178f202cf09dcc24dd0953a41d9ab24bc33e1f7220544ba86e41/llama_index_vector_stores_faiss-0.5.2.tar.gz", hash = "sha256:924504765e68b1f84ec602feb2d9516be6a6c12fad5e133f19cc5da3b23f4282", size = 5910, upload-time = "2025-12-17T21:01:13.21Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/c5/e6/57da31b38d173cd9124fdcdd47487b9a917b69bd49e8f6e551407ccfa860/llama_index_vector_stores_faiss-0.5.3.tar.gz", hash = "sha256:9620b1e27e96233fda88878c453532fba6061cf7ba7a53698a34703faab21ece", size = 6048, upload-time = "2026-02-12T14:22:14.612Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/8f/c1/c8317250c2a83d1d439814d1a7f41fa34a23c224b3099da898f08a249859/llama_index_vector_stores_faiss-0.5.2-py3-none-any.whl", hash = "sha256:72a3a03d9f25c70bbcc8c61aa860cd1db69f2a8070606ecc3266d767b71ff2a2", size = 7605, upload-time = "2025-12-17T21:01:12.429Z" },
|
{ url = "https://files.pythonhosted.org/packages/ed/ad/ad192dd624ca2875b8ca74e55fddf9b083d6614524004f7830379d0a0cfd/llama_index_vector_stores_faiss-0.5.3-py3-none-any.whl", hash = "sha256:ef186e38a820e696a1adca15432c8539d73f2959eb05671011db21091a286c8c", size = 7738, upload-time = "2026-02-12T14:22:13.756Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2766,9 +2767,9 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mysqlclient"
|
name = "mysqlclient"
|
||||||
version = "2.2.7"
|
version = "2.2.8"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/61/68/810093cb579daae426794bbd9d88aa830fae296e85172d18cb0f0e5dd4bc/mysqlclient-2.2.7.tar.gz", hash = "sha256:24ae22b59416d5fcce7e99c9d37548350b4565baac82f95e149cac6ce4163845", size = 91383, upload-time = "2025-01-10T12:06:00.763Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/eb/b0/9df076488cb2e536d40ce6dbd4273c1f20a386e31ffe6e7cb613902b3c2a/mysqlclient-2.2.8.tar.gz", hash = "sha256:8ed20c5615a915da451bb308c7d0306648a4fd9a2809ba95c992690006306199", size = 92287, upload-time = "2026-02-10T10:58:37.405Z" }
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nest-asyncio"
|
name = "nest-asyncio"
|
||||||
@@ -3537,23 +3538,23 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "prek"
|
name = "prek"
|
||||||
version = "0.3.2"
|
version = "0.3.3"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/d3/f5/ee52def928dd1355c20bcfcf765e1e61434635c33f3075e848e7b83a157b/prek-0.3.2.tar.gz", hash = "sha256:dce0074ff1a21290748ca567b4bda7553ee305a8c7b14d737e6c58364a499364", size = 334229, upload-time = "2026-02-06T13:49:47.539Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/bf/f1/7613dc8347a33e40fc5b79eec6bc7d458d8bbc339782333d8433b665f86f/prek-0.3.3.tar.gz", hash = "sha256:117bd46ebeb39def24298ce021ccc73edcf697b81856fcff36d762dd56093f6f", size = 343697, upload-time = "2026-02-15T13:33:28.723Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/76/69/70a5fc881290a63910494df2677c0fb241d27cfaa435bbcd0de5cd2e2443/prek-0.3.2-py3-none-linux_armv6l.whl", hash = "sha256:4f352f9c3fc98aeed4c8b2ec4dbf16fc386e45eea163c44d67e5571489bd8e6f", size = 4614960, upload-time = "2026-02-06T13:50:05.818Z" },
|
{ url = "https://files.pythonhosted.org/packages/2d/8b/dce13d2a3065fd1e8ffce593a0e51c4a79c3cde9c9a15dc0acc8d9d1573d/prek-0.3.3-py3-none-linux_armv6l.whl", hash = "sha256:e8629cac4bdb131be8dc6e5a337f0f76073ad34a8305f3fe2bc1ab6201ede0a4", size = 4644636, upload-time = "2026-02-15T13:33:43.609Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/c0/15/a82d5d32a2207ccae5d86ea9e44f2b93531ed000faf83a253e8d1108e026/prek-0.3.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:4a000cfbc3a6ec7d424f8be3c3e69ccd595448197f92daac8652382d0acc2593", size = 4622889, upload-time = "2026-02-06T13:49:53.662Z" },
|
{ url = "https://files.pythonhosted.org/packages/01/30/06ab4dbe7ce02a8ce833e92deb1d9a8e85ae9d40e33d1959a2070b7494c6/prek-0.3.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:4b9e819b9e4118e1e785047b1c8bd9aec7e4d836ed034cb58b7db5bcaaf49437", size = 4651410, upload-time = "2026-02-15T13:33:34.277Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/89/75/ea833b58a12741397017baef9b66a6e443bfa8286ecbd645d14111446280/prek-0.3.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:5436bdc2702cbd7bcf9e355564ae66f8131211e65fefae54665a94a07c3d450a", size = 4239653, upload-time = "2026-02-06T13:50:02.88Z" },
|
{ url = "https://files.pythonhosted.org/packages/d4/fc/da3bc5cb38471e7192eda06b7a26b7c24ef83e82da2c1dbc145f2bf33640/prek-0.3.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:bf29db3b5657c083eb8444c25aadeeec5167dc492e9019e188f87932f01ea50a", size = 4273163, upload-time = "2026-02-15T13:33:42.106Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/10/b4/d9c3885987afac6e20df4cb7db14e3b0d5a08a77ae4916488254ebac4d0b/prek-0.3.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:0161b5f584f9e7f416d6cf40a17b98f17953050ff8d8350ec60f20fe966b86b6", size = 4595101, upload-time = "2026-02-06T13:49:49.813Z" },
|
{ url = "https://files.pythonhosted.org/packages/b4/74/47839395091e2937beced81a5dd2f8ea9c8239c853da8611aaf78ee21a8b/prek-0.3.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:ae09736149815b26e64a9d350ca05692bab32c2afdf2939114d3211aaad68a3e", size = 4631808, upload-time = "2026-02-15T13:33:20.076Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/21/a6/1a06473ed83dbc898de22838abdb13954e2583ce229f857f61828384634c/prek-0.3.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4e641e8533bca38797eebb49aa89ed0e8db0e61225943b27008c257e3af4d631", size = 4521978, upload-time = "2026-02-06T13:49:41.266Z" },
|
{ url = "https://files.pythonhosted.org/packages/e2/89/3f5ef6f7c928c017cb63b029349d6bc03598ab7f6979d4a770ce02575f82/prek-0.3.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:856c2b55c51703c366bb4ce81c6a91102b70573a9fc8637db2ac61c66e4565f9", size = 4548959, upload-time = "2026-02-15T13:33:36.325Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/0c/5e/c38390d5612e6d86b32151c1d2fdab74a57913473193591f0eb00c894c21/prek-0.3.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfca1810d49d3f9ef37599c958c4e716bc19a1d78a7e88cbdcb332e0b008994f", size = 4829108, upload-time = "2026-02-06T13:49:44.598Z" },
|
{ url = "https://files.pythonhosted.org/packages/b2/18/80002c4c4475f90ca025f27739a016927a0e5d905c60612fc95da1c56ab7/prek-0.3.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3acdf13a018f685beaff0a71d4b0d2ccbab4eaa1aced6d08fd471c1a654183eb", size = 4862256, upload-time = "2026-02-15T13:33:37.754Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/80/a6/cecce2ab623747ff65ed990bb0d95fa38449ee19b348234862acf9392fff/prek-0.3.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5d69d754299a95a85dc20196f633232f306bee7e7c8cba61791f49ce70404ec", size = 5357520, upload-time = "2026-02-06T13:49:48.512Z" },
|
{ url = "https://files.pythonhosted.org/packages/c5/25/648bf084c2468fa7cfcdbbe9e59956bbb31b81f36e113bc9107d80af26a7/prek-0.3.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0f035667a8bd0a77b2bfa2b2e125da8cb1793949e9eeef0d8daab7f8ac8b57fe", size = 5404486, upload-time = "2026-02-15T13:33:39.239Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/a5/18/d6bcb29501514023c76d55d5cd03bdbc037737c8de8b6bc41cdebfb1682c/prek-0.3.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:539dcb90ad9b20837968539855df6a29493b328a1ae87641560768eed4f313b0", size = 4852635, upload-time = "2026-02-06T13:49:58.347Z" },
|
{ url = "https://files.pythonhosted.org/packages/8b/43/261fb60a11712a327da345912bd8b338dc5a050199de800faafa278a6133/prek-0.3.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d09b2ad14332eede441d977de08eb57fb3f61226ed5fd2ceb7aadf5afcdb6794", size = 4887513, upload-time = "2026-02-15T13:33:40.702Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/1b/0a/ae46f34ba27ba87aea5c9ad4ac9cd3e07e014fd5079ae079c84198f62118/prek-0.3.2-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:1998db3d0cbe243984736c82232be51318f9192e2433919a6b1c5790f600b5fd", size = 4599484, upload-time = "2026-02-06T13:49:43.296Z" },
|
{ url = "https://files.pythonhosted.org/packages/c7/2c/581e757ee57ec6046b32e0ee25660fc734bc2622c319f57119c49c0cab58/prek-0.3.3-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:c0c3ffac16e37a9daba43a7e8316778f5809b70254be138761a8b5b9ef0df28e", size = 4632336, upload-time = "2026-02-15T13:33:25.867Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/1a/a9/73bfb5b3f7c3583f9b0d431924873928705cdef6abb3d0461c37254a681b/prek-0.3.2-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:07ab237a5415a3e8c0db54de9d63899bcd947624bdd8820d26f12e65f8d19eb7", size = 4657694, upload-time = "2026-02-06T13:50:01.074Z" },
|
{ url = "https://files.pythonhosted.org/packages/d5/d8/aa276ce5d11b77882da4102ca0cb7161095831105043ae7979bbfdcc3dc4/prek-0.3.3-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:a3dc7720b580c07c0386e17af2486a5b4bc2f6cc57034a288a614dcbc4abe555", size = 4679370, upload-time = "2026-02-15T13:33:22.247Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/a7/bc/0994bc176e1a80110fad3babce2c98b0ac4007630774c9e18fc200a34781/prek-0.3.2-py3-none-musllinux_1_1_armv7l.whl", hash = "sha256:0ced19701d69c14a08125f14a5dd03945982edf59e793c73a95caf4697a7ac30", size = 4509337, upload-time = "2026-02-06T13:49:54.891Z" },
|
{ url = "https://files.pythonhosted.org/packages/70/19/9d4fa7bde428e58d9f48a74290c08736d42aeb5690dcdccc7a713e34a449/prek-0.3.3-py3-none-musllinux_1_1_armv7l.whl", hash = "sha256:60e0fa15da5020a03df2ee40268145ec5b88267ec2141a205317ad4df8c992d6", size = 4540316, upload-time = "2026-02-15T13:33:24.088Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/f9/13/e73f85f65ba8f626468e5d1694ab3763111513da08e0074517f40238c061/prek-0.3.2-py3-none-musllinux_1_1_i686.whl", hash = "sha256:ffb28189f976fa111e770ee94e4f298add307714568fb7d610c8a7095cb1ce59", size = 4697350, upload-time = "2026-02-06T13:50:04.526Z" },
|
{ url = "https://files.pythonhosted.org/packages/25/b5/973cce29257e0b47b16cc9b4c162772ea01dbb7c080791ea0c068e106e05/prek-0.3.3-py3-none-musllinux_1_1_i686.whl", hash = "sha256:553515da9586d9624dc42db32b744fdb91cf62b053753037a0cadb3c2d8d82a2", size = 4724566, upload-time = "2026-02-15T13:33:29.832Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/14/47/98c46dcd580305b9960252a4eb966f1a7b1035c55c363f378d85662ba400/prek-0.3.2-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:f63134b3eea14421789a7335d86f99aee277cb520427196f2923b9260c60e5c5", size = 4955860, upload-time = "2026-02-06T13:49:56.581Z" },
|
{ url = "https://files.pythonhosted.org/packages/d6/8b/ad8b2658895a8ed2b0bc630bf38686fe38b7ff2c619c58953a80e4de3048/prek-0.3.3-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:9512cf370e0d1496503463a4a65621480efb41b487841a9e9ff1661edf14b238", size = 4995072, upload-time = "2026-02-15T13:33:27.417Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -4618,24 +4619,24 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruff"
|
name = "ruff"
|
||||||
version = "0.15.0"
|
version = "0.15.2"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/c8/39/5cee96809fbca590abea6b46c6d1c586b49663d1d2830a751cc8fc42c666/ruff-0.15.0.tar.gz", hash = "sha256:6bdea47cdbea30d40f8f8d7d69c0854ba7c15420ec75a26f463290949d7f7e9a", size = 4524893, upload-time = "2026-02-03T17:53:35.357Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/06/04/eab13a954e763b0606f460443fcbf6bb5a0faf06890ea3754ff16523dce5/ruff-0.15.2.tar.gz", hash = "sha256:14b965afee0969e68bb871eba625343b8673375f457af4abe98553e8bbb98342", size = 4558148, upload-time = "2026-02-19T22:32:20.271Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/bc/88/3fd1b0aa4b6330d6aaa63a285bc96c9f71970351579152d231ed90914586/ruff-0.15.0-py3-none-linux_armv6l.whl", hash = "sha256:aac4ebaa612a82b23d45964586f24ae9bc23ca101919f5590bdb368d74ad5455", size = 10354332, upload-time = "2026-02-03T17:52:54.892Z" },
|
{ url = "https://files.pythonhosted.org/packages/2f/70/3a4dc6d09b13cb3e695f28307e5d889b2e1a66b7af9c5e257e796695b0e6/ruff-0.15.2-py3-none-linux_armv6l.whl", hash = "sha256:120691a6fdae2f16d65435648160f5b81a9625288f75544dc40637436b5d3c0d", size = 10430565, upload-time = "2026-02-19T22:32:41.824Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/72/f6/62e173fbb7eb75cc29fe2576a1e20f0a46f671a2587b5f604bfb0eaf5f6f/ruff-0.15.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:dcd4be7cc75cfbbca24a98d04d0b9b36a270d0833241f776b788d59f4142b14d", size = 10767189, upload-time = "2026-02-03T17:53:19.778Z" },
|
{ url = "https://files.pythonhosted.org/packages/71/0b/bb8457b56185ece1305c666dc895832946d24055be90692381c31d57466d/ruff-0.15.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:a89056d831256099658b6bba4037ac6dd06f49d194199215befe2bb10457ea5e", size = 10820354, upload-time = "2026-02-19T22:32:07.366Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/99/e4/968ae17b676d1d2ff101d56dc69cf333e3a4c985e1ec23803df84fc7bf9e/ruff-0.15.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d747e3319b2bce179c7c1eaad3d884dc0a199b5f4d5187620530adf9105268ce", size = 10075384, upload-time = "2026-02-03T17:53:29.241Z" },
|
{ url = "https://files.pythonhosted.org/packages/2d/c1/e0532d7f9c9e0b14c46f61b14afd563298b8b83f337b6789ddd987e46121/ruff-0.15.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e36dee3a64be0ebd23c86ffa3aa3fd3ac9a712ff295e192243f814a830b6bd87", size = 10170767, upload-time = "2026-02-19T22:32:13.188Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/a2/bf/9843c6044ab9e20af879c751487e61333ca79a2c8c3058b15722386b8cae/ruff-0.15.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:650bd9c56ae03102c51a5e4b554d74d825ff3abe4db22b90fd32d816c2e90621", size = 10481363, upload-time = "2026-02-03T17:52:43.332Z" },
|
{ url = "https://files.pythonhosted.org/packages/47/e8/da1aa341d3af017a21c7a62fb5ec31d4e7ad0a93ab80e3a508316efbcb23/ruff-0.15.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9fb47b6d9764677f8c0a193c0943ce9a05d6763523f132325af8a858eadc2b9", size = 10529591, upload-time = "2026-02-19T22:32:02.547Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/55/d9/4ada5ccf4cd1f532db1c8d44b6f664f2208d3d93acbeec18f82315e15193/ruff-0.15.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a6664b7eac559e3048223a2da77769c2f92b43a6dfd4720cef42654299a599c9", size = 10187736, upload-time = "2026-02-03T17:53:00.522Z" },
|
{ url = "https://files.pythonhosted.org/packages/93/74/184fbf38e9f3510231fbc5e437e808f0b48c42d1df9434b208821efcd8d6/ruff-0.15.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f376990f9d0d6442ea9014b19621d8f2aaf2b8e39fdbfc79220b7f0c596c9b80", size = 10260771, upload-time = "2026-02-19T22:32:36.938Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/86/e2/f25eaecd446af7bb132af0a1d5b135a62971a41f5366ff41d06d25e77a91/ruff-0.15.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f811f97b0f092b35320d1556f3353bf238763420ade5d9e62ebd2b73f2ff179", size = 10968415, upload-time = "2026-02-03T17:53:15.705Z" },
|
{ url = "https://files.pythonhosted.org/packages/05/ac/605c20b8e059a0bc4b42360414baa4892ff278cec1c91fff4be0dceedefd/ruff-0.15.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2dcc987551952d73cbf5c88d9fdee815618d497e4df86cd4c4824cc59d5dd75f", size = 11045791, upload-time = "2026-02-19T22:32:31.642Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/e7/dc/f06a8558d06333bf79b497d29a50c3a673d9251214e0d7ec78f90b30aa79/ruff-0.15.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:761ec0a66680fab6454236635a39abaf14198818c8cdf691e036f4bc0f406b2d", size = 11809643, upload-time = "2026-02-03T17:53:23.031Z" },
|
{ url = "https://files.pythonhosted.org/packages/fd/52/db6e419908f45a894924d410ac77d64bdd98ff86901d833364251bd08e22/ruff-0.15.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:42a47fd785cbe8c01b9ff45031af875d101b040ad8f4de7bbb716487c74c9a77", size = 11879271, upload-time = "2026-02-19T22:32:29.305Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/dd/45/0ece8db2c474ad7df13af3a6d50f76e22a09d078af63078f005057ca59eb/ruff-0.15.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:940f11c2604d317e797b289f4f9f3fa5555ffe4fb574b55ed006c3d9b6f0eb78", size = 11234787, upload-time = "2026-02-03T17:52:46.432Z" },
|
{ url = "https://files.pythonhosted.org/packages/3e/d8/7992b18f2008bdc9231d0f10b16df7dda964dbf639e2b8b4c1b4e91b83af/ruff-0.15.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cbe9f49354866e575b4c6943856989f966421870e85cd2ac94dccb0a9dcb2fea", size = 11303707, upload-time = "2026-02-19T22:32:22.492Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/8a/d9/0e3a81467a120fd265658d127db648e4d3acfe3e4f6f5d4ea79fac47e587/ruff-0.15.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bcbca3d40558789126da91d7ef9a7c87772ee107033db7191edefa34e2c7f1b4", size = 11112797, upload-time = "2026-02-03T17:52:49.274Z" },
|
{ url = "https://files.pythonhosted.org/packages/d7/02/849b46184bcfdd4b64cde61752cc9a146c54759ed036edd11857e9b8443b/ruff-0.15.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b7a672c82b5f9887576087d97be5ce439f04bbaf548ee987b92d3a7dede41d3a", size = 11149151, upload-time = "2026-02-19T22:32:44.234Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/b2/cb/8c0b3b0c692683f8ff31351dfb6241047fa873a4481a76df4335a8bff716/ruff-0.15.0-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:9a121a96db1d75fa3eb39c4539e607f628920dd72ff1f7c5ee4f1b768ac62d6e", size = 11033133, upload-time = "2026-02-03T17:53:33.105Z" },
|
{ url = "https://files.pythonhosted.org/packages/70/04/f5284e388bab60d1d3b99614a5a9aeb03e0f333847e2429bebd2aaa1feec/ruff-0.15.2-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:72ecc64f46f7019e2bcc3cdc05d4a7da958b629a5ab7033195e11a438403d956", size = 11091132, upload-time = "2026-02-19T22:32:24.691Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/f8/5e/23b87370cf0f9081a8c89a753e69a4e8778805b8802ccfe175cc410e50b9/ruff-0.15.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5298d518e493061f2eabd4abd067c7e4fb89e2f63291c94332e35631c07c3662", size = 10442646, upload-time = "2026-02-03T17:53:06.278Z" },
|
{ url = "https://files.pythonhosted.org/packages/fa/ae/88d844a21110e14d92cf73d57363fab59b727ebeabe78009b9ccb23500af/ruff-0.15.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:8dcf243b15b561c655c1ef2f2b0050e5d50db37fe90115507f6ff37d865dc8b4", size = 10504717, upload-time = "2026-02-19T22:32:26.75Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/e1/9a/3c94de5ce642830167e6d00b5c75aacd73e6347b4c7fc6828699b150a5ee/ruff-0.15.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:afb6e603d6375ff0d6b0cee563fa21ab570fd15e65c852cb24922cef25050cf1", size = 10195750, upload-time = "2026-02-03T17:53:26.084Z" },
|
{ url = "https://files.pythonhosted.org/packages/64/27/867076a6ada7f2b9c8292884ab44d08fd2ba71bd2b5364d4136f3cd537e1/ruff-0.15.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:dab6941c862c05739774677c6273166d2510d254dac0695c0e3f5efa1b5585de", size = 10263122, upload-time = "2026-02-19T22:32:10.036Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/30/15/e396325080d600b436acc970848d69df9c13977942fb62bb8722d729bee8/ruff-0.15.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:77e515f6b15f828b94dc17d2b4ace334c9ddb7d9468c54b2f9ed2b9c1593ef16", size = 10676120, upload-time = "2026-02-03T17:53:09.363Z" },
|
{ url = "https://files.pythonhosted.org/packages/e7/ef/faf9321d550f8ebf0c6373696e70d1758e20ccdc3951ad7af00c0956be7c/ruff-0.15.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:1b9164f57fc36058e9a6806eb92af185b0697c9fe4c7c52caa431c6554521e5c", size = 10735295, upload-time = "2026-02-19T22:32:39.227Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/8d/c9/229a23d52a2983de1ad0fb0ee37d36e0257e6f28bfd6b498ee2c76361874/ruff-0.15.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:6f6e80850a01eb13b3e42ee0ebdf6e4497151b48c35051aab51c101266d187a3", size = 11201636, upload-time = "2026-02-03T17:52:57.281Z" },
|
{ url = "https://files.pythonhosted.org/packages/2f/55/e8089fec62e050ba84d71b70e7834b97709ca9b7aba10c1a0b196e493f97/ruff-0.15.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:80d24fcae24d42659db7e335b9e1531697a7102c19185b8dc4a028b952865fd8", size = 11241641, upload-time = "2026-02-19T22:32:34.617Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -4826,7 +4827,7 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sentence-transformers"
|
name = "sentence-transformers"
|
||||||
version = "5.2.2"
|
version = "5.2.3"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "huggingface-hub", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
{ name = "huggingface-hub", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
||||||
@@ -4841,9 +4842,9 @@ dependencies = [
|
|||||||
{ name = "transformers", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
{ name = "transformers", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
||||||
{ name = "typing-extensions", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
{ name = "typing-extensions", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/a6/bc/0bc9c0ec1cf83ab2ec6e6f38667d167349b950fff6dd2086b79bd360eeca/sentence_transformers-5.2.2.tar.gz", hash = "sha256:7033ee0a24bc04c664fd490abf2ef194d387b3a58a97adcc528783ff505159fa", size = 381607, upload-time = "2026-01-27T11:11:02.658Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/5b/30/21664028fc0776eb1ca024879480bbbab36f02923a8ff9e4cae5a150fa35/sentence_transformers-5.2.3.tar.gz", hash = "sha256:3cd3044e1f3fe859b6a1b66336aac502eaae5d3dd7d5c8fc237f37fbf58137c7", size = 381623, upload-time = "2026-02-17T14:05:20.238Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/cc/21/7e925890636791386e81b52878134f114d63072e79fffe14cdcc5e7a5e6a/sentence_transformers-5.2.2-py3-none-any.whl", hash = "sha256:280ac54bffb84c110726b4d8848ba7b7c60813b9034547f8aea6e9a345cd1c23", size = 494106, upload-time = "2026-01-27T11:11:00.983Z" },
|
{ url = "https://files.pythonhosted.org/packages/46/9f/dba4b3e18ebbe1eaa29d9f1764fbc7da0cd91937b83f2b7928d15c5d2d36/sentence_transformers-5.2.3-py3-none-any.whl", hash = "sha256:6437c62d4112b615ddebda362dfc16a4308d604c5b68125ed586e3e95d5b2e30", size = 494225, upload-time = "2026-02-17T14:05:18.596Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -5132,6 +5133,15 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/ab/0d/c1ad6f4016a3968c048545f5d9b8ffebf577774b2ede3e2e352553b685fe/tiktoken-0.12.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5edb8743b88d5be814b1a8a8854494719080c28faaa1ccbef02e87354fe71ef0", size = 1253706, upload-time = "2025-10-06T20:22:33.385Z" },
|
{ url = "https://files.pythonhosted.org/packages/ab/0d/c1ad6f4016a3968c048545f5d9b8ffebf577774b2ede3e2e352553b685fe/tiktoken-0.12.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5edb8743b88d5be814b1a8a8854494719080c28faaa1ccbef02e87354fe71ef0", size = 1253706, upload-time = "2025-10-06T20:22:33.385Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tinytag"
|
||||||
|
version = "2.2.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/98/07/fb260bac73119f369a10e884016516d07cd760b5068e703773f83dd5e7bf/tinytag-2.2.0.tar.gz", hash = "sha256:f15b082510f6e0fc717e597edc8759d6f2d3ff6194ac0f3bcd675a9a09d9b798", size = 38120, upload-time = "2025-12-15T21:10:19.093Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b1/e2/9818fcebb348237389d2ac2fea97cf2b2638378a0866105a45ae9be49728/tinytag-2.2.0-py3-none-any.whl", hash = "sha256:d2cf3ef8ee0f6c854663f77d9d5f8159ee1c834c70f5ea4f214ddc4af8148f79", size = 32861, upload-time = "2025-12-15T21:10:17.63Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokenizers"
|
name = "tokenizers"
|
||||||
version = "0.22.2"
|
version = "0.22.2"
|
||||||
@@ -5480,11 +5490,11 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "types-markdown"
|
name = "types-markdown"
|
||||||
version = "3.10.0.20251106"
|
version = "3.10.2.20260211"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/de/e4/060f0dadd9b551cae77d6407f2bc84b168f918d90650454aff219c1b3ed2/types_markdown-3.10.0.20251106.tar.gz", hash = "sha256:12836f7fcbd7221db8baeb0d3a2f820b95050d0824bfa9665c67b4d144a1afa1", size = 19486, upload-time = "2025-11-06T03:06:44.317Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/6d/2e/35b30a09f6ee8a69142408d3ceb248c4454aa638c0a414d8704a3ef79563/types_markdown-3.10.2.20260211.tar.gz", hash = "sha256:66164310f88c11a58c6c706094c6f8c537c418e3525d33b76276a5fbd66b01ce", size = 19768, upload-time = "2026-02-11T04:19:29.497Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/92/58/f666ca9391f2a8bd33bb0b0797cde6ac3e764866708d5f8aec6fab215320/types_markdown-3.10.0.20251106-py3-none-any.whl", hash = "sha256:2c39512a573899b59efae07e247ba088a75b70e3415e81277692718f430afd7e", size = 25862, upload-time = "2025-11-06T03:06:43.082Z" },
|
{ url = "https://files.pythonhosted.org/packages/54/c9/659fa2df04b232b0bfcd05d2418e683080e91ec68f636f3c0a5a267350e7/types_markdown-3.10.2.20260211-py3-none-any.whl", hash = "sha256:2d94d08587e3738203b3c4479c449845112b171abe8b5cadc9b0c12fcf3e99da", size = 25854, upload-time = "2026-02-11T04:19:28.647Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -6094,7 +6104,7 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zensical"
|
name = "zensical"
|
||||||
version = "0.0.21"
|
version = "0.0.23"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "click", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
{ name = "click", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
||||||
@@ -6105,18 +6115,18 @@ dependencies = [
|
|||||||
{ name = "pyyaml", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
|
{ name = "pyyaml", 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/8a/50/2655b5f72d0c72f4366be580f5e2354ff05280d047ea986fe89570e44589/zensical-0.0.21.tar.gz", hash = "sha256:c13563836fa63a3cabeffd83fe3a770ca740cfa5ae7b85df85d89837e31b3b4a", size = 3819731, upload-time = "2026-02-04T17:47:59.396Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/a3/ab/a65452b4e769552fd5a78c4996d6cf322630d896ddfd55c5433d96485e8b/zensical-0.0.23.tar.gz", hash = "sha256:5c4fc3aaf075df99d8cf41b9f2566e4d588180d9a89493014d3607dfe50ac4bc", size = 3822451, upload-time = "2026-02-11T21:24:38.373Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/1d/98/90710d232cb35b633815fa7b493da542391b89283b6103a5bb4ae9fc0dd9/zensical-0.0.21-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:67404cc70c330246dfb7269bcdb60a25be0bb60a212a09c9c50229a1341b1f84", size = 12237120, upload-time = "2026-02-04T17:47:28.615Z" },
|
{ url = "https://files.pythonhosted.org/packages/66/86/035aa02bd36d26a03a1885bc22a73d4fe61ba0e21d0033cc42baf13d24f6/zensical-0.0.23-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:35d6d3eb803fe73a67187a1a25443408bd02a8dd50e151f4a4bafd40de3f0928", size = 12242966, upload-time = "2026-02-11T21:24:05.894Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/97/fb/4280b3781157e8f051711732192f949bf29beeafd0df3e33c1c8bf9b7a1a/zensical-0.0.21-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:d4fd253ccfbf5af56434124f13bac01344e456c020148369b18d8836b6537c3c", size = 12118047, upload-time = "2026-02-04T17:47:31.369Z" },
|
{ url = "https://files.pythonhosted.org/packages/be/68/335dfbb7efc972964f0610736a0ad243dd8a5dcc2ec76b9ddb84c847a4a4/zensical-0.0.23-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:5973267460a190f348f24d445ff0c01e8ed334fd075947687b305e68257f6b18", size = 12125173, upload-time = "2026-02-11T21:24:08.507Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/74/b3/b7f85ae9cf920cf9f17bf157ae6c274919477148feb7716bf735636caa0e/zensical-0.0.21-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:440e40cdc30a29bf7466bcd6f43ed7bd1c54ea3f1a0fefca65619358b481a5bc", size = 12473440, upload-time = "2026-02-04T17:47:33.577Z" },
|
{ url = "https://files.pythonhosted.org/packages/25/9c/d567da04fbeb077df5cf06a94f947af829ebef0ff5ca7d0ba4910a6cbdf6/zensical-0.0.23-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:953adf1f0b346a6c65fc6e05e6cc1c38a6440fec29c50c76fb29700cc1927006", size = 12489636, upload-time = "2026-02-11T21:24:10.91Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/d8/ac/1dc6e98f79ed19b9f103c88a0bd271f9140565d7d26b64bc1542b3ef6d91/zensical-0.0.21-cp310-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:368e832fc8068e75dc45cab59379db4cefcd81eb116f48d058db8fb7b7aa8d14", size = 12412588, upload-time = "2026-02-04T17:47:36.491Z" },
|
{ url = "https://files.pythonhosted.org/packages/fe/6e/481a3ecf8a7b63a35c67f5be1ea548185d55bb1dacead54f76a9550197b2/zensical-0.0.23-cp310-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:49c1cbd6131dafa056be828e081759184f9b8dd24b99bf38d1e77c8c31b0c720", size = 12421313, upload-time = "2026-02-11T21:24:13.9Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/bd/76/16a580f6dd32b387caa4a41615451e7dddd1917a2ff2e5b08744f41b4e11/zensical-0.0.21-cp310-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f4ab962d47f9dd73510eed168469326c7a452554dfbfdb9cdf85efc7140244df", size = 12749438, upload-time = "2026-02-04T17:47:38.969Z" },
|
{ url = "https://files.pythonhosted.org/packages/ba/aa/a95481547f708432636f5f8155917c90d877c244c62124a084f7448b60b2/zensical-0.0.23-cp310-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f5b7fe22c5d33b2b91899c5df7631ad4ce9cccfabac2560cc92ba73eafe2d297", size = 12761031, upload-time = "2026-02-11T21:24:17.016Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/95/30/4baaa1c910eee61db5f49d0d45f2e550a0027218c618f3dd7f8da966a019/zensical-0.0.21-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b846d53dfce007f056ff31848f87f3f2a388228e24d4851c0cafdce0fa204c9b", size = 12514504, upload-time = "2026-02-04T17:47:41.31Z" },
|
{ url = "https://files.pythonhosted.org/packages/c1/9f/ce1c5af9afd11fe3521a90441aba48c484f98730c6d833d69ee4387ae2e9/zensical-0.0.23-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a3679d6bf6374f503afb74d9f6061da5de83c25922f618042b63a30b16f0389", size = 12527415, upload-time = "2026-02-11T21:24:19.558Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/76/77/931fccae5580b94409a0448a26106f922dcfa7822e7b93cacd2876dd63a8/zensical-0.0.21-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:daac1075552d230d52d621d2e4754ba24d5afcaa201a7a991f1a8d57e320c9de", size = 12647832, upload-time = "2026-02-04T17:47:44.073Z" },
|
{ url = "https://files.pythonhosted.org/packages/a8/b8/13a5d4d99f3b77e7bf4e791ef991a611ca2f108ed7eddf20858544ab0a91/zensical-0.0.23-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:54d981e21a19c3dcec6e7fa77c4421db47389dfdff20d29fea70df8e1be4062e", size = 12665352, upload-time = "2026-02-11T21:24:22.703Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/5b/82/3cf75de64340829d55c87c36704f4d1d8c952bd2cdc8a7bc48cbfb8ab333/zensical-0.0.21-cp310-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:7b380f545adb6d40896f9bd698eb0e1540ed4258d35b83f55f91658d0fdae312", size = 12678537, upload-time = "2026-02-04T17:47:46.899Z" },
|
{ url = "https://files.pythonhosted.org/packages/ad/84/3d0a187ed941826ca26b19a661c41685d8017b2a019afa0d353eb2ebbdba/zensical-0.0.23-cp310-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:afde7865cc3c79c99f6df4a911d638fb2c3b472a1b81367d47163f8e3c36f910", size = 12689042, upload-time = "2026-02-11T21:24:26.118Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/77/91/6f4938dceeaa241f78bbfaf58a94acef10ba18be3468795173e3087abeb6/zensical-0.0.21-cp310-abi3-musllinux_1_2_i686.whl", hash = "sha256:5c2227fdab64616bea94b40b8340bafe00e2e23631cc58eeea1e7267167e6ac5", size = 12822164, upload-time = "2026-02-04T17:47:49.231Z" },
|
{ url = "https://files.pythonhosted.org/packages/f0/65/12466408f428f2cf7140b32d484753db0891debae3c956f4c076b51eeb17/zensical-0.0.23-cp310-abi3-musllinux_1_2_i686.whl", hash = "sha256:c484674d7b0a3e6d39db83914db932249bccdef2efaf8a5669671c66c16f584d", size = 12834779, upload-time = "2026-02-11T21:24:28.788Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/a2/4e/a9c9d25ef0766f767db7b4f09da68da9b3d8a28c3d68cfae01f8e3f9e297/zensical-0.0.21-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:2e0f5154d236ed0f98662ee68785b67e8cd2138ea9d5e26070649e93c22eeee0", size = 12785632, upload-time = "2026-02-04T17:47:52.613Z" },
|
{ url = "https://files.pythonhosted.org/packages/a9/ab/0771ac6ffb30e4f04c20374e3beca9e71c3f81112219cdbd86cdc0e3d337/zensical-0.0.23-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:927d12fe2851f355fb3206809e04641d6651bdd2ff4afe9c205721aa3a32aa82", size = 12797057, upload-time = "2026-02-11T21:24:31.383Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
Reference in New Issue
Block a user