From 366e3e228cb463816405fdc2501f2efd2cf00979 Mon Sep 17 00:00:00 2001 From: Heinz-Alexander Fuetterer Date: Sun, 8 Dec 2024 13:17:26 +0100 Subject: [PATCH 1/2] feat: drop support for python 3.8 --- .github/workflows/ci.yml | 2 +- CONTRIBUTING.md | 2 +- pyproject.toml | 3 +-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 50bb65989a..b5e1496151 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -100,7 +100,7 @@ jobs: runs-on: ubuntu-24.04 strategy: matrix: - python-version: ['3.8', '3.12'] + python-version: ['3.9', '3.12'] db-backend: [mysql, postgres] steps: - uses: actions/checkout@v4 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 93e917e5a7..e671bbe8e2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -31,7 +31,7 @@ If you decide to work on the issue yourself, please wait until you received some ## How to set up your development environment -You need [Python 3.8+](https://www.python.org/downloads). +You need [Python 3.9+](https://www.python.org/downloads). Install the package with development requirements: diff --git a/pyproject.toml b/pyproject.toml index 4eb52f24e0..785c6c3446 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,7 +17,7 @@ license = {text = "Apache-2.0"} authors = [ {name = "RDMO Arbeitsgemeinschaft", email = "rdmo-team@listserv.dfn.de"}, ] -requires-python = ">=3.8" +requires-python = ">=3.9" classifiers = [ "Development Status :: 5 - Production/Stable", "Environment :: Web Environment", @@ -27,7 +27,6 @@ classifiers = [ "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", From 718f0342fbf9d54d11e01dfa2258ac1d4cabcd19 Mon Sep 17 00:00:00 2001 From: David Wallace Date: Wed, 18 Dec 2024 18:47:35 +0100 Subject: [PATCH 2/2] style(py39): apply ruff fixes for typing Signed-off-by: David Wallace --- rdmo/core/import_helpers.py | 3 ++- rdmo/core/imports.py | 8 ++++---- rdmo/core/xml.py | 16 ++++++++-------- rdmo/management/import_utils.py | 7 +++---- rdmo/management/imports.py | 8 ++++---- rdmo/management/tests/helpers_import_elements.py | 8 ++++---- rdmo/management/tests/helpers_models.py | 3 +-- rdmo/management/tests/test_merge_attributes.py | 6 +++--- 8 files changed, 29 insertions(+), 30 deletions(-) diff --git a/rdmo/core/import_helpers.py b/rdmo/core/import_helpers.py index 544db24971..540947fdf3 100644 --- a/rdmo/core/import_helpers.py +++ b/rdmo/core/import_helpers.py @@ -1,6 +1,7 @@ +from collections.abc import Iterable, Sequence from dataclasses import dataclass, field from inspect import signature -from typing import Callable, Iterable, Optional, Sequence, Union +from typing import Callable, Optional, Union from django.db import models diff --git a/rdmo/core/imports.py b/rdmo/core/imports.py index 7fd28969c4..008bc5f833 100644 --- a/rdmo/core/imports.py +++ b/rdmo/core/imports.py @@ -5,7 +5,7 @@ from enum import Enum from os.path import join as pj from random import randint -from typing import List, Optional, Tuple, Union +from typing import Optional, Union from django.core.exceptions import ObjectDoesNotExist, ValidationError from django.db import models @@ -54,7 +54,7 @@ def generate_tempfile_name(): return fn -def get_or_return_instance(model: models.Model, uri: Optional[str] = None) -> Tuple[models.Model, bool]: +def get_or_return_instance(model: models.Model, uri: Optional[str] = None) -> tuple[models.Model, bool]: if uri is None: return model(), True try: @@ -125,10 +125,10 @@ def _initialize_track_changes_element_field(element: dict, element_field: str) - def track_changes_on_element(element: dict, element_field: str, - new_value: Union[str, List[str], None] = None, + new_value: Union[str, list[str], None] = None, instance_field: Optional[str] = None, original=None, - original_value: Optional[Union[str, List[str]]] = None): + original_value: Optional[Union[str, list[str]]] = None): if (original is None and original_value is None) or new_value is None: return diff --git a/rdmo/core/xml.py b/rdmo/core/xml.py index 264977352e..fa80efe5bb 100644 --- a/rdmo/core/xml.py +++ b/rdmo/core/xml.py @@ -2,7 +2,7 @@ import re from collections import OrderedDict from pathlib import Path -from typing import Dict, Optional, Tuple +from typing import Optional from xml.etree.ElementTree import Element as xmlElement from django.utils.translation import gettext_lazy as _ @@ -20,14 +20,14 @@ ELEMENTS_USING_KEY = {RDMO_MODELS['attribute']} -def resolve_file(file_name: str) -> Tuple[Optional[Path], Optional[str]]: +def resolve_file(file_name: str) -> tuple[Optional[Path], Optional[str]]: file = Path(file_name).resolve() if file.exists(): return file, None return None, _('This file does not exists.') -def read_xml(file: Path) -> Tuple[Optional[xmlElement], Optional[str]]: +def read_xml(file: Path) -> tuple[Optional[xmlElement], Optional[str]]: # step 2: parse xml and get the root try: root = ET.parse(file).getroot() @@ -36,7 +36,7 @@ def read_xml(file: Path) -> Tuple[Optional[xmlElement], Optional[str]]: return None, _('XML Parsing Error') + f': {e!s}' -def validate_root(root: Optional[xmlElement]) -> Tuple[bool, Optional[str]]: +def validate_root(root: Optional[xmlElement]) -> tuple[bool, Optional[str]]: if root is None: return False, _('The content of the XML file does not consist of well-formed data or markup.') if root.tag != 'rdmo': @@ -44,7 +44,7 @@ def validate_root(root: Optional[xmlElement]) -> Tuple[bool, Optional[str]]: return True, None -def validate_and_get_xml_version_from_root(root: xmlElement) -> Tuple[Optional[Version], list]: +def validate_and_get_xml_version_from_root(root: xmlElement) -> tuple[Optional[Version], list]: unparsed_root_version = root.attrib.get('version') or LEGACY_RDMO_XML_VERSION root_version, rdmo_version = parse(unparsed_root_version), parse(RDMO_INSTANCE_VERSION) if root_version > rdmo_version: @@ -71,7 +71,7 @@ def validate_legacy_elements(elements: dict, root_version: Version) -> list: return errors -def parse_elements(root: xmlElement) -> Tuple[Dict, Optional[str]]: +def parse_elements(root: xmlElement) -> tuple[dict, Optional[str]]: # step 3: create element dicts from xml try: elements = flat_xml_to_elements(root) @@ -81,7 +81,7 @@ def parse_elements(root: xmlElement) -> Tuple[Dict, Optional[str]]: return {}, _('This is not a valid RDMO XML file.') -def parse_xml_to_elements(xml_file=None) -> Tuple[OrderedDict, list]: +def parse_xml_to_elements(xml_file=None) -> tuple[OrderedDict, list]: errors = [] @@ -257,7 +257,7 @@ def validate_pre_conversion_for_missing_key_in_legacy_elements(elements, version raise ValueError(f"Missing legacy elements, elements containing 'key' were expected for this XML with version {version} and elements {models_in_elements}.") # noqa: E501 -def update_related_legacy_elements(elements: Dict, +def update_related_legacy_elements(elements: dict, target_uri: str, source_model: str, legacy_element_field: str, element_field: str): # search for the related elements that use the uri diff --git a/rdmo/management/import_utils.py b/rdmo/management/import_utils.py index 5ec31d3350..3000cc3416 100644 --- a/rdmo/management/import_utils.py +++ b/rdmo/management/import_utils.py @@ -1,7 +1,6 @@ import logging from collections import defaultdict from dataclasses import asdict -from typing import Dict, Set, Tuple from django.core.exceptions import FieldDoesNotExist from django.db.models import Model @@ -40,7 +39,7 @@ def is_valid_import_element(element: dict) -> bool: return True -def get_redundant_keys_from_element(element_keys: Set, model: Model) -> Set: +def get_redundant_keys_from_element(element_keys: set, model: Model) -> set: model_fields = {i.name for i in model._meta.get_fields()} required_element_keys = {'uri', 'model'} import_dict_keys = {i.value for i in IMPORT_ELEMENT_INIT_DICT.keys()} @@ -51,14 +50,14 @@ def get_redundant_keys_from_element(element_keys: Set, model: Model) -> Set: redundant_keys = redundant_keys - element_lang_keys return redundant_keys -def initialize_import_element_dict(element: Dict) -> None: +def initialize_import_element_dict(element: dict) -> None: # initialize element dict with default values for _k,_val in IMPORT_ELEMENT_INIT_DICT.items(): element[_k] = _val() return element -def initialize_and_clean_import_element_dict(element: Dict, model: Model) -> Tuple[Dict, Dict]: +def initialize_and_clean_import_element_dict(element: dict, model: Model) -> tuple[dict, dict]: redundant_keys = get_redundant_keys_from_element(set(element.keys()), model) excluded_element_data = {} for k in redundant_keys: diff --git a/rdmo/management/imports.py b/rdmo/management/imports.py index 3e483d24aa..6bbffc0e7f 100644 --- a/rdmo/management/imports.py +++ b/rdmo/management/imports.py @@ -1,7 +1,7 @@ import copy import logging from collections import OrderedDict -from typing import Dict, List, Optional +from typing import Optional from django.conf import settings from django.contrib.sites.shortcuts import get_current_site @@ -57,7 +57,7 @@ def import_elements(uploaded_elements: OrderedDict, save: bool = True, - request: Optional[HttpRequest] = None) -> List[Dict]: + request: Optional[HttpRequest] = None) -> list[dict]: imported_elements = [] uploaded_elements_initial_ordering = {uri: n for n, uri in enumerate(uploaded_elements.keys())} uploaded_uris = set(uploaded_elements.keys()) @@ -94,11 +94,11 @@ def import_elements(uploaded_elements: OrderedDict, def import_element( - element: Optional[Dict] = None, + element: Optional[dict] = None, save: bool = True, request: Optional[HttpRequest] = None, current_site = None - ) -> Dict: + ) -> dict: initialize_import_element_dict(element) diff --git a/rdmo/management/tests/helpers_import_elements.py b/rdmo/management/tests/helpers_import_elements.py index d50b923952..49fc3bae9e 100644 --- a/rdmo/management/tests/helpers_import_elements.py +++ b/rdmo/management/tests/helpers_import_elements.py @@ -1,7 +1,7 @@ import random from collections import OrderedDict from functools import partial -from typing import Dict, List, Optional, Tuple, Union +from typing import Optional, Union from rdmo.core.imports import ImportElementFields, track_changes_on_element from rdmo.management.import_utils import initialize_import_element_dict @@ -32,7 +32,7 @@ def filter_changed_fields(element, updated_fields=None) -> bool: return True return _changed -def get_changed_elements(elements: List[Dict]) -> Dict[str, Dict[str,Union[bool,str]]]: +def get_changed_elements(elements: list[dict]) -> dict[str, dict[str,Union[bool,str]]]: changed_elements = {} for element in elements: @@ -47,14 +47,14 @@ def get_changed_elements(elements: List[Dict]) -> Dict[str, Dict[str,Union[bool, return changed_elements -def _test_helper_filter_updated_and_changed(elements: List[Dict], updated_fields: Optional[Tuple]) -> List[Dict]: +def _test_helper_filter_updated_and_changed(elements: list[dict], updated_fields: Optional[tuple]) -> list[dict]: filter_func = partial(filter_changed_fields, updated_fields=updated_fields) changed_elements = filter(filter_func, elements) return list(changed_elements) def _test_helper_change_fields_elements(elements, - fields_to_update: Optional[Tuple] = None, + fields_to_update: Optional[tuple] = None, n=3) -> OrderedDict: """ elements test preparation function """ diff --git a/rdmo/management/tests/helpers_models.py b/rdmo/management/tests/helpers_models.py index bc0042662f..668a81a038 100644 --- a/rdmo/management/tests/helpers_models.py +++ b/rdmo/management/tests/helpers_models.py @@ -1,5 +1,4 @@ from dataclasses import dataclass -from typing import List from rdmo.conditions.models import Condition from rdmo.core.models import Model @@ -54,6 +53,6 @@ def verbose_name_plural(self) -> str: ) -def delete_all_objects(db_models: List): +def delete_all_objects(db_models: list): for db_model in db_models: db_model.objects.all().delete() diff --git a/rdmo/management/tests/test_merge_attributes.py b/rdmo/management/tests/test_merge_attributes.py index 5d18ee9dea..edc880745d 100644 --- a/rdmo/management/tests/test_merge_attributes.py +++ b/rdmo/management/tests/test_merge_attributes.py @@ -1,6 +1,6 @@ import io from string import Template -from typing import List, Union +from typing import Union import pytest @@ -48,7 +48,7 @@ def _get_queryset(related_field, attribute=None): return model.objects.filter(**{lookup_field: attribute}) -def create_new_uris_with_uri_prefix_for_template(new_uri_prefix: str) -> List[str]: +def create_new_uris_with_uri_prefix_for_template(new_uri_prefix: str) -> list[str]: new_uris = [] for extra_path in VIEW_TEMPLATE_URI_PATH_ADDITIONS: new_uri_path = VIEW_TEMPLATE_URI_PATH + extra_path @@ -57,7 +57,7 @@ def create_new_uris_with_uri_prefix_for_template(new_uri_prefix: str) -> List[st return new_uris -def create_copy_of_view_that_uses_new_attribute(db, new_prefixes: List[str]): +def create_copy_of_view_that_uses_new_attribute(db, new_prefixes: list[str]): qs = View.objects.filter(**{"uri__contains": EXAMPLE_VIEW_URI_PATH}).all() if not qs.exists(): raise ValueError("Views for tests should exist here.")