mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2026-02-27 13:56:25 +00:00
Compare commits
3 Commits
chore/enab
...
feature-py
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
98d5d665f0 | ||
|
|
3fa9e75fa0 | ||
|
|
c94b6ce792 |
2
.github/workflows/ci-backend.yml
vendored
2
.github/workflows/ci-backend.yml
vendored
@@ -31,7 +31,7 @@ jobs:
|
|||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
python-version: ['3.10', '3.11', '3.12']
|
python-version: ['3.11', '3.12', '3.13', '3.14']
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
|
|||||||
@@ -1,4 +1,9 @@
|
|||||||
|
# This file configures pre-commit hooks.
|
||||||
|
# See https://pre-commit.com/ for general information
|
||||||
|
# See https://pre-commit.com/hooks.html for a listing of possible hooks
|
||||||
|
# We actually run via https://github.com/j178/prek which is compatible
|
||||||
repos:
|
repos:
|
||||||
|
# General hooks
|
||||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
rev: v6.0.0
|
rev: v6.0.0
|
||||||
hooks:
|
hooks:
|
||||||
@@ -6,16 +11,21 @@ repos:
|
|||||||
- id: check-json
|
- id: check-json
|
||||||
exclude: "tsconfig.*json"
|
exclude: "tsconfig.*json"
|
||||||
- id: check-yaml
|
- id: check-yaml
|
||||||
args: ["--unsafe"]
|
args:
|
||||||
|
- "--unsafe"
|
||||||
- id: check-toml
|
- id: check-toml
|
||||||
- id: check-executables-have-shebangs
|
- id: check-executables-have-shebangs
|
||||||
- id: end-of-file-fixer
|
- id: end-of-file-fixer
|
||||||
exclude_types: [svg, pofile]
|
exclude_types:
|
||||||
|
- svg
|
||||||
|
- pofile
|
||||||
exclude: "(^LICENSE$|^src/documents/static/bootstrap.min.css$)"
|
exclude: "(^LICENSE$|^src/documents/static/bootstrap.min.css$)"
|
||||||
- id: mixed-line-ending
|
- id: mixed-line-ending
|
||||||
args: ["--fix=lf"]
|
args:
|
||||||
|
- "--fix=lf"
|
||||||
- id: trailing-whitespace
|
- id: trailing-whitespace
|
||||||
exclude_types: [svg]
|
exclude_types:
|
||||||
|
- svg
|
||||||
- id: check-case-conflict
|
- id: check-case-conflict
|
||||||
- id: detect-private-key
|
- id: detect-private-key
|
||||||
- repo: https://github.com/codespell-project/codespell
|
- repo: https://github.com/codespell-project/codespell
|
||||||
@@ -23,29 +33,53 @@ repos:
|
|||||||
hooks:
|
hooks:
|
||||||
- id: codespell
|
- id: codespell
|
||||||
additional_dependencies: [tomli]
|
additional_dependencies: [tomli]
|
||||||
exclude_types: [pofile, json]
|
exclude_types:
|
||||||
- repo: https://github.com/google/yamlfmt
|
- pofile
|
||||||
rev: v0.21.0
|
- json
|
||||||
|
# See https://github.com/prettier/prettier/issues/15742 for the fork reason
|
||||||
|
- repo: https://github.com/rbubley/mirrors-prettier
|
||||||
|
rev: 'v3.8.1'
|
||||||
hooks:
|
hooks:
|
||||||
- id: yamlfmt
|
- id: prettier
|
||||||
exclude: "^src-ui/pnpm-lock.yaml"
|
types_or:
|
||||||
types: [yaml]
|
- javascript
|
||||||
|
- ts
|
||||||
|
- markdown
|
||||||
|
additional_dependencies:
|
||||||
|
- prettier@3.3.3
|
||||||
|
- 'prettier-plugin-organize-imports@4.1.0'
|
||||||
|
# Python hooks
|
||||||
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
|
rev: v0.15.0
|
||||||
|
hooks:
|
||||||
|
- id: ruff-check
|
||||||
|
- id: ruff-format
|
||||||
|
- repo: https://github.com/tox-dev/pyproject-fmt
|
||||||
|
rev: "v2.12.1"
|
||||||
|
hooks:
|
||||||
|
- id: pyproject-fmt
|
||||||
|
# Dockerfile hooks
|
||||||
- repo: https://github.com/AleksaC/hadolint-py
|
- repo: https://github.com/AleksaC/hadolint-py
|
||||||
rev: v2.14.0
|
rev: v2.14.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: hadolint
|
- id: hadolint
|
||||||
|
# Shell script hooks
|
||||||
- repo: https://github.com/lovesegfault/beautysh
|
- repo: https://github.com/lovesegfault/beautysh
|
||||||
rev: v6.4.2
|
rev: v6.4.2
|
||||||
hooks:
|
hooks:
|
||||||
- id: beautysh
|
- id: beautysh
|
||||||
types: [file]
|
types: [file]
|
||||||
files: (\.sh$|/run$|/finish$)
|
files: (\.sh$|/run$|/finish$)
|
||||||
args: ["--tab"]
|
args:
|
||||||
|
- "--tab"
|
||||||
- repo: https://github.com/shellcheck-py/shellcheck-py
|
- repo: https://github.com/shellcheck-py/shellcheck-py
|
||||||
rev: "v0.11.0.1"
|
rev: "v0.11.0.1"
|
||||||
hooks:
|
hooks:
|
||||||
- id: shellcheck
|
- id: shellcheck
|
||||||
- repo: https://github.com/tox-dev/pyproject-fmt
|
- repo: https://github.com/google/yamlfmt
|
||||||
rev: "v2.16.2"
|
rev: v0.21.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: pyproject-fmt
|
- id: yamlfmt
|
||||||
|
exclude: "^src-ui/pnpm-lock.yaml"
|
||||||
|
types:
|
||||||
|
- yaml
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
repos:
|
|
||||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
||||||
rev: v0.15.4
|
|
||||||
hooks:
|
|
||||||
- id: ruff-check
|
|
||||||
- id: ruff-format
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
default: true
|
|
||||||
MD013: false # line length -- mdformat handles this
|
|
||||||
MD033: false # inline HTML -- MkDocs uses it
|
|
||||||
MD041: false # first line heading -- not always true in MkDocs
|
|
||||||
MD046: # code block style
|
|
||||||
style: fenced
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
repos:
|
|
||||||
- repo: https://github.com/executablebooks/mdformat
|
|
||||||
rev: 1.0.0
|
|
||||||
hooks:
|
|
||||||
- id: mdformat
|
|
||||||
additional_dependencies:
|
|
||||||
- mdformat-mkdocs
|
|
||||||
- mdformat-ruff
|
|
||||||
- repo: https://github.com/DavidAnson/markdownlint-cli2
|
|
||||||
rev: v0.21.0
|
|
||||||
hooks:
|
|
||||||
- id: markdownlint-cli2
|
|
||||||
args: ["--config", ".markdownlint.yaml"]
|
|
||||||
@@ -3,10 +3,9 @@ name = "paperless-ngx"
|
|||||||
version = "2.20.8"
|
version = "2.20.8"
|
||||||
description = "A community-supported supercharged document management system: scan, index and archive all your physical documents"
|
description = "A community-supported supercharged document management system: scan, index and archive all your physical documents"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.10"
|
requires-python = ">=3.11"
|
||||||
classifiers = [
|
classifiers = [
|
||||||
"Programming Language :: Python :: 3 :: Only",
|
"Programming Language :: Python :: 3 :: Only",
|
||||||
"Programming Language :: Python :: 3.10",
|
|
||||||
"Programming Language :: Python :: 3.11",
|
"Programming Language :: Python :: 3.11",
|
||||||
"Programming Language :: Python :: 3.12",
|
"Programming Language :: Python :: 3.12",
|
||||||
"Programming Language :: Python :: 3.13",
|
"Programming Language :: Python :: 3.13",
|
||||||
@@ -177,7 +176,7 @@ torch = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[tool.ruff]
|
[tool.ruff]
|
||||||
target-version = "py310"
|
target-version = "py311"
|
||||||
line-length = 88
|
line-length = 88
|
||||||
src = [
|
src = [
|
||||||
"src",
|
"src",
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
repos:
|
|
||||||
- repo: https://github.com/rbubley/mirrors-prettier
|
|
||||||
rev: 'v3.8.1'
|
|
||||||
hooks:
|
|
||||||
- id: prettier
|
|
||||||
types_or: [javascript, ts]
|
|
||||||
additional_dependencies:
|
|
||||||
- prettier@3.3.3
|
|
||||||
- 'prettier-plugin-organize-imports@4.1.0'
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
orphan: true
|
|
||||||
|
|
||||||
repos:
|
|
||||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
||||||
rev: v0.15.4
|
|
||||||
hooks:
|
|
||||||
- id: ruff-check
|
|
||||||
- id: ruff-format
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
|
from datetime import UTC
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from datetime import timezone
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
@@ -139,7 +139,7 @@ def thumbnail_last_modified(request: Any, pk: int) -> datetime | None:
|
|||||||
# No cache, get the timestamp and cache the datetime
|
# No cache, get the timestamp and cache the datetime
|
||||||
last_modified = datetime.fromtimestamp(
|
last_modified = datetime.fromtimestamp(
|
||||||
doc.thumbnail_path.stat().st_mtime,
|
doc.thumbnail_path.stat().st_mtime,
|
||||||
tz=timezone.utc,
|
tz=UTC,
|
||||||
)
|
)
|
||||||
cache.set(doc_key, last_modified, CACHE_50_MINUTES)
|
cache.set(doc_key, last_modified, CACHE_50_MINUTES)
|
||||||
return last_modified
|
return last_modified
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import datetime
|
|||||||
import hashlib
|
import hashlib
|
||||||
import os
|
import os
|
||||||
import tempfile
|
import tempfile
|
||||||
from enum import Enum
|
from enum import StrEnum
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
from typing import Final
|
from typing import Final
|
||||||
@@ -80,7 +80,7 @@ class ConsumerError(Exception):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class ConsumerStatusShortMessage(str, Enum):
|
class ConsumerStatusShortMessage(StrEnum):
|
||||||
DOCUMENT_ALREADY_EXISTS = "document_already_exists"
|
DOCUMENT_ALREADY_EXISTS = "document_already_exists"
|
||||||
DOCUMENT_ALREADY_EXISTS_IN_TRASH = "document_already_exists_in_trash"
|
DOCUMENT_ALREADY_EXISTS_IN_TRASH = "document_already_exists_in_trash"
|
||||||
ASN_ALREADY_EXISTS = "asn_already_exists"
|
ASN_ALREADY_EXISTS = "asn_already_exists"
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ import math
|
|||||||
import re
|
import re
|
||||||
from collections import Counter
|
from collections import Counter
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
|
from datetime import UTC
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from datetime import time
|
from datetime import time
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from datetime import timezone
|
|
||||||
from shutil import rmtree
|
from shutil import rmtree
|
||||||
from time import sleep
|
from time import sleep
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
@@ -437,7 +437,7 @@ class ManualResults:
|
|||||||
class LocalDateParser(English):
|
class LocalDateParser(English):
|
||||||
def reverse_timezone_offset(self, d):
|
def reverse_timezone_offset(self, d):
|
||||||
return (d.replace(tzinfo=django_timezone.get_current_timezone())).astimezone(
|
return (d.replace(tzinfo=django_timezone.get_current_timezone())).astimezone(
|
||||||
timezone.utc,
|
UTC,
|
||||||
)
|
)
|
||||||
|
|
||||||
def date_from(self, *args, **kwargs):
|
def date_from(self, *args, **kwargs):
|
||||||
@@ -641,8 +641,8 @@ def rewrite_natural_date_keywords(query_string: str) -> str:
|
|||||||
end = datetime(local_now.year - 1, 12, 31, 23, 59, 59, tzinfo=tz)
|
end = datetime(local_now.year - 1, 12, 31, 23, 59, 59, tzinfo=tz)
|
||||||
|
|
||||||
# Convert to UTC and format
|
# Convert to UTC and format
|
||||||
start_str = start.astimezone(timezone.utc).strftime("%Y%m%d%H%M%S")
|
start_str = start.astimezone(UTC).strftime("%Y%m%d%H%M%S")
|
||||||
end_str = end.astimezone(timezone.utc).strftime("%Y%m%d%H%M%S")
|
end_str = end.astimezone(UTC).strftime("%Y%m%d%H%M%S")
|
||||||
return f"{field}:[{start_str} TO {end_str}]"
|
return f"{field}:[{start_str} TO {end_str}]"
|
||||||
|
|
||||||
return re.sub(pattern, repl, query_string, flags=re.IGNORECASE)
|
return re.sub(pattern, repl, query_string, flags=re.IGNORECASE)
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ from types import TracebackType
|
|||||||
try:
|
try:
|
||||||
from typing import Self
|
from typing import Self
|
||||||
except ImportError:
|
except ImportError:
|
||||||
from typing_extensions import Self
|
from typing import Self
|
||||||
|
|
||||||
import dateparser
|
import dateparser
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ if TYPE_CHECKING:
|
|||||||
from channels_redis.pubsub import RedisPubSubChannelLayer
|
from channels_redis.pubsub import RedisPubSubChannelLayer
|
||||||
|
|
||||||
|
|
||||||
class ProgressStatusOptions(str, enum.Enum):
|
class ProgressStatusOptions(enum.StrEnum):
|
||||||
STARTED = "STARTED"
|
STARTED = "STARTED"
|
||||||
WORKING = "WORKING"
|
WORKING = "WORKING"
|
||||||
SUCCESS = "SUCCESS"
|
SUCCESS = "SUCCESS"
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ def base_config() -> DateParserConfig:
|
|||||||
12,
|
12,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
tzinfo=datetime.timezone.utc,
|
tzinfo=datetime.UTC,
|
||||||
),
|
),
|
||||||
filename_date_order="YMD",
|
filename_date_order="YMD",
|
||||||
content_date_order="DMY",
|
content_date_order="DMY",
|
||||||
@@ -45,7 +45,7 @@ def config_with_ignore_dates() -> DateParserConfig:
|
|||||||
12,
|
12,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
tzinfo=datetime.timezone.utc,
|
tzinfo=datetime.UTC,
|
||||||
),
|
),
|
||||||
filename_date_order="DMY",
|
filename_date_order="DMY",
|
||||||
content_date_order="MDY",
|
content_date_order="MDY",
|
||||||
|
|||||||
@@ -101,50 +101,50 @@ class TestFilterDate:
|
|||||||
[
|
[
|
||||||
# Valid Dates
|
# Valid Dates
|
||||||
pytest.param(
|
pytest.param(
|
||||||
datetime.datetime(2024, 1, 10, tzinfo=datetime.timezone.utc),
|
datetime.datetime(2024, 1, 10, tzinfo=datetime.UTC),
|
||||||
datetime.datetime(2024, 1, 10, tzinfo=datetime.timezone.utc),
|
datetime.datetime(2024, 1, 10, tzinfo=datetime.UTC),
|
||||||
id="valid_past_date",
|
id="valid_past_date",
|
||||||
),
|
),
|
||||||
pytest.param(
|
pytest.param(
|
||||||
datetime.datetime(2024, 1, 15, 12, 0, 0, tzinfo=datetime.timezone.utc),
|
datetime.datetime(2024, 1, 15, 12, 0, 0, tzinfo=datetime.UTC),
|
||||||
datetime.datetime(2024, 1, 15, 12, 0, 0, tzinfo=datetime.timezone.utc),
|
datetime.datetime(2024, 1, 15, 12, 0, 0, tzinfo=datetime.UTC),
|
||||||
id="exactly_at_reference",
|
id="exactly_at_reference",
|
||||||
),
|
),
|
||||||
pytest.param(
|
pytest.param(
|
||||||
datetime.datetime(1901, 1, 1, tzinfo=datetime.timezone.utc),
|
datetime.datetime(1901, 1, 1, tzinfo=datetime.UTC),
|
||||||
datetime.datetime(1901, 1, 1, tzinfo=datetime.timezone.utc),
|
datetime.datetime(1901, 1, 1, tzinfo=datetime.UTC),
|
||||||
id="year_1901_valid",
|
id="year_1901_valid",
|
||||||
),
|
),
|
||||||
# Date is > reference_time
|
# Date is > reference_time
|
||||||
pytest.param(
|
pytest.param(
|
||||||
datetime.datetime(2024, 1, 16, tzinfo=datetime.timezone.utc),
|
datetime.datetime(2024, 1, 16, tzinfo=datetime.UTC),
|
||||||
None,
|
None,
|
||||||
id="future_date_day_after",
|
id="future_date_day_after",
|
||||||
),
|
),
|
||||||
# date.date() in ignore_dates
|
# date.date() in ignore_dates
|
||||||
pytest.param(
|
pytest.param(
|
||||||
datetime.datetime(2024, 1, 1, 0, 0, 0, tzinfo=datetime.timezone.utc),
|
datetime.datetime(2024, 1, 1, 0, 0, 0, tzinfo=datetime.UTC),
|
||||||
None,
|
None,
|
||||||
id="ignored_date_midnight_jan1",
|
id="ignored_date_midnight_jan1",
|
||||||
),
|
),
|
||||||
pytest.param(
|
pytest.param(
|
||||||
datetime.datetime(2024, 1, 1, 10, 30, 0, tzinfo=datetime.timezone.utc),
|
datetime.datetime(2024, 1, 1, 10, 30, 0, tzinfo=datetime.UTC),
|
||||||
None,
|
None,
|
||||||
id="ignored_date_midday_jan1",
|
id="ignored_date_midday_jan1",
|
||||||
),
|
),
|
||||||
pytest.param(
|
pytest.param(
|
||||||
datetime.datetime(2024, 12, 25, 15, 0, 0, tzinfo=datetime.timezone.utc),
|
datetime.datetime(2024, 12, 25, 15, 0, 0, tzinfo=datetime.UTC),
|
||||||
None,
|
None,
|
||||||
id="ignored_date_dec25_future",
|
id="ignored_date_dec25_future",
|
||||||
),
|
),
|
||||||
# date.year <= 1900
|
# date.year <= 1900
|
||||||
pytest.param(
|
pytest.param(
|
||||||
datetime.datetime(1899, 12, 31, tzinfo=datetime.timezone.utc),
|
datetime.datetime(1899, 12, 31, tzinfo=datetime.UTC),
|
||||||
None,
|
None,
|
||||||
id="year_1899",
|
id="year_1899",
|
||||||
),
|
),
|
||||||
pytest.param(
|
pytest.param(
|
||||||
datetime.datetime(1900, 1, 1, tzinfo=datetime.timezone.utc),
|
datetime.datetime(1900, 1, 1, tzinfo=datetime.UTC),
|
||||||
None,
|
None,
|
||||||
id="year_1900_boundary",
|
id="year_1900_boundary",
|
||||||
),
|
),
|
||||||
@@ -176,7 +176,7 @@ class TestFilterDate:
|
|||||||
1,
|
1,
|
||||||
12,
|
12,
|
||||||
0,
|
0,
|
||||||
tzinfo=datetime.timezone.utc,
|
tzinfo=datetime.UTC,
|
||||||
)
|
)
|
||||||
another_ignored = datetime.datetime(
|
another_ignored = datetime.datetime(
|
||||||
2024,
|
2024,
|
||||||
@@ -184,7 +184,7 @@ class TestFilterDate:
|
|||||||
25,
|
25,
|
||||||
15,
|
15,
|
||||||
30,
|
30,
|
||||||
tzinfo=datetime.timezone.utc,
|
tzinfo=datetime.UTC,
|
||||||
)
|
)
|
||||||
allowed_date = datetime.datetime(
|
allowed_date = datetime.datetime(
|
||||||
2024,
|
2024,
|
||||||
@@ -192,7 +192,7 @@ class TestFilterDate:
|
|||||||
2,
|
2,
|
||||||
12,
|
12,
|
||||||
0,
|
0,
|
||||||
tzinfo=datetime.timezone.utc,
|
tzinfo=datetime.UTC,
|
||||||
)
|
)
|
||||||
|
|
||||||
assert parser._filter_date(ignored_date) is None
|
assert parser._filter_date(ignored_date) is None
|
||||||
@@ -204,7 +204,7 @@ class TestFilterDate:
|
|||||||
regex_parser: RegexDateParserPlugin,
|
regex_parser: RegexDateParserPlugin,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Should work with timezone-aware datetimes."""
|
"""Should work with timezone-aware datetimes."""
|
||||||
date_utc = datetime.datetime(2024, 1, 10, 12, 0, tzinfo=datetime.timezone.utc)
|
date_utc = datetime.datetime(2024, 1, 10, 12, 0, tzinfo=datetime.UTC)
|
||||||
|
|
||||||
result = regex_parser._filter_date(date_utc)
|
result = regex_parser._filter_date(date_utc)
|
||||||
|
|
||||||
@@ -221,8 +221,8 @@ class TestRegexDateParser:
|
|||||||
"report-2023-12-25.txt",
|
"report-2023-12-25.txt",
|
||||||
"Event recorded on 25/12/2022.",
|
"Event recorded on 25/12/2022.",
|
||||||
[
|
[
|
||||||
datetime.datetime(2023, 12, 25, tzinfo=datetime.timezone.utc),
|
datetime.datetime(2023, 12, 25, tzinfo=datetime.UTC),
|
||||||
datetime.datetime(2022, 12, 25, tzinfo=datetime.timezone.utc),
|
datetime.datetime(2022, 12, 25, tzinfo=datetime.UTC),
|
||||||
],
|
],
|
||||||
id="filename-y-m-d_and_content-d-m-y",
|
id="filename-y-m-d_and_content-d-m-y",
|
||||||
),
|
),
|
||||||
@@ -230,8 +230,8 @@ class TestRegexDateParser:
|
|||||||
"img_2023.01.02.jpg",
|
"img_2023.01.02.jpg",
|
||||||
"Taken on 01/02/2023",
|
"Taken on 01/02/2023",
|
||||||
[
|
[
|
||||||
datetime.datetime(2023, 1, 2, tzinfo=datetime.timezone.utc),
|
datetime.datetime(2023, 1, 2, tzinfo=datetime.UTC),
|
||||||
datetime.datetime(2023, 2, 1, tzinfo=datetime.timezone.utc),
|
datetime.datetime(2023, 2, 1, tzinfo=datetime.UTC),
|
||||||
],
|
],
|
||||||
id="ambiguous-dates-respect-orders",
|
id="ambiguous-dates-respect-orders",
|
||||||
),
|
),
|
||||||
@@ -239,7 +239,7 @@ class TestRegexDateParser:
|
|||||||
"notes.txt",
|
"notes.txt",
|
||||||
"bad date 99/99/9999 and 25/12/2022",
|
"bad date 99/99/9999 and 25/12/2022",
|
||||||
[
|
[
|
||||||
datetime.datetime(2022, 12, 25, tzinfo=datetime.timezone.utc),
|
datetime.datetime(2022, 12, 25, tzinfo=datetime.UTC),
|
||||||
],
|
],
|
||||||
id="parse-exception-skips-bad-and-yields-good",
|
id="parse-exception-skips-bad-and-yields-good",
|
||||||
),
|
),
|
||||||
@@ -275,24 +275,24 @@ class TestRegexDateParser:
|
|||||||
or "2023.12.25" in date_string
|
or "2023.12.25" in date_string
|
||||||
or "2023-12-25" in date_string
|
or "2023-12-25" in date_string
|
||||||
):
|
):
|
||||||
return datetime.datetime(2023, 12, 25, tzinfo=datetime.timezone.utc)
|
return datetime.datetime(2023, 12, 25, tzinfo=datetime.UTC)
|
||||||
|
|
||||||
# content DMY 25/12/2022
|
# content DMY 25/12/2022
|
||||||
if "25/12/2022" in date_string or "25-12-2022" in date_string:
|
if "25/12/2022" in date_string or "25-12-2022" in date_string:
|
||||||
return datetime.datetime(2022, 12, 25, tzinfo=datetime.timezone.utc)
|
return datetime.datetime(2022, 12, 25, tzinfo=datetime.UTC)
|
||||||
|
|
||||||
# filename YMD 2023.01.02
|
# filename YMD 2023.01.02
|
||||||
if "2023.01.02" in date_string or "2023-01-02" in date_string:
|
if "2023.01.02" in date_string or "2023-01-02" in date_string:
|
||||||
return datetime.datetime(2023, 1, 2, tzinfo=datetime.timezone.utc)
|
return datetime.datetime(2023, 1, 2, tzinfo=datetime.UTC)
|
||||||
|
|
||||||
# ambiguous 01/02/2023 -> respect DATE_ORDER setting
|
# ambiguous 01/02/2023 -> respect DATE_ORDER setting
|
||||||
if "01/02/2023" in date_string:
|
if "01/02/2023" in date_string:
|
||||||
if date_order == "DMY":
|
if date_order == "DMY":
|
||||||
return datetime.datetime(2023, 2, 1, tzinfo=datetime.timezone.utc)
|
return datetime.datetime(2023, 2, 1, tzinfo=datetime.UTC)
|
||||||
if date_order == "YMD":
|
if date_order == "YMD":
|
||||||
return datetime.datetime(2023, 1, 2, tzinfo=datetime.timezone.utc)
|
return datetime.datetime(2023, 1, 2, tzinfo=datetime.UTC)
|
||||||
# fallback
|
# fallback
|
||||||
return datetime.datetime(2023, 2, 1, tzinfo=datetime.timezone.utc)
|
return datetime.datetime(2023, 2, 1, tzinfo=datetime.UTC)
|
||||||
|
|
||||||
# simulate parse failure for malformed input
|
# simulate parse failure for malformed input
|
||||||
if "99/99/9999" in date_string or "bad date" in date_string:
|
if "99/99/9999" in date_string or "bad date" in date_string:
|
||||||
@@ -328,7 +328,7 @@ class TestRegexDateParser:
|
|||||||
12,
|
12,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
tzinfo=datetime.timezone.utc,
|
tzinfo=datetime.UTC,
|
||||||
),
|
),
|
||||||
filename_date_order="YMD",
|
filename_date_order="YMD",
|
||||||
content_date_order="DMY",
|
content_date_order="DMY",
|
||||||
@@ -344,13 +344,13 @@ class TestRegexDateParser:
|
|||||||
) -> datetime.datetime | None:
|
) -> datetime.datetime | None:
|
||||||
if "10/12/2023" in date_string or "10-12-2023" in date_string:
|
if "10/12/2023" in date_string or "10-12-2023" in date_string:
|
||||||
# ignored date
|
# ignored date
|
||||||
return datetime.datetime(2023, 12, 10, tzinfo=datetime.timezone.utc)
|
return datetime.datetime(2023, 12, 10, tzinfo=datetime.UTC)
|
||||||
if "01/02/2024" in date_string or "01-02-2024" in date_string:
|
if "01/02/2024" in date_string or "01-02-2024" in date_string:
|
||||||
# future relative to reference_time -> filtered
|
# future relative to reference_time -> filtered
|
||||||
return datetime.datetime(2024, 2, 1, tzinfo=datetime.timezone.utc)
|
return datetime.datetime(2024, 2, 1, tzinfo=datetime.UTC)
|
||||||
if "05/01/2023" in date_string or "05-01-2023" in date_string:
|
if "05/01/2023" in date_string or "05-01-2023" in date_string:
|
||||||
# valid
|
# valid
|
||||||
return datetime.datetime(2023, 1, 5, tzinfo=datetime.timezone.utc)
|
return datetime.datetime(2023, 1, 5, tzinfo=datetime.UTC)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
mocker.patch(target, side_effect=fake_parse)
|
mocker.patch(target, side_effect=fake_parse)
|
||||||
@@ -358,7 +358,7 @@ class TestRegexDateParser:
|
|||||||
content = "Ignored: 10/12/2023, Future: 01/02/2024, Keep: 05/01/2023"
|
content = "Ignored: 10/12/2023, Future: 01/02/2024, Keep: 05/01/2023"
|
||||||
results = list(parser.parse("whatever.txt", content))
|
results = list(parser.parse("whatever.txt", content))
|
||||||
|
|
||||||
assert results == [datetime.datetime(2023, 1, 5, tzinfo=datetime.timezone.utc)]
|
assert results == [datetime.datetime(2023, 1, 5, tzinfo=datetime.UTC)]
|
||||||
|
|
||||||
def test_parse_handles_no_matches_and_returns_empty_list(
|
def test_parse_handles_no_matches_and_returns_empty_list(
|
||||||
self,
|
self,
|
||||||
@@ -392,7 +392,7 @@ class TestRegexDateParser:
|
|||||||
12,
|
12,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
tzinfo=datetime.timezone.utc,
|
tzinfo=datetime.UTC,
|
||||||
),
|
),
|
||||||
filename_date_order=None,
|
filename_date_order=None,
|
||||||
content_date_order="DMY",
|
content_date_order="DMY",
|
||||||
@@ -409,9 +409,9 @@ class TestRegexDateParser:
|
|||||||
) -> datetime.datetime | None:
|
) -> datetime.datetime | None:
|
||||||
# return distinct datetimes so we can tell which source was parsed
|
# return distinct datetimes so we can tell which source was parsed
|
||||||
if "25/12/2022" in date_string:
|
if "25/12/2022" in date_string:
|
||||||
return datetime.datetime(2022, 12, 25, tzinfo=datetime.timezone.utc)
|
return datetime.datetime(2022, 12, 25, tzinfo=datetime.UTC)
|
||||||
if "2023-12-25" in date_string:
|
if "2023-12-25" in date_string:
|
||||||
return datetime.datetime(2023, 12, 25, tzinfo=datetime.timezone.utc)
|
return datetime.datetime(2023, 12, 25, tzinfo=datetime.UTC)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
mock = mocker.patch(target, side_effect=fake_parse)
|
mock = mocker.patch(target, side_effect=fake_parse)
|
||||||
@@ -429,5 +429,5 @@ class TestRegexDateParser:
|
|||||||
assert "25/12/2022" in called_date_string
|
assert "25/12/2022" in called_date_string
|
||||||
# And the parser should have yielded the corresponding datetime
|
# And the parser should have yielded the corresponding datetime
|
||||||
assert results == [
|
assert results == [
|
||||||
datetime.datetime(2022, 12, 25, tzinfo=datetime.timezone.utc),
|
datetime.datetime(2022, 12, 25, tzinfo=datetime.UTC),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -336,7 +336,11 @@ class TestFileHandling(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
|
|||||||
added=d1,
|
added=d1,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(generate_filename(doc1), Path("232-01-09.pdf"))
|
# Account for 3.14 padding changes
|
||||||
|
expected_year: str = d1.strftime("%Y")
|
||||||
|
expected_filename: Path = Path(f"{expected_year}-01-09.pdf")
|
||||||
|
|
||||||
|
self.assertEqual(generate_filename(doc1), expected_filename)
|
||||||
|
|
||||||
doc1.added = timezone.make_aware(datetime.datetime(2020, 11, 16, 1, 1, 1))
|
doc1.added = timezone.make_aware(datetime.datetime(2020, 11, 16, 1, 1, 1))
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ class TestDateLocalization:
|
|||||||
14,
|
14,
|
||||||
30,
|
30,
|
||||||
5,
|
5,
|
||||||
tzinfo=datetime.timezone.utc,
|
tzinfo=datetime.UTC,
|
||||||
)
|
)
|
||||||
|
|
||||||
TEST_DATETIME_STRING: str = "2023-10-26T14:30:05+00:00"
|
TEST_DATETIME_STRING: str = "2023-10-26T14:30:05+00:00"
|
||||||
|
|||||||
@@ -4570,7 +4570,7 @@ class TestDateWorkflowLocalization(
|
|||||||
14,
|
14,
|
||||||
30,
|
30,
|
||||||
5,
|
5,
|
||||||
tzinfo=datetime.timezone.utc,
|
tzinfo=datetime.UTC,
|
||||||
)
|
)
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from enum import Enum
|
from enum import StrEnum
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
@@ -11,7 +11,7 @@ if TYPE_CHECKING:
|
|||||||
from django.http import HttpRequest
|
from django.http import HttpRequest
|
||||||
|
|
||||||
|
|
||||||
class VersionResolutionError(str, Enum):
|
class VersionResolutionError(StrEnum):
|
||||||
INVALID = "invalid"
|
INVALID = "invalid"
|
||||||
NOT_FOUND = "not_found"
|
NOT_FOUND = "not_found"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user