Skip to content

Commit 4261f34

Browse files
authored
drop support for python 3.9 (EOL), add python 3.13 (#707)
1 parent 7a14208 commit 4261f34

File tree

26 files changed

+120
-138
lines changed

26 files changed

+120
-138
lines changed

.github/workflows/plugin_test.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# This workflow will install Python 3.9 and run the tests of all supported plugins.
1+
# This workflow will install Python 3.12 and run the tests of all supported plugins.
22
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
33

44
name: test plugins

.github/workflows/pytest.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ jobs:
1515
strategy:
1616
fail-fast: false
1717
matrix:
18-
python-version: ["3.9", "3.10", "3.11", "3.12"]
18+
python-version: ["3.10", "3.11", "3.12", "3.13"]
1919

2020
steps:
2121
- uses: actions/checkout@v4
@@ -30,7 +30,6 @@ jobs:
3030
run: |
3131
uv pip install coverage coveralls
3232
- name: Install nomad
33-
if: "${{ matrix.python-version != '3.8' && matrix.python-version != '3.9'}}"
3433
run: |
3534
uv pip install nomad-lab[infrastructure]@git+https://gitlab.mpcdf.mpg.de/nomad-lab/nomad-FAIR.git
3635
- name: Install pynx

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,12 @@ authors = [
1212
description = "Extend NeXus for experiments and characterization in Materials Science and Materials Engineering and serve as a NOMAD parser implementation for NeXus."
1313
readme = "README.md"
1414
license = { file = "LICENSE" }
15-
requires-python = ">=3.9"
15+
requires-python = ">=3.10"
1616
classifiers = [
17-
"Programming Language :: Python :: 3.9",
1817
"Programming Language :: Python :: 3.10",
1918
"Programming Language :: Python :: 3.11",
2019
"Programming Language :: Python :: 3.12",
20+
"Programming Language :: Python :: 3.13",
2121
"License :: OSI Approved :: Apache Software License",
2222
"Operating System :: OS Independent",
2323
]

src/pynxtools/_build_wrapper.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
pass
1515

1616

17-
def get_vcs_version(tag_match="*[0-9]*") -> Optional[str]:
17+
def get_vcs_version(tag_match="*[0-9]*") -> str | None:
1818
"""
1919
The version of the Nexus standard and the NeXus Definition language
2020
based on git tags and commits

src/pynxtools/dataconverter/convert.py

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -43,18 +43,7 @@
4343
logger = logging.getLogger("pynxtools")
4444

4545

46-
if sys.version_info >= (3, 10):
47-
from importlib.metadata import entry_points
48-
else:
49-
try:
50-
from importlib_metadata import entry_points
51-
except ImportError:
52-
# If importlib_metadata is not present
53-
# we provide a dummy function just returning an empty list.
54-
# pylint: disable=W0613
55-
def entry_points(group):
56-
"""Dummy function for importlib_metadata"""
57-
return []
46+
from importlib.metadata import entry_points
5847

5948

6049
class ValidationFailed(Exception):
@@ -106,7 +95,7 @@ def transfer_data_into_template(
10695
input_file,
10796
reader,
10897
nxdl_name,
109-
nxdl_root: Optional[ET._Element] = None,
98+
nxdl_root: ET._Element | None = None,
11099
skip_verify: bool = False,
111100
**kwargs,
112101
):

src/pynxtools/dataconverter/hdfdict.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
def hdf_file(hdf, lazy=True, *args, **kwargs):
1616
"""Context manager that yields an h5 file if `hdf` is a string,
1717
otherwise it yields hdf as is."""
18-
if isinstance(hdf, (str, Path)):
18+
if isinstance(hdf, str | Path):
1919
if not lazy:
2020
# The file can be closed after reading
2121
# therefore the context manager is used.
@@ -221,7 +221,7 @@ def dump(data, hdf, packer=pack_dataset, mode="w", *args, **kwargs):
221221

222222
def _recurse(datadict, hdfobject):
223223
for key, value in datadict.items():
224-
if isinstance(value, (dict, LazyHdfDict)):
224+
if isinstance(value, dict | LazyHdfDict):
225225
hdfgroup = hdfobject.create_group(key)
226226
_recurse(value, hdfgroup)
227227
else:

src/pynxtools/dataconverter/helpers.py

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
from datetime import datetime, timezone
2626
from enum import Enum, auto
2727
from functools import cache, lru_cache
28-
from typing import Any, Callable, Literal, Optional, Union, cast
28+
from typing import Any, Literal, Optional, Union, cast
2929

3030
import h5py
3131
import lxml.etree as ET
@@ -132,7 +132,7 @@ def __init__(self):
132132

133133
self.logging = True
134134

135-
def _log(self, path: str, log_type: ValidationProblem, value: Optional[Any], *args):
135+
def _log(self, path: str, log_type: ValidationProblem, value: Any | None, *args):
136136
if value is None:
137137
value = "<unknown>"
138138

@@ -287,7 +287,7 @@ def collect_and_log(
287287
self,
288288
path: str,
289289
log_type: ValidationProblem,
290-
value: Optional[Any],
290+
value: Any | None,
291291
*args,
292292
**kwargs,
293293
):
@@ -359,7 +359,7 @@ def get_nxdl_name_from_elem(xml_element) -> str:
359359
return name_to_add
360360

361361

362-
def get_nxdl_name_for(xml_elem: ET._Element) -> Optional[str]:
362+
def get_nxdl_name_for(xml_elem: ET._Element) -> str | None:
363363
"""
364364
Get the name of the element from the NXDL element.
365365
For an entity having a name this is just the name.
@@ -625,7 +625,7 @@ def convert_nexus_to_caps(nexus_name):
625625
return nexus_name[2:].upper()
626626

627627

628-
def contains_uppercase(field_name: Optional[str]) -> bool:
628+
def contains_uppercase(field_name: str | None) -> bool:
629629
"""Helper function to check if a field name contains uppercase characters."""
630630
if field_name is None:
631631
return False
@@ -648,7 +648,7 @@ def convert_nexus_to_suggested_name(nexus_name):
648648
return nexus_name[2:]
649649

650650

651-
def convert_data_converter_entry_to_nxdl_path_entry(entry) -> Union[str, None]:
651+
def convert_data_converter_entry_to_nxdl_path_entry(entry) -> str | None:
652652
"""
653653
Helper function to convert data converter style entry to NXDL style entry:
654654
ENTRY[entry] -> ENTRY
@@ -784,7 +784,7 @@ def is_positive_int(value: Any) -> bool:
784784
return bool(np.all(value > 0))
785785

786786

787-
def convert_str_to_bool_safe(value: str) -> Optional[bool]:
787+
def convert_str_to_bool_safe(value: str) -> bool | None:
788788
"""Only returns True or False if someone mistakenly adds quotation marks but mean a bool.
789789
790790
For everything else it raises a ValueError.
@@ -988,7 +988,7 @@ def is_valid_enum(
988988
)
989989

990990

991-
def split_class_and_name_of(name: str) -> tuple[Optional[str], str]:
991+
def split_class_and_name_of(name: str) -> tuple[str | None, str]:
992992
"""
993993
Return the class and the name of a data dict entry of the form
994994
`split_class_and_name_of("ENTRY[entry]")`, which will return `("ENTRY", "entry")`.
@@ -1225,7 +1225,7 @@ def get_first_group(root):
12251225
return root
12261226

12271227

1228-
def check_for_valid_atom_types(atoms: Union[str, list]):
1228+
def check_for_valid_atom_types(atoms: str | list):
12291229
"""Check for whether atom exists in periodic table."""
12301230

12311231
if isinstance(atoms, list):
@@ -1357,7 +1357,7 @@ def extract_atom_types(formula, mode="hill"):
13571357

13581358

13591359
# pylint: disable=too-many-branches
1360-
def transform_to_intended_dt(str_value: Any) -> Optional[Any]:
1360+
def transform_to_intended_dt(str_value: Any) -> Any | None:
13611361
"""Transform string to the intended data type, if not then return str_value.
13621362
13631363
E.g '2.5E-2' will be transfor into 2.5E-2
@@ -1407,7 +1407,7 @@ def transform_to_intended_dt(str_value: Any) -> Optional[Any]:
14071407
modified_parts: list = []
14081408
for part in parts:
14091409
part = transform_to_intended_dt(part)
1410-
if isinstance(part, (int, float)):
1410+
if isinstance(part, int | float):
14111411
modified_parts.append(part)
14121412
else:
14131413
return str_value
@@ -1430,9 +1430,7 @@ def nested_dict_to_slash_separated_path(
14301430
flattened_dict[path] = val
14311431

14321432

1433-
def clean_str_attr(
1434-
attr: Optional[Union[str, bytes]], encoding: str = "utf-8"
1435-
) -> Optional[str]:
1433+
def clean_str_attr(attr: str | bytes | None, encoding: str = "utf-8") -> str | None:
14361434
"""
14371435
Return the attribute as a string.
14381436

src/pynxtools/dataconverter/nexus_tree.py

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ class NexusNode(NodeMixin):
156156

157157
name: str
158158
nx_type: Literal["group", "field", "attribute", "choice"]
159-
name_type: Optional[Literal["specified", "any", "partial"]] = "specified"
159+
name_type: Literal["specified", "any", "partial"] | None = "specified"
160160
optionality: Literal["required", "recommended", "optional"] = "required"
161161
variadic: bool = False
162162
inheritance: list[ET._Element]
@@ -165,8 +165,8 @@ class NexusNode(NodeMixin):
165165
nxdl_base: str
166166
occurrence_limits: tuple[
167167
# TODO: Use Annotated[int, Field(strict=True, ge=0)] for py>3.8
168-
Optional[int],
169-
Optional[int],
168+
int | None,
169+
int | None,
170170
] = (None, None)
171171
lvl_map = {
172172
"required": ("required",),
@@ -202,12 +202,12 @@ def __init__(
202202
self,
203203
name: str,
204204
nx_type: Literal["group", "field", "attribute", "choice"],
205-
name_type: Optional[Literal["specified", "any", "partial"]] = "specified",
205+
name_type: Literal["specified", "any", "partial"] | None = "specified",
206206
optionality: Literal["required", "recommended", "optional"] = "required",
207-
variadic: Optional[bool] = None,
207+
variadic: bool | None = None,
208208
parent: Optional["NexusNode"] = None,
209-
inheritance: Optional[list[Any]] = None,
210-
nxdl_base: Optional[str] = None,
209+
inheritance: list[Any] | None = None,
210+
nxdl_base: str | None = None,
211211
) -> None:
212212
super().__init__()
213213
self.name = name
@@ -322,9 +322,9 @@ def get_child_for(self, xml_elem: ET._Element) -> Optional["NexusNode"]:
322322

323323
def get_all_direct_children_names(
324324
self,
325-
node_type: Optional[str] = None,
326-
nx_class: Optional[str] = None,
327-
depth: Optional[int] = None,
325+
node_type: str | None = None,
326+
nx_class: str | None = None,
327+
depth: int | None = None,
328328
only_appdef: bool = False,
329329
) -> set[str]:
330330
"""
@@ -485,7 +485,7 @@ def required_fields_and_attrs_names(
485485

486486
return req_children
487487

488-
def get_docstring(self, depth: Optional[int] = None) -> dict[str, str]:
488+
def get_docstring(self, depth: int | None = None) -> dict[str, str]:
489489
"""
490490
Gets the docstrings of the current node and its parents up to a certain depth.
491491
@@ -765,8 +765,8 @@ class NexusGroup(NexusNode):
765765
nx_class: str
766766
occurrence_limits: tuple[
767767
# TODO: Use Annotated[int, Field(strict=True, ge=0)] for py>3.8
768-
Optional[int],
769-
Optional[int],
768+
int | None,
769+
int | None,
770770
] = (None, None)
771771

772772
def _check_sibling_namefit(self):
@@ -920,11 +920,11 @@ class NexusEntity(NexusNode):
920920
"""
921921

922922
nx_type: Literal["field", "attribute"]
923-
unit: Optional[NexusUnitCategory] = None
923+
unit: NexusUnitCategory | None = None
924924
dtype: NexusType = "NX_CHAR"
925-
items: Optional[list[str]] = None
925+
items: list[str] | None = None
926926
open_enum: bool = False
927-
shape: Optional[tuple[Optional[int], ...]] = None
927+
shape: tuple[int | None, ...] | None = None
928928

929929
def _check_compatibility_with(self, xml_elem: ET._Element) -> bool:
930930
"""Check compatibility of this node with an XML element from the (possible) inheritance"""
@@ -1018,7 +1018,7 @@ def _check_dimensions_fit(xml_elem: ET._Element) -> bool:
10181018
return True
10191019
elem_dim = elem_dimensions.findall("nx:dim", namespaces=namespaces)
10201020
elem_dimension_rank = rank if rank is not None else len(rank)
1021-
dims: list[Optional[int]] = [None] * int(rank)
1021+
dims: list[int | None] = [None] * int(rank)
10221022

10231023
for dim in elem_dim:
10241024
idx = int(dim.attrib["index"])
@@ -1134,7 +1134,7 @@ def _set_shape(self):
11341134
return
11351135
xml_dim = dimension.findall("nx:dim", namespaces=namespaces)
11361136
rank = rank if rank is not None else len(xml_dim)
1137-
dims: list[Optional[int]] = [None] * int(rank)
1137+
dims: list[int | None] = [None] * int(rank)
11381138
for dim in xml_dim:
11391139
idx = int(dim.attrib["index"])
11401140
if "value" not in dim.attrib:

src/pynxtools/dataconverter/readers/json_map/reader.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,11 @@ def get_val_nested_keystring_from_dict(keystring, data):
4545
Fetches data from the actual data dict using path strings without a leading '/':
4646
'path/to/data/in/dict'
4747
"""
48-
if isinstance(keystring, (list, dict)):
48+
if isinstance(keystring, list | dict):
4949
return keystring
5050

5151
current_key = keystring.split("/")[0]
52-
if isinstance(data[current_key], (dict, hdfdict.LazyHdfDict)):
52+
if isinstance(data[current_key], dict | hdfdict.LazyHdfDict):
5353
return get_val_nested_keystring_from_dict(
5454
keystring[keystring.find("/") + 1 :], data[current_key]
5555
)
@@ -68,7 +68,7 @@ def get_attrib_nested_keystring_from_dict(keystring, data):
6868
Fetches all attributes from the data dict using path strings without a leading '/':
6969
'path/to/data/in/dict'
7070
"""
71-
if isinstance(keystring, (list, dict)):
71+
if isinstance(keystring, list | dict):
7272
return keystring
7373

7474
key_splits = keystring.split("/")

src/pynxtools/dataconverter/readers/json_yml/reader.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818
"""An example reader implementation for the DataConverter."""
1919

2020
import os
21-
from typing import Any, Callable
21+
from collections.abc import Callable
22+
from typing import Any
2223

2324
from pynxtools.dataconverter.readers.base.reader import BaseReader
2425
from pynxtools.dataconverter.template import Template

0 commit comments

Comments
 (0)