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)