diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 829cf25559..339a545368 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -30,7 +30,6 @@ jobs:
"22.04",
]
python-version: [
- "3.8",
"3.9",
"3.10",
"3.11",
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index cc9073a7d6..95d91d896d 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -10,7 +10,7 @@ repos:
src/(MCPClient/MCPServer|dashboard)/install/.*\.json
)
- repo: https://github.com/astral-sh/ruff-pre-commit
- rev: v0.7.0
+ rev: v0.8.5
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
@@ -21,7 +21,7 @@ repos:
- id: django-upgrade
args: [--target-version, "4.2"]
- repo: https://github.com/igorshubovych/markdownlint-cli
- rev: v0.42.0
+ rev: v0.43.0
hooks:
- id: markdownlint
exclude: |
@@ -46,7 +46,7 @@ repos:
hooks:
- id: validate-cff
- repo: https://github.com/pre-commit/mirrors-mypy
- rev: v1.12.1
+ rev: v1.14.1
hooks:
- id: mypy
additional_dependencies:
@@ -54,7 +54,7 @@ repos:
- types-python-dateutil
- pytest
- repo: https://github.com/tcort/markdown-link-check
- rev: v3.12.2
+ rev: v3.13.6
hooks:
- id: markdown-link-check
stages: [manual]
diff --git a/hack/submodules/archivematica-acceptance-tests b/hack/submodules/archivematica-acceptance-tests
index 5e87b4680d..0ebb512ba0 160000
--- a/hack/submodules/archivematica-acceptance-tests
+++ b/hack/submodules/archivematica-acceptance-tests
@@ -1 +1 @@
-Subproject commit 5e87b4680d80d965106b4cf3e1f9e200de11f271
+Subproject commit 0ebb512ba07415c1f4a2211ca1110d7236197c3f
diff --git a/hack/submodules/archivematica-storage-service b/hack/submodules/archivematica-storage-service
index 9e915ce50e..00e671996e 160000
--- a/hack/submodules/archivematica-storage-service
+++ b/hack/submodules/archivematica-storage-service
@@ -1 +1 @@
-Subproject commit 9e915ce50ed66213642fe21744bf788bce34b2bf
+Subproject commit 00e671996e17e097bca526ae032e37123f3cd72d
diff --git a/pyproject.toml b/pyproject.toml
index 99e1fa0e6b..a36c8960b8 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -23,6 +23,9 @@ include = [
]
branch = true
+[tool.ruff]
+target-version = "py39"
+
[tool.ruff.lint]
# Rule reference: https://docs.astral.sh/ruff/rules/
select = [
diff --git a/requirements-dev.in b/requirements-dev.in
index dfcfe84656..c682a63775 100644
--- a/requirements-dev.in
+++ b/requirements-dev.in
@@ -1,5 +1,6 @@
-r requirements.txt
+coverage[toml]
git+https://github.com/artefactual-labs/mockldap@v0.3.1#egg=mockldap
pip-tools
pytest
@@ -13,7 +14,3 @@ tox
# gevent dependency in requirements.txt.
# See https://github.com/microsoft/playwright-python/issues/2190
git+https://github.com/microsoft/playwright-python.git@d9cdfbb1e178b6770625e9f857139aff77516af0#egg=playwright
-
-# These dependencies dropped support for Python 3.8, so pinning them for now.
-coverage[toml]==7.6.1
-pytest-randomly==3.15.0
diff --git a/requirements-dev.txt b/requirements-dev.txt
index 188b81c0f8..a9956df116 100644
--- a/requirements-dev.txt
+++ b/requirements-dev.txt
@@ -4,17 +4,17 @@
#
# pip-compile --allow-unsafe --output-file=requirements-dev.txt requirements-dev.in
#
-agentarchives==0.9.0
+agentarchives==0.10.0
# via -r requirements.txt
-amclient==1.3.0
+amclient==1.4.0
# via -r requirements.txt
-ammcpc==0.2.0
+ammcpc==0.3.0
# via -r requirements.txt
asgiref==3.8.1
# via
# -r requirements.txt
# django
-attrs==24.2.0
+attrs==24.3.0
# via
# -r requirements.txt
# jsonschema
@@ -27,7 +27,7 @@ build==1.2.2.post1
# via pip-tools
cachetools==5.5.0
# via tox
-certifi==2024.8.30
+certifi==2024.12.14
# via
# -r requirements.txt
# requests
@@ -37,21 +37,21 @@ cffi==1.17.1
# cryptography
chardet==5.2.0
# via tox
-charset-normalizer==3.4.0
+charset-normalizer==3.4.1
# via
# -r requirements.txt
# requests
clamd==1.0.2
# via -r requirements.txt
-click==8.1.7
+click==8.1.8
# via pip-tools
colorama==0.4.6
# via tox
-coverage[toml]==7.6.1
+coverage[toml]==7.6.10
# via
# -r requirements-dev.in
# pytest-cov
-cryptography==43.0.3
+cryptography==44.0.0
# via
# -r requirements.txt
# josepy
@@ -59,14 +59,14 @@ cryptography==43.0.3
# pyopenssl
distlib==0.3.9
# via virtualenv
-django==4.2.16
+django==4.2.17
# via
# -r requirements.txt
# django-auth-ldap
# django-cas-ng
# django-csp
# mozilla-django-oidc
-django-auth-ldap==5.0.0
+django-auth-ldap==5.1.0
# via -r requirements.txt
django-autoslug==1.9.9
# via -r requirements.txt
@@ -80,7 +80,7 @@ django-prometheus==2.3.1
# via -r requirements.txt
django-shibboleth-remoteuser @ git+https://github.com/artefactual-labs/django-shibboleth-remoteuser.git@f08a7864d6130416c352981ccf318fff0fd5be58
# via -r requirements.txt
-django-tastypie==0.14.7
+django-tastypie==0.15.0
# via -r requirements.txt
elasticsearch==6.8.2
# via -r requirements.txt
@@ -94,7 +94,7 @@ funcparserlib==2.0.0a0
# via mockldap
gearman3 @ git+https://github.com/artefactual-labs/python-gearman.git@b68efc868c7a494dd6a2d2e820fb098a6da9f797
# via -r requirements.txt
-gevent==24.2.1
+gevent==24.11.1
# via -r requirements.txt
greenlet==3.1.1
# via
@@ -112,7 +112,7 @@ importlib-metadata==8.5.0
# -r requirements.txt
# build
# pytest-randomly
-importlib-resources==6.4.5
+importlib-resources==6.5.2
# via
# -r requirements.txt
# opf-fido
@@ -126,7 +126,7 @@ josepy==1.14.0
# mozilla-django-oidc
jsonschema==4.23.0
# via -r requirements.txt
-jsonschema-specifications==2023.12.1
+jsonschema-specifications==2024.10.1
# via
# -r requirements.txt
# jsonschema
@@ -138,13 +138,13 @@ lxml==5.3.0
# ammcpc
# metsrw
# python-cas
-metsrw==0.5.1
+metsrw==0.6.0
# via -r requirements.txt
mockldap @ git+https://github.com/artefactual-labs/mockldap@v0.3.1
# via -r requirements-dev.in
mozilla-django-oidc==4.0.1
# via -r requirements.txt
-mysqlclient==2.2.5
+mysqlclient==2.2.6
# via
# -r requirements.txt
# agentarchives
@@ -154,9 +154,9 @@ olefile==0.47
# opf-fido
opf-fido @ git+https://github.com/artefactual-labs/fido.git@564ceb8018a8650fe931cf20e6780ee008e60fca
# via -r requirements.txt
-orjson==3.10.10
+orjson==3.10.13
# via -r requirements.txt
-packaging==24.1
+packaging==24.2
# via
# -r requirements.txt
# build
@@ -178,7 +178,7 @@ pluggy==1.5.0
# via
# pytest
# tox
-prometheus-client==0.21.0
+prometheus-client==0.21.1
# via
# -r requirements.txt
# django-prometheus
@@ -197,7 +197,7 @@ pycparser==2.22
# cffi
pyee==12.0.0
# via playwright
-pyopenssl==24.2.1
+pyopenssl==24.3.0
# via
# -r requirements.txt
# josepy
@@ -207,7 +207,7 @@ pyproject-hooks==1.2.0
# via
# build
# pip-tools
-pytest==8.3.3
+pytest==8.3.4
# via
# -r requirements-dev.in
# pytest-base-url
@@ -217,13 +217,13 @@ pytest==8.3.3
# pytest-randomly
pytest-base-url==2.1.0
# via pytest-playwright
-pytest-cov==5.0.0
+pytest-cov==6.0.0
# via -r requirements-dev.in
pytest-django==4.9.0
# via -r requirements-dev.in
-pytest-playwright==0.5.2
+pytest-playwright==0.6.2
# via -r requirements-dev.in
-pytest-randomly==3.15.0
+pytest-randomly==3.16.0
# via -r requirements-dev.in
python-cas==1.6.0
# via
@@ -258,24 +258,24 @@ requests==2.32.3
# opf-fido
# pytest-base-url
# python-cas
-rpds-py==0.20.0
+rpds-py==0.22.3
# via
# -r requirements.txt
# jsonschema
# referencing
-six==1.16.0
+six==1.17.0
# via
# -r requirements.txt
# opf-fido
# python-cas
# python-dateutil
-sqlparse==0.5.1
+sqlparse==0.5.3
# via
# -r requirements.txt
# django
text-unidecode==1.3
# via python-slugify
-tomli==2.0.2
+tomli==2.2.1
# via
# build
# coverage
@@ -293,19 +293,19 @@ typing-extensions==4.12.2
# tox
unidecode==1.3.8
# via -r requirements.txt
-urllib3==2.2.3
+urllib3==2.3.0
# via
# -r requirements.txt
# amclient
# elasticsearch
# requests
-virtualenv==20.27.1
+virtualenv==20.28.1
# via tox
-wheel==0.44.0
+wheel==0.45.1
# via pip-tools
-whitenoise==6.7.0
+whitenoise==6.8.2
# via -r requirements.txt
-zipp==3.20.2
+zipp==3.21.0
# via
# -r requirements.txt
# importlib-metadata
@@ -314,7 +314,7 @@ zope-event==5.0
# via
# -r requirements.txt
# gevent
-zope-interface==7.1.1
+zope-interface==7.2
# via
# -r requirements.txt
# gevent
@@ -322,7 +322,7 @@ zope-interface==7.1.1
# The following packages are considered to be unsafe in a requirements file:
pip==24.3.1
# via pip-tools
-setuptools==75.2.0
+setuptools==75.6.0
# via
# -r requirements.txt
# pip-tools
diff --git a/requirements.in b/requirements.in
index 204bc96c0a..4393cd65fe 100644
--- a/requirements.in
+++ b/requirements.in
@@ -13,6 +13,7 @@ django-prometheus
django-tastypie
elasticsearch>=6.0.0,<7.0.0
git+https://github.com/artefactual-labs/python-gearman.git@b68efc868c7a494dd6a2d2e820fb098a6da9f797#egg=gearman3
+gevent
gunicorn
importlib-metadata
inotify_simple
@@ -26,6 +27,7 @@ prometheus_client
python-dateutil
requests
unidecode
+whitenoise
# Required by LDAP authentication
django-auth-ldap
@@ -36,9 +38,3 @@ django-cas-ng
# Required for OpenID Connect authentication
mozilla-django-oidc
-
-# These dependencies dropped support for Python 3.8, so pinning them for now.
-django-auth-ldap==5.0.0
-gevent==24.2.1
-jsonschema-specifications==2023.12.1
-whitenoise==6.7.0
diff --git a/requirements.txt b/requirements.txt
index d7eb7776ec..c83559e68a 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -4,15 +4,15 @@
#
# pip-compile --allow-unsafe --output-file=requirements.txt requirements.in
#
-agentarchives==0.9.0
+agentarchives==0.10.0
# via -r requirements.in
-amclient==1.3.0
+amclient==1.4.0
# via -r requirements.in
-ammcpc==0.2.0
+ammcpc==0.3.0
# via -r requirements.in
asgiref==3.8.1
# via django
-attrs==24.2.0
+attrs==24.3.0
# via
# jsonschema
# referencing
@@ -20,27 +20,27 @@ bagit @ git+https://github.com/artefactual-labs/bagit-python.git@902051d8410219f
# via -r requirements.in
brotli==1.1.0
# via -r requirements.in
-certifi==2024.8.30
+certifi==2024.12.14
# via requests
cffi==1.17.1
# via cryptography
-charset-normalizer==3.4.0
+charset-normalizer==3.4.1
# via requests
clamd==1.0.2
# via -r requirements.in
-cryptography==43.0.3
+cryptography==44.0.0
# via
# josepy
# mozilla-django-oidc
# pyopenssl
-django==4.2.16
+django==4.2.17
# via
# -r requirements.in
# django-auth-ldap
# django-cas-ng
# django-csp
# mozilla-django-oidc
-django-auth-ldap==5.0.0
+django-auth-ldap==5.1.0
# via -r requirements.in
django-autoslug==1.9.9
# via -r requirements.in
@@ -54,13 +54,13 @@ django-prometheus==2.3.1
# via -r requirements.in
django-shibboleth-remoteuser @ git+https://github.com/artefactual-labs/django-shibboleth-remoteuser.git@f08a7864d6130416c352981ccf318fff0fd5be58
# via -r requirements.in
-django-tastypie==0.14.7
+django-tastypie==0.15.0
# via -r requirements.in
elasticsearch==6.8.2
# via -r requirements.in
gearman3 @ git+https://github.com/artefactual-labs/python-gearman.git@b68efc868c7a494dd6a2d2e820fb098a6da9f797
# via -r requirements.in
-gevent==24.2.1
+gevent==24.11.1
# via -r requirements.in
greenlet==3.1.1
# via gevent
@@ -70,7 +70,7 @@ idna==3.10
# via requests
importlib-metadata==8.5.0
# via -r requirements.in
-importlib-resources==6.4.5
+importlib-resources==6.5.2
# via opf-fido
inotify-simple==1.3.5
# via -r requirements.in
@@ -78,10 +78,8 @@ josepy==1.14.0
# via mozilla-django-oidc
jsonschema==4.23.0
# via -r requirements.in
-jsonschema-specifications==2023.12.1
- # via
- # -r requirements.in
- # jsonschema
+jsonschema-specifications==2024.10.1
+ # via jsonschema
lazy-paged-sequence==0.3
# via -r requirements.in
lxml==5.3.0
@@ -90,21 +88,21 @@ lxml==5.3.0
# ammcpc
# metsrw
# python-cas
-metsrw==0.5.1
+metsrw==0.6.0
# via -r requirements.in
mozilla-django-oidc==4.0.1
# via -r requirements.in
-mysqlclient==2.2.5
+mysqlclient==2.2.6
# via agentarchives
olefile==0.47
# via opf-fido
opf-fido @ git+https://github.com/artefactual-labs/fido.git@564ceb8018a8650fe931cf20e6780ee008e60fca
# via -r requirements.in
-orjson==3.10.10
+orjson==3.10.13
# via -r requirements.in
-packaging==24.1
+packaging==24.2
# via gunicorn
-prometheus-client==0.21.0
+prometheus-client==0.21.1
# via
# -r requirements.in
# django-prometheus
@@ -116,7 +114,7 @@ pyasn1-modules==0.4.1
# via python-ldap
pycparser==2.22
# via cffi
-pyopenssl==24.2.1
+pyopenssl==24.3.0
# via josepy
python-cas==1.6.0
# via django-cas-ng
@@ -142,39 +140,39 @@ requests==2.32.3
# mozilla-django-oidc
# opf-fido
# python-cas
-rpds-py==0.20.0
+rpds-py==0.22.3
# via
# jsonschema
# referencing
-six==1.16.0
+six==1.17.0
# via
# opf-fido
# python-cas
# python-dateutil
-sqlparse==0.5.1
+sqlparse==0.5.3
# via django
typing-extensions==4.12.2
# via asgiref
unidecode==1.3.8
# via -r requirements.in
-urllib3==2.2.3
+urllib3==2.3.0
# via
# amclient
# elasticsearch
# requests
-whitenoise==6.7.0
+whitenoise==6.8.2
# via -r requirements.in
-zipp==3.20.2
+zipp==3.21.0
# via
# importlib-metadata
# importlib-resources
zope-event==5.0
# via gevent
-zope-interface==7.1.1
+zope-interface==7.2
# via gevent
# The following packages are considered to be unsafe in a requirements file:
-setuptools==75.2.0
+setuptools==75.6.0
# via
# zope-event
# zope-interface
diff --git a/src/MCPClient/lib/client/gearman.py b/src/MCPClient/lib/client/gearman.py
index 6653e296e5..eef6b97d47 100644
--- a/src/MCPClient/lib/client/gearman.py
+++ b/src/MCPClient/lib/client/gearman.py
@@ -4,11 +4,7 @@
from datetime import datetime
from multiprocessing.synchronize import Event
from types import TracebackType
-from typing import Dict
-from typing import List
from typing import Optional
-from typing import Tuple
-from typing import Type
from typing import Union
import gearman
@@ -34,8 +30,8 @@
# },
# ...
# }
-TaskData = Dict[str, Union[str, bool]]
-GearmanJobTasks = Dict[str, TaskData]
+TaskData = dict[str, Union[str, bool]]
+GearmanJobTasks = dict[str, TaskData]
# This is how `results` looks in the `_format_job_results` method below:
# {
@@ -49,8 +45,8 @@
# },
# ...
# }
-JobData = Dict[str, Union[int, Optional[datetime], str]]
-JobResults = Dict[str, JobData]
+JobData = dict[str, Union[int, Optional[datetime], str]]
+JobResults = dict[str, JobData]
logger = logging.getLogger("archivematica.mcp.client.gearman")
@@ -60,8 +56,8 @@ class MCPGearmanWorker(gearman.GearmanWorker): # type: ignore
def __init__(
self,
- hosts: List[str],
- client_scripts: List[str],
+ hosts: list[str],
+ client_scripts: list[str],
shutdown_event: Optional[Event] = None,
max_jobs_to_process: Optional[int] = None,
) -> None:
@@ -82,7 +78,7 @@ def __init__(
logger.debug("Worker %s registered tasks: %s", self.client_id, client_scripts)
@staticmethod
- def _format_job_results(jobs: List[Job]) -> JobResults:
+ def _format_job_results(jobs: list[Job]) -> JobResults:
results = {}
for job in jobs:
@@ -103,7 +99,7 @@ def _format_job_results(jobs: List[Job]) -> JobResults:
return results
@staticmethod
- def _prepare_jobs(task_name: str, gearman_job: GearmanJob) -> List[Job]:
+ def _prepare_jobs(task_name: str, gearman_job: GearmanJob) -> list[Job]:
"""Given a tasks dictionary, return a list of Job objects."""
tasks: GearmanJobTasks = gearman_job.data["tasks"]
@@ -132,7 +128,7 @@ def handle_job(
task_name: str,
gearman_worker: gearman.GearmanWorker,
gearman_job: GearmanJob,
- ) -> Dict[str, JobResults]:
+ ) -> dict[str, JobResults]:
job_module = self.job_modules[task_name]
logger.debug(
"Gearman job request %s received for %s",
@@ -152,7 +148,7 @@ def handle_job(
def on_job_exception(
self,
current_job: GearmanJob,
- exc_info: Tuple[Type[BaseException], BaseException, TracebackType],
+ exc_info: tuple[type[BaseException], BaseException, TracebackType],
) -> bool:
logger.error(
"An unhandled exception occurred processing a Gearman job",
diff --git a/src/MCPClient/lib/client/job.py b/src/MCPClient/lib/client/job.py
index 018f298e01..e423589178 100644
--- a/src/MCPClient/lib/client/job.py
+++ b/src/MCPClient/lib/client/job.py
@@ -9,14 +9,12 @@
import logging
import sys
import traceback
+from collections.abc import Generator
+from collections.abc import Iterable
+from collections.abc import Mapping
from contextlib import contextmanager
from logging.handlers import BufferingHandler
from typing import Any
-from typing import Dict
-from typing import Generator
-from typing import Iterable
-from typing import List
-from typing import Mapping
from typing import Optional
from typing import TypeVar
from typing import Union
@@ -28,7 +26,7 @@
logger = logging.getLogger("archivematica.mcp.client.job")
SelfJob = TypeVar("SelfJob", bound="Job")
-TaskData = Dict[str, Union[int, Optional[datetime.datetime], str]]
+TaskData = dict[str, Union[int, Optional[datetime.datetime], str]]
class Job:
@@ -36,7 +34,7 @@ def __init__(
self,
name: str,
uuid: str,
- arguments: List[str],
+ arguments: list[str],
capture_output: bool = False,
) -> None:
"""
@@ -60,7 +58,7 @@ def __init__(
self.end_time: Optional[datetime.datetime] = None
@classmethod
- def bulk_set_start_times(cls, jobs: List[SelfJob]) -> None:
+ def bulk_set_start_times(cls, jobs: list[SelfJob]) -> None:
"""Bulk set the processing start time for a batch of jobs."""
start_time = timezone.now()
uuids = [job.uuid for job in jobs]
@@ -69,7 +67,7 @@ def bulk_set_start_times(cls, jobs: List[SelfJob]) -> None:
job.start_time = start_time
@classmethod
- def bulk_mark_failed(cls, jobs: List[SelfJob], message: str) -> None:
+ def bulk_mark_failed(cls, jobs: list[SelfJob], message: str) -> None:
uuids = [job.uuid for job in jobs]
Task.objects.filter(taskuuid__in=uuids).update(
diff --git a/src/MCPClient/lib/client/loader.py b/src/MCPClient/lib/client/loader.py
index f61c84724b..e1198bc372 100644
--- a/src/MCPClient/lib/client/loader.py
+++ b/src/MCPClient/lib/client/loader.py
@@ -2,11 +2,10 @@
import warnings
from configparser import RawConfigParser
from types import ModuleType
-from typing import Dict
from typing import Optional
-def get_supported_modules(modules_file_path: str) -> Dict[str, str]:
+def get_supported_modules(modules_file_path: str) -> dict[str, str]:
"""Create and return the ``supported_modules`` dict by parsing the MCPClient
modules config file (typically MCPClient/lib/archivematicaClientModules).
"""
@@ -41,7 +40,7 @@ def get_module_concurrency(module: ModuleType) -> int:
return 1
-def load_job_modules(modules_file_path: str) -> Dict[str, Optional[ModuleType]]:
+def load_job_modules(modules_file_path: str) -> dict[str, Optional[ModuleType]]:
"""Return a dict of {client script name: module}."""
supported_modules = get_supported_modules(modules_file_path)
diff --git a/src/MCPClient/lib/client/metrics.py b/src/MCPClient/lib/client/metrics.py
index 20df295a8b..03e268f084 100644
--- a/src/MCPClient/lib/client/metrics.py
+++ b/src/MCPClient/lib/client/metrics.py
@@ -8,7 +8,6 @@
import threading
from typing import Callable
from typing import Optional
-from typing import Tuple
from typing import TypeVar
from wsgiref.simple_server import WSGIServer
@@ -255,13 +254,13 @@ def worker_exit(process_id: Optional[int]) -> None:
@skip_if_prometheus_disabled
-def start_prometheus_server() -> Tuple[WSGIServer, threading.Thread]:
+def start_prometheus_server() -> tuple[WSGIServer, threading.Thread]:
registry = CollectorRegistry()
multiprocess.MultiProcessCollector(registry)
init_counter_labels()
- result: Tuple[WSGIServer, threading.Thread] = start_http_server(
+ result: tuple[WSGIServer, threading.Thread] = start_http_server(
settings.PROMETHEUS_BIND_PORT,
addr=settings.PROMETHEUS_BIND_ADDRESS,
registry=registry,
diff --git a/src/MCPClient/lib/client/pool.py b/src/MCPClient/lib/client/pool.py
index 936cca13c2..640765f4d8 100644
--- a/src/MCPClient/lib/client/pool.py
+++ b/src/MCPClient/lib/client/pool.py
@@ -20,11 +20,8 @@
import time
from multiprocessing.synchronize import Event
from types import ModuleType
-from typing import Dict
-from typing import List
from typing import Optional
from typing import Protocol
-from typing import Tuple
from typing import TypeVar
import django
@@ -68,14 +65,14 @@ def put_nowait(self, item: T) -> None: ...
# ),
# ...
# ]
-WorkerInitArgs = List[Tuple[Tuple[LogQueue, List[str]], Dict[str, Event]]]
+WorkerInitArgs = list[tuple[tuple[LogQueue, list[str]], dict[str, Event]]]
logger = logging.getLogger("archivematica.mcp.client.worker")
def run_gearman_worker(
log_queue: LogQueue,
- client_scripts: List[str],
+ client_scripts: list[str],
shutdown_event: Optional[Event] = None,
) -> None:
"""Target function executed by child processes in the pool."""
@@ -110,7 +107,7 @@ class WorkerPool:
def __init__(self) -> None:
self.log_queue: LogQueue = multiprocessing.Queue()
self.shutdown_event = multiprocessing.Event()
- self.workers: List[multiprocessing.Process] = []
+ self.workers: list[multiprocessing.Process] = []
self.job_modules = loader.load_job_modules(settings.CLIENT_MODULES_FILE)
self.worker_function = run_gearman_worker
@@ -161,8 +158,8 @@ def stop(self) -> None:
self.logging_listener.stop()
def _get_script_workers_required(
- self, job_modules: Dict[str, Optional[ModuleType]]
- ) -> Dict[str, int]:
+ self, job_modules: dict[str, Optional[ModuleType]]
+ ) -> dict[str, int]:
workers_required = {}
for client_script, module in job_modules.items():
concurrency = loader.get_module_concurrency(module)
@@ -172,11 +169,11 @@ def _get_script_workers_required(
# Use Queue[logging.LogRecord] instead of Any
def _get_worker_init_args(
- self, script_workers_required: Dict[str, int]
+ self, script_workers_required: dict[str, int]
) -> WorkerInitArgs:
# Don't mutate the argument
script_workers_required = script_workers_required.copy()
- init_scripts: List[List[str]] = []
+ init_scripts: list[list[str]] = []
for i in range(self.pool_size):
init_scripts.append([])
diff --git a/src/MCPClient/lib/client/utils.py b/src/MCPClient/lib/client/utils.py
index fb2c9213fd..fe90de6946 100644
--- a/src/MCPClient/lib/client/utils.py
+++ b/src/MCPClient/lib/client/utils.py
@@ -1,11 +1,10 @@
import shlex
-from typing import List
from django.conf import settings
from django.utils import timezone
-def parse_command_line(s: str) -> List[str]:
+def parse_command_line(s: str) -> list[str]:
return [_shlex_unescape(x) for x in shlex.split(s)]
diff --git a/src/MCPClient/lib/client/worker.py b/src/MCPClient/lib/client/worker.py
index ceb9a234c2..0292d47c03 100644
--- a/src/MCPClient/lib/client/worker.py
+++ b/src/MCPClient/lib/client/worker.py
@@ -1,6 +1,5 @@
import logging
from types import ModuleType
-from typing import List
from dbconns import auto_close_old_connections
@@ -11,7 +10,7 @@
@auto_close_old_connections() # type: ignore
-def run_task(task_name: str, job_module: ModuleType, jobs: List[Job]) -> None:
+def run_task(task_name: str, job_module: ModuleType, jobs: list[Job]) -> None:
"""Do actual processing of the jobs given."""
logger.info("\n\n*** RUNNING TASK: %s***", task_name)
Job.bulk_set_start_times(jobs)
diff --git a/src/MCPClient/lib/clientScripts/characterize_file.py b/src/MCPClient/lib/clientScripts/characterize_file.py
index dd875d5a2a..5eb645e5ce 100755
--- a/src/MCPClient/lib/clientScripts/characterize_file.py
+++ b/src/MCPClient/lib/clientScripts/characterize_file.py
@@ -10,7 +10,6 @@
import dataclasses
import multiprocessing
import uuid
-from typing import List
import django
@@ -139,7 +138,7 @@ def parse_args(parser: argparse.ArgumentParser, job: Job) -> CharacterizeFileArg
return CharacterizeFileArgs(**vars(namespace))
-def call(jobs: List[Job]) -> None:
+def call(jobs: list[Job]) -> None:
parser = get_parser()
with transaction.atomic():
diff --git a/src/MCPClient/lib/clientScripts/determine_aip_version_key_exit_code.py b/src/MCPClient/lib/clientScripts/determine_aip_version_key_exit_code.py
index 9e76220a20..eda27d3e5b 100755
--- a/src/MCPClient/lib/clientScripts/determine_aip_version_key_exit_code.py
+++ b/src/MCPClient/lib/clientScripts/determine_aip_version_key_exit_code.py
@@ -17,13 +17,12 @@
# along with Archivematica. If not, see .
import os
import sys
-from typing import Dict
from typing import Optional
import namespaces as ns
from lxml import etree
-VERSION_MAP: Dict[Optional[str], int] = {
+VERSION_MAP: dict[Optional[str], int] = {
# Only change exit code if AIP format changes. If unknown, default to latest
# version. Currently, all AIPs are the same format so no special cases
# required.
diff --git a/src/MCPClient/lib/clientScripts/has_packages.py b/src/MCPClient/lib/clientScripts/has_packages.py
index 2a073df960..7a758abfb5 100755
--- a/src/MCPClient/lib/clientScripts/has_packages.py
+++ b/src/MCPClient/lib/clientScripts/has_packages.py
@@ -1,5 +1,4 @@
#!/usr/bin/env python
-from typing import List
import django
@@ -63,7 +62,7 @@ def main(job: Job, sip_uuid: str) -> int:
return 1
-def call(jobs: List[Job]) -> None:
+def call(jobs: list[Job]) -> None:
for job in jobs:
with job.JobContext():
job.set_status(main(job, job.args[1]))
diff --git a/src/MCPClient/lib/clientScripts/identify_file_format.py b/src/MCPClient/lib/clientScripts/identify_file_format.py
index 24f16e1ac3..ffbb7d2cb1 100755
--- a/src/MCPClient/lib/clientScripts/identify_file_format.py
+++ b/src/MCPClient/lib/clientScripts/identify_file_format.py
@@ -3,7 +3,6 @@
import dataclasses
import multiprocessing
import uuid
-from typing import List
from typing import Optional
import django
@@ -242,7 +241,7 @@ def parse_args(parser: argparse.ArgumentParser, job: Job) -> IdentifyFileFormatA
return IdentifyFileFormatArgs(**vars(namespace))
-def call(jobs: List[Job]) -> None:
+def call(jobs: list[Job]) -> None:
parser = get_parser()
with transaction.atomic():
diff --git a/src/MCPClient/lib/clientScripts/normalize.py b/src/MCPClient/lib/clientScripts/normalize.py
index 6e240b6b00..aa184041e5 100755
--- a/src/MCPClient/lib/clientScripts/normalize.py
+++ b/src/MCPClient/lib/clientScripts/normalize.py
@@ -9,7 +9,6 @@
import traceback
import uuid
from typing import Callable
-from typing import List
from typing import Optional
import django
@@ -608,7 +607,7 @@ def parse_args(parser: argparse.ArgumentParser, job: Job) -> NormalizeArgs:
return NormalizeArgs(**vars(namespace))
-def call(jobs: List[Job]) -> None:
+def call(jobs: list[Job]) -> None:
parser = get_parser()
with transaction.atomic():
diff --git a/src/MCPClient/lib/clientScripts/policy_check.py b/src/MCPClient/lib/clientScripts/policy_check.py
index b3a6374276..83c8b43295 100755
--- a/src/MCPClient/lib/clientScripts/policy_check.py
+++ b/src/MCPClient/lib/clientScripts/policy_check.py
@@ -18,10 +18,7 @@
from custom_handlers import get_script_logger
django.setup()
-from typing import Dict
-from typing import List
from typing import Optional
-from typing import Tuple
import databaseFunctions
from client.job import Job
@@ -315,7 +312,7 @@ def _execute_rule_command(self, rule: FPRule) -> str:
)
return result
- def _get_command_to_execute(self, rule: FPRule) -> Tuple[str, List[str]]:
+ def _get_command_to_execute(self, rule: FPRule) -> tuple[str, list[str]]:
"""Return a 2-tuple consisting of a) the FPR rule ``rule``'s command
and b) a list of arguments to pass to it.
"""
@@ -332,14 +329,14 @@ def _get_command_to_execute(self, rule: FPRule) -> Tuple[str, List[str]]:
else:
return (rule.command.command, [self.file_path, self.policies_dir])
- def _save_to_logs_dir(self, output: Dict[str, str]) -> None:
+ def _save_to_logs_dir(self, output: dict[str, str]) -> None:
"""Save the MediaConch policy file as well as the raw MediaConch stdout
for the target file to the logs/ directory of the SIP.
"""
self._save_stdout_to_logs_dir(output)
self._save_policy_to_subm_doc_dir(output)
- def _save_stdout_to_logs_dir(self, output: Dict[str, str]) -> None:
+ def _save_stdout_to_logs_dir(self, output: dict[str, str]) -> None:
"""Save the output of running MediaConch's policy checker against the
input file to
logs/policyChecks//.xml in the SIP.
@@ -356,7 +353,7 @@ def _save_stdout_to_logs_dir(self, output: Dict[str, str]) -> None:
with open(stdout_path, "w") as f:
f.write(mc_stdout)
- def _save_policy_to_subm_doc_dir(self, output: Dict[str, str]) -> None:
+ def _save_policy_to_subm_doc_dir(self, output: dict[str, str]) -> None:
"""Save the policy file text in ``output['policy']`` to a file named
``output['policyFileName']`` in
metadata/submissionDocumentation/policies/ in the SIP, if it is not
@@ -464,7 +461,7 @@ def sip_policy_checks_dir(self) -> Optional[str]:
return self._sip_policy_checks_dir
-def call(jobs: List[Job]) -> None:
+def call(jobs: list[Job]) -> None:
with transaction.atomic():
for job in jobs:
with job.JobContext(logger=logger):
diff --git a/src/MCPClient/lib/clientScripts/transcribe_file.py b/src/MCPClient/lib/clientScripts/transcribe_file.py
index 906db4061c..f92def343d 100755
--- a/src/MCPClient/lib/clientScripts/transcribe_file.py
+++ b/src/MCPClient/lib/clientScripts/transcribe_file.py
@@ -4,10 +4,8 @@
import multiprocessing
import os
import uuid
-from typing import List
+from collections.abc import Sequence
from typing import Optional
-from typing import Sequence
-from typing import Tuple
import django
@@ -105,7 +103,7 @@ def fetch_rules_for(file_: File) -> Sequence[FPRule]:
return []
-def fetch_rules_for_derivatives(file_: File) -> Tuple[Optional[File], Sequence[FPRule]]:
+def fetch_rules_for_derivatives(file_: File) -> tuple[Optional[File], Sequence[FPRule]]:
derivs = Derivation.objects.filter(source_file=file_)
for deriv in derivs:
derived_file = deriv.derived_file
@@ -203,7 +201,7 @@ def parse_args(parser: argparse.ArgumentParser, job: Job) -> TranscribeFileArgs:
return TranscribeFileArgs(**vars(namespace))
-def call(jobs: List[Job]) -> None:
+def call(jobs: list[Job]) -> None:
parser = get_parser()
with transaction.atomic():
diff --git a/src/MCPClient/lib/clientScripts/validate_file.py b/src/MCPClient/lib/clientScripts/validate_file.py
index 0318949a60..7abbf03f41 100755
--- a/src/MCPClient/lib/clientScripts/validate_file.py
+++ b/src/MCPClient/lib/clientScripts/validate_file.py
@@ -18,10 +18,9 @@
import ast
import os
import sys
+from collections.abc import Mapping
from pprint import pformat
from typing import Any
-from typing import List
-from typing import Mapping
from typing import Optional
import django
@@ -324,21 +323,21 @@ def sip_pres_val_dir(self) -> Optional[str]:
return self._sip_pres_val_dir
-def _get_shared_path(argv: List[str]) -> Optional[str]:
+def _get_shared_path(argv: list[str]) -> Optional[str]:
try:
return argv[4]
except IndexError:
return None
-def _get_file_type(argv: List[str]) -> str:
+def _get_file_type(argv: list[str]) -> str:
try:
return argv[5]
except IndexError:
return "original"
-def call(jobs: List[Job]) -> None:
+def call(jobs: list[Job]) -> None:
with transaction.atomic():
for job in jobs:
with job.JobContext(logger=logger):
diff --git a/src/archivematicaCommon/lib/archivematicaFunctions.py b/src/archivematicaCommon/lib/archivematicaFunctions.py
index 6dfa48c867..6a6ece804d 100644
--- a/src/archivematicaCommon/lib/archivematicaFunctions.py
+++ b/src/archivematicaCommon/lib/archivematicaFunctions.py
@@ -27,10 +27,9 @@
import os
import pprint
import re
+from collections.abc import Iterable
from itertools import zip_longest
from pathlib import Path
-from typing import Dict
-from typing import Iterable
from uuid import uuid4
from amclient import AMClient
@@ -530,7 +529,7 @@ def chunk_iterable(iterable, chunk_size=10, fillvalue=None):
def get_oidc_secondary_providers(
oidc_secondary_provider_names: Iterable[str],
-) -> Dict[str, Dict[str, str]]:
+) -> dict[str, dict[str, str]]:
"""Build secondary OIDC provider details dict. Takes a list of secondary
OIDC providers and gathers details about these providers from env vars.
Output dict contains details for each OIDC connection which can then be
diff --git a/src/archivematicaCommon/lib/dbconns.py b/src/archivematicaCommon/lib/dbconns.py
index bd5b9a5060..dd12cab88c 100644
--- a/src/archivematicaCommon/lib/dbconns.py
+++ b/src/archivematicaCommon/lib/dbconns.py
@@ -21,7 +21,6 @@
import threading
import traceback
from contextlib import ContextDecorator
-from typing import Type
from django.conf import settings
from django.db import close_old_connections
@@ -85,7 +84,7 @@ def emit(self, record):
)
-auto_close_old_connections: Type[AutoCloseOldConnections]
+auto_close_old_connections: type[AutoCloseOldConnections]
if settings.DEBUG:
logger.debug("Using DEBUG auto_close_old_connections")
diff --git a/src/archivematicaCommon/lib/executeOrRunSubProcess.py b/src/archivematicaCommon/lib/executeOrRunSubProcess.py
index 0b8ada6cf5..8eb804c02a 100644
--- a/src/archivematicaCommon/lib/executeOrRunSubProcess.py
+++ b/src/archivematicaCommon/lib/executeOrRunSubProcess.py
@@ -21,19 +21,16 @@
import subprocess
import sys
import tempfile
-from typing import Dict
-from typing import List
from typing import Optional
-from typing import Tuple
from typing import Union
from archivematicaFunctions import escape
-Arguments = List[str]
+Arguments = list[str]
Input = Union[str, bytes, io.IOBase]
-Environment = Dict[str, str]
-Command = Union[str, List[str]]
-Result = Tuple[int, str, str]
+Environment = dict[str, str]
+Command = Union[str, list[str]]
+Result = tuple[int, str, str]
def launchSubProcess(
diff --git a/src/dashboard/src/components/administration/migrations/0001_initial.py b/src/dashboard/src/components/administration/migrations/0001_initial.py
index 8a28bd1bd9..14abb7364d 100644
--- a/src/dashboard/src/components/administration/migrations/0001_initial.py
+++ b/src/dashboard/src/components/administration/migrations/0001_initial.py
@@ -1,6 +1,4 @@
import uuid
-from typing import List
-from typing import Tuple
import main.models
from django.db import migrations
@@ -8,7 +6,7 @@
class Migration(migrations.Migration):
- dependencies: List[Tuple[str, str]] = []
+ dependencies: list[tuple[str, str]] = []
operations = [
migrations.CreateModel(
diff --git a/src/dashboard/src/fpr/management/commands/import_pronom_ids.py b/src/dashboard/src/fpr/management/commands/import_pronom_ids.py
index 0f4d79147b..f05f03d1ad 100644
--- a/src/dashboard/src/fpr/management/commands/import_pronom_ids.py
+++ b/src/dashboard/src/fpr/management/commands/import_pronom_ids.py
@@ -1,7 +1,6 @@
import os
import sys
import uuid
-from typing import Dict
from django.core.management.base import BaseCommand
from django.db import connection
@@ -15,7 +14,7 @@
# Introduced in fpr/migrations/0035_python3_compatibility.py
FILE_BY_EXTENSION_CMD_UUID = "8546b624-7894-4201-8df6-f239d5e0d5ba"
-archivematica_formats: Dict[str, Format] = {}
+archivematica_formats: dict[str, Format] = {}
unknown_format_group = FormatGroup.objects.get(description="Unknown")
file_by_extension = IDCommand.objects.get(uuid=FILE_BY_EXTENSION_CMD_UUID)
diff --git a/src/dashboard/src/fpr/migrations/0001_initial.py b/src/dashboard/src/fpr/migrations/0001_initial.py
index ba43b39f8e..40823113c2 100644
--- a/src/dashboard/src/fpr/migrations/0001_initial.py
+++ b/src/dashboard/src/fpr/migrations/0001_initial.py
@@ -1,6 +1,4 @@
import uuid
-from typing import List
-from typing import Tuple
import autoslug.fields
import main.models
@@ -9,7 +7,7 @@
class Migration(migrations.Migration):
- dependencies: List[Tuple[str, str]] = []
+ dependencies: list[tuple[str, str]] = []
operations = [
migrations.CreateModel(
diff --git a/src/dashboard/src/fpr/south_migrations/0001_initial.py b/src/dashboard/src/fpr/south_migrations/0001_initial.py
index b816629256..6af9c76709 100644
--- a/src/dashboard/src/fpr/south_migrations/0001_initial.py
+++ b/src/dashboard/src/fpr/south_migrations/0001_initial.py
@@ -1,5 +1,4 @@
from typing import Any
-from typing import Dict
from south.db import db
from south.v2 import SchemaMigration
@@ -452,7 +451,7 @@ def backwards(self, orm):
# Deleting model 'FileIDsBySingleID'
db.delete_table("FileIDsBySingleID")
- models: Dict[str, Any] = {
+ models: dict[str, Any] = {
"fpr.agent": {
"Meta": {"object_name": "Agent", "db_table": "u'Agent'"},
"agentIdentifierType": (
diff --git a/src/dashboard/src/fpr/south_migrations/0002_api_v2.py b/src/dashboard/src/fpr/south_migrations/0002_api_v2.py
index ebb878a3ed..603711fbc4 100644
--- a/src/dashboard/src/fpr/south_migrations/0002_api_v2.py
+++ b/src/dashboard/src/fpr/south_migrations/0002_api_v2.py
@@ -1,5 +1,4 @@
from typing import Any
-from typing import Dict
from south.db import db
from south.v2 import SchemaMigration
@@ -552,7 +551,7 @@ def backwards(self, orm):
),
)
- models: Dict[str, Any] = {
+ models: dict[str, Any] = {
"fpr.agent": {
"Meta": {"object_name": "Agent", "db_table": "u'Agent'"},
"agentIdentifierType": (
diff --git a/src/dashboard/src/settings/base.py b/src/dashboard/src/settings/base.py
index 462cb75175..3e6d49f167 100644
--- a/src/dashboard/src/settings/base.py
+++ b/src/dashboard/src/settings/base.py
@@ -19,8 +19,6 @@
import os
from io import StringIO
from typing import Any
-from typing import Dict
-from typing import List
import email_settings
from appconfig import Config
@@ -378,7 +376,7 @@ def _get_settings_from_file(path):
},
}
-TEMPLATES: List[Dict[str, Any]] = [
+TEMPLATES: list[dict[str, Any]] = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [os.path.join(BASE_PATH, "templates")],
diff --git a/tests/MCPClient/test_antivirus_clamdscan.py b/tests/MCPClient/test_antivirus_clamdscan.py
index 5bf353a8ca..443952cfc2 100644
--- a/tests/MCPClient/test_antivirus_clamdscan.py
+++ b/tests/MCPClient/test_antivirus_clamdscan.py
@@ -85,11 +85,10 @@ def patch(pass_by_stream, pass_by_reference, scanner, ret=OKAY_RET, excepts=Fals
scanner = setup_clamdscanner(settings, stream=False)
- with mock.patch.object(
- scanner, "pass_by_stream"
- ) as pass_by_stream, mock.patch.object(
- scanner, "pass_by_reference"
- ) as pass_by_reference:
+ with (
+ mock.patch.object(scanner, "pass_by_stream") as pass_by_stream,
+ mock.patch.object(scanner, "pass_by_reference") as pass_by_reference,
+ ):
deps = patch(pass_by_stream, pass_by_reference, scanner, ret=OKAY_RET)
passed, state, details = scanner.scan("/file")
assert passed is True
@@ -99,11 +98,10 @@ def patch(pass_by_stream, pass_by_reference, scanner, ret=OKAY_RET, excepts=Fals
deps.pass_by_reference.assert_called_once()
scanner = setup_clamdscanner(settings, stream=True)
- with mock.patch.object(
- scanner, "pass_by_stream"
- ) as pass_by_stream, mock.patch.object(
- scanner, "pass_by_reference"
- ) as pass_by_reference:
+ with (
+ mock.patch.object(scanner, "pass_by_stream") as pass_by_stream,
+ mock.patch.object(scanner, "pass_by_reference") as pass_by_reference,
+ ):
deps = patch(pass_by_stream, pass_by_reference, scanner, ret=OKAY_RET)
passed, state, details = scanner.scan("/file")
assert passed is True
@@ -112,33 +110,30 @@ def patch(pass_by_stream, pass_by_reference, scanner, ret=OKAY_RET, excepts=Fals
deps.pass_by_stream.assert_called_once()
deps.pass_by_reference.assert_not_called()
- with mock.patch.object(
- scanner, "pass_by_stream"
- ) as pass_by_stream, mock.patch.object(
- scanner, "pass_by_reference"
- ) as pass_by_reference:
+ with (
+ mock.patch.object(scanner, "pass_by_stream") as pass_by_stream,
+ mock.patch.object(scanner, "pass_by_reference") as pass_by_reference,
+ ):
patch(pass_by_stream, pass_by_reference, scanner, ret=ERROR_RET)
passed, state, details = scanner.scan("/file")
assert passed is False
assert state == "ERROR"
assert details == "Permission denied"
- with mock.patch.object(
- scanner, "pass_by_stream"
- ) as pass_by_stream, mock.patch.object(
- scanner, "pass_by_reference"
- ) as pass_by_reference:
+ with (
+ mock.patch.object(scanner, "pass_by_stream") as pass_by_stream,
+ mock.patch.object(scanner, "pass_by_reference") as pass_by_reference,
+ ):
patch(pass_by_stream, pass_by_reference, scanner, ret=FOUND_RET)
passed, state, details = scanner.scan("/file")
assert passed is False
assert state == "FOUND"
assert details == "Eicar-Test-Signature"
- with mock.patch.object(
- scanner, "pass_by_stream"
- ) as pass_by_stream, mock.patch.object(
- scanner, "pass_by_reference"
- ) as pass_by_reference:
+ with (
+ mock.patch.object(scanner, "pass_by_stream") as pass_by_stream,
+ mock.patch.object(scanner, "pass_by_reference") as pass_by_reference,
+ ):
# Testing a generic Exception returned by the clamdscan micorservice.
patch(pass_by_stream, pass_by_reference, scanner, ret=OKAY_RET, excepts=True)
passed, state, details = scanner.scan("/file")
@@ -146,11 +141,10 @@ def patch(pass_by_stream, pass_by_reference, scanner, ret=OKAY_RET, excepts=Fals
assert state is None
assert details is None
- with mock.patch.object(
- scanner, "pass_by_stream"
- ) as pass_by_stream, mock.patch.object(
- scanner, "pass_by_reference"
- ) as pass_by_reference:
+ with (
+ mock.patch.object(scanner, "pass_by_stream") as pass_by_stream,
+ mock.patch.object(scanner, "pass_by_reference") as pass_by_reference,
+ ):
# Testing a generic IOError that is not a broken pipe error that we're
# expecting to be able to manage from clamdscan.
patch(
@@ -165,11 +159,10 @@ def patch(pass_by_stream, pass_by_reference, scanner, ret=OKAY_RET, excepts=Fals
assert state is None
assert details is None
- with mock.patch.object(
- scanner, "pass_by_stream"
- ) as pass_by_stream, mock.patch.object(
- scanner, "pass_by_reference"
- ) as pass_by_reference:
+ with (
+ mock.patch.object(scanner, "pass_by_stream") as pass_by_stream,
+ mock.patch.object(scanner, "pass_by_reference") as pass_by_reference,
+ ):
# Broken pipe is a known error from the clamd library.
brokenpipe_error = OSError("Testing a broken pipe error")
brokenpipe_error.errno = errno.EPIPE
@@ -185,11 +178,10 @@ def patch(pass_by_stream, pass_by_reference, scanner, ret=OKAY_RET, excepts=Fals
assert state is None
assert details is None
- with mock.patch.object(
- scanner, "pass_by_stream"
- ) as pass_by_stream, mock.patch.object(
- scanner, "pass_by_reference"
- ) as pass_by_reference:
+ with (
+ mock.patch.object(scanner, "pass_by_stream") as pass_by_stream,
+ mock.patch.object(scanner, "pass_by_reference") as pass_by_reference,
+ ):
# The INSTREAM size limit error is known to us; test it here.
instream_error = BufferTooLongError("INSTREAM size limit exceeded. ERROR.")
patch(
@@ -204,11 +196,10 @@ def patch(pass_by_stream, pass_by_reference, scanner, ret=OKAY_RET, excepts=Fals
assert state is None
assert details is None
- with mock.patch.object(
- scanner, "pass_by_stream"
- ) as pass_by_stream, mock.patch.object(
- scanner, "pass_by_reference"
- ) as pass_by_reference:
+ with (
+ mock.patch.object(scanner, "pass_by_stream") as pass_by_stream,
+ mock.patch.object(scanner, "pass_by_reference") as pass_by_reference,
+ ):
# The clamd library can return a further error code here, and we we test it
# to make sure that if it does, it is managed.
connection_error = ConnectionError("Error while reading from socket.")
diff --git a/tests/MCPClient/test_create_aip_mets.py b/tests/MCPClient/test_create_aip_mets.py
index a56eb73e3c..cc37bdbf1f 100644
--- a/tests/MCPClient/test_create_aip_mets.py
+++ b/tests/MCPClient/test_create_aip_mets.py
@@ -7,7 +7,6 @@
import tempfile
import unittest
from pathlib import Path
-from typing import List
import archivematicaCreateMETSMetadataCSV
import archivematicaCreateMETSRights
@@ -30,7 +29,7 @@
class TestNormativeStructMap(TestCase):
"""Test creation of normative structMap."""
- fixture_files: List[str] = []
+ fixture_files: list[str] = []
fixtures = [os.path.join(THIS_DIR, "fixtures", p) for p in fixture_files]
@pytest.fixture(autouse=True)
diff --git a/tests/MCPClient/test_normalize.py b/tests/MCPClient/test_normalize.py
index 025a34d5a0..40667bebe4 100644
--- a/tests/MCPClient/test_normalize.py
+++ b/tests/MCPClient/test_normalize.py
@@ -1,10 +1,9 @@
import argparse
import pathlib
import uuid
-from typing import Mapping
+from collections.abc import Mapping
+from collections.abc import Sequence
from typing import Optional
-from typing import Sequence
-from typing import Tuple
from unittest import mock
import normalize
@@ -579,7 +578,7 @@ def execute_or_run_side_effect(
arguments: Optional[Sequence[str]] = None,
env_updates: Optional[Mapping[str, str]] = None,
capture_output: bool = True,
- ) -> Tuple[int, str, str]:
+ ) -> tuple[int, str, str]:
"""Mock thumbnail generation by creating a new temporary file."""
parser = argparse.ArgumentParser()
parser.add_argument("--output-directory", required=True)
@@ -707,7 +706,7 @@ def execute_or_run_side_effect(
arguments: Optional[Sequence[str]] = None,
env_updates: Optional[Mapping[str, str]] = None,
capture_output: bool = True,
- ) -> Tuple[int, str, str]:
+ ) -> tuple[int, str, str]:
"""Mock a complex normalization command that initially fails and then
falls back to its default rule and also runs its own verification and
event detail commands.
diff --git a/tests/MCPClient/test_verify_checksum.py b/tests/MCPClient/test_verify_checksum.py
index 94cf4dde88..06f9a3179f 100644
--- a/tests/MCPClient/test_verify_checksum.py
+++ b/tests/MCPClient/test_verify_checksum.py
@@ -146,10 +146,11 @@ def test_compare_hashes_failed(self):
"sha256: objects/nested/ファイル3.bin: FAILED\n"
"sha256: objects/readonly.file: FAILED open or read"
)
- with mock.patch.object(
- hashsum, "_call", return_value=output_string
- ) as mock_call, mock.patch.object(
- hashsum, "count_and_compare_lines", return_value=True
+ with (
+ mock.patch.object(
+ hashsum, "_call", return_value=output_string
+ ) as mock_call,
+ mock.patch.object(hashsum, "count_and_compare_lines", return_value=True),
):
mock_call.side_effect = subprocess.CalledProcessError(
returncode=1, cmd=toolname, output=output_string
@@ -186,10 +187,11 @@ def test_compare_hashes_with_bad_files(self):
"sha1: comparison exited with status: 1. Please check the formatting of the checksums or integrity of the files.\n"
"sha1: sha1sum: WARNING: 1 line is improperly formatted"
)
- with mock.patch.object(
- hashsum, "_call", return_value=no_proper_output
- ) as mock_call, mock.patch.object(
- hashsum, "count_and_compare_lines", return_value=True
+ with (
+ mock.patch.object(
+ hashsum, "_call", return_value=no_proper_output
+ ) as mock_call,
+ mock.patch.object(hashsum, "count_and_compare_lines", return_value=True),
):
mock_call.side_effect = subprocess.CalledProcessError(
returncode=1, cmd=toolname, output=no_proper_output
@@ -226,10 +228,9 @@ def test_line_comparison_fail(self):
hash_file = "metadata/checksum.sha1"
hashsum = self.setup_hashsum(hash_file, Job("stub", "stub", ["", ""]))
toolname = "sha1sum"
- with mock.patch.object(
- hashsum, "_call", return_value=None
- ) as mock_call, mock.patch.object(
- hashsum, "count_and_compare_lines", return_value=False
+ with (
+ mock.patch.object(hashsum, "_call", return_value=None) as mock_call,
+ mock.patch.object(hashsum, "count_and_compare_lines", return_value=False),
):
mock_call.side_effect = subprocess.CalledProcessError(
returncode=1, cmd=toolname, output=None
diff --git a/tests/MCPServer/test_integration.py b/tests/MCPServer/test_integration.py
index 468bba4739..274213a7ff 100644
--- a/tests/MCPServer/test_integration.py
+++ b/tests/MCPServer/test_integration.py
@@ -114,10 +114,13 @@ def test_workflow_integration(
settings.PROCESSING_DIRECTORY = str(tmp_path / "processing")
mock_get_task_backend.return_value = echo_backend
- with mock.patch.dict(
- "server.packages.BASE_REPLACEMENTS",
- {r"%processingDirectory%": settings.PROCESSING_DIRECTORY},
- ), mock.patch.object(transfer, "files", return_value=dummy_file_replacements):
+ with (
+ mock.patch.dict(
+ "server.packages.BASE_REPLACEMENTS",
+ {r"%processingDirectory%": settings.PROCESSING_DIRECTORY},
+ ),
+ mock.patch.object(transfer, "files", return_value=dummy_file_replacements),
+ ):
# Schedule the first job
first_workflow_chain = workflow.get_chains()[
"3816f689-65a8-4ad0-ac27-74292a70b093"
diff --git a/tests/archivematicaCommon/test_execute_functions.py b/tests/archivematicaCommon/test_execute_functions.py
index 0f1fa41b91..3425d8ffc4 100644
--- a/tests/archivematicaCommon/test_execute_functions.py
+++ b/tests/archivematicaCommon/test_execute_functions.py
@@ -1,7 +1,7 @@
import pathlib
import shlex
import tempfile
-from typing import Generator
+from collections.abc import Generator
from unittest.mock import ANY
from unittest.mock import Mock
from unittest.mock import patch
diff --git a/tests/dashboard/components/accounts/test_views.py b/tests/dashboard/components/accounts/test_views.py
index 8b42a678dc..2465883655 100644
--- a/tests/dashboard/components/accounts/test_views.py
+++ b/tests/dashboard/components/accounts/test_views.py
@@ -1,7 +1,6 @@
import hmac
import uuid
from hashlib import sha1
-from typing import Type
from unittest import mock
from urllib.parse import parse_qs
from urllib.parse import urlparse
@@ -60,7 +59,7 @@ def dashboard_uuid() -> None:
@pytest.fixture
-def non_administrative_user(django_user_model: Type[User]) -> User:
+def non_administrative_user(django_user_model: type[User]) -> User:
return django_user_model.objects.create_user(
username="test",
password="test",
diff --git a/tests/integration/test_oidc_auth.py b/tests/integration/test_oidc_auth.py
index 8eac1e8669..bcda6ae2fe 100644
--- a/tests/integration/test_oidc_auth.py
+++ b/tests/integration/test_oidc_auth.py
@@ -1,6 +1,5 @@
import os
import uuid
-from typing import Type
import pytest
from components import helpers
@@ -20,7 +19,7 @@ def dashboard_uuid() -> None:
@pytest.fixture
-def user(django_user_model: Type[User]) -> User:
+def user(django_user_model: type[User]) -> User:
user = django_user_model.objects.create(
username="foobar",
email="foobar@example.com",
@@ -38,7 +37,7 @@ def test_oidc_backend_creates_local_user(
page: Page,
live_server: LiveServer,
dashboard_uuid: None,
- django_user_model: Type[User],
+ django_user_model: type[User],
) -> None:
page.goto(live_server.url)