diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9e5064b5c45..3434101e47e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,7 +24,7 @@ jobs: fetch-depth: 0 - uses: actions/setup-python@v5 with: - python-version: "3.9" + python-version: "3.10" - name: Check README syntax run: | pip install restructuredtext-lint @@ -39,7 +39,7 @@ jobs: matrix: include: - os: ubuntu-latest - python-version: "3.9" + python-version: "3.10" install-method: mamba - os: ubuntu-latest @@ -60,12 +60,12 @@ jobs: install-method: pip - os: macos-latest - python-version: "3.12" - install-method: mamba + python-version: "3.10" + install-method: pip - os: macos-latest - python-version: "3.9" - install-method: pip + python-version: "3.12" + install-method: mamba defaults: run: @@ -164,7 +164,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: "3.9" + python-version: "3.10" - name: Install doc dependencies run: | diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 6be28ee5c85..6ffd378669e 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -6,7 +6,7 @@ build: - ffmpeg - graphviz tools: - python: "3.9" + python: "3.10" python: install: diff --git a/docs/changes/2526.maintenance.rst b/docs/changes/2526.maintenance.rst new file mode 100644 index 00000000000..8bb17904ae0 --- /dev/null +++ b/docs/changes/2526.maintenance.rst @@ -0,0 +1 @@ +Drop support for python 3.9. diff --git a/docs/conf.py b/docs/conf.py index f1c65238e1d..61e104d8cd7 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,6 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- -# # ctapipe documentation build configuration file, created by # sphinx-quickstart on Fri Jan 6 10:22:58 2017. # @@ -390,7 +388,7 @@ def setup(app): # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = { - "python": ("https://docs.python.org/3.9", None), + "python": ("https://docs.python.org/3.10", None), "numpy": ("https://numpy.org/doc/stable/", None), "scipy": ("https://docs.scipy.org/doc/scipy/", None), "astropy": ("https://docs.astropy.org/en/latest/", None), diff --git a/examples/algorithms/nd_interpolation.py b/examples/algorithms/nd_interpolation.py index 21a03057e1a..ae776f3a770 100644 --- a/examples/algorithms/nd_interpolation.py +++ b/examples/algorithms/nd_interpolation.py @@ -117,7 +117,7 @@ plt.pcolormesh( energy_table.bin_centers(0), energy_table.bin_centers(1), energy_table.hist.T ) -plt.title("Raw table, uninterpolated {0}".format(energy_table.hist.T.shape)) +plt.title(f"Raw table, uninterpolated {energy_table.hist.T.shape}") cb = plt.colorbar() cb.set_label(r"$\log_{10}(E/\mathrm{TeV})$") diff --git a/pyproject.toml b/pyproject.toml index e452ce67cc5..c0b8e248540 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,7 +18,6 @@ classifiers = [ "Intended Audience :: Science/Research", "License :: OSI Approved :: BSD License", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", @@ -28,7 +27,7 @@ classifiers = [ ] dynamic = ["version"] -requires-python = ">=3.9" +requires-python = ">=3.10" dependencies = [ "astropy >=5.3,<7.0.0a0", @@ -36,7 +35,6 @@ dependencies = [ "docutils", "eventio >=1.9.1, <2.0.0a0", "iminuit >=2", - "importlib_metadata; python_version < '3.10'", "joblib", "matplotlib ~=3.0", "numba >=0.56", @@ -193,7 +191,7 @@ relative_files = true [tool.ruff] -target-version = "py39" +target-version = "py310" line-length = 88 [tool.ruff.lint] diff --git a/sonar-project.properties b/sonar-project.properties index bcc86901d69..4d07b95df09 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -2,4 +2,4 @@ sonar.projectKey=ctapipe_github_9404f0f6-9294-11ed-bf3b-07aa00bdfe94 sonar.qualitygate.wait=true sonar.language=python sonar.python.coverage.reportPaths=coverage.xml -sonar.python.version=3.9 +sonar.python.version=3.10 diff --git a/src/ctapipe/atmosphere.py b/src/ctapipe/atmosphere.py index 579cf69b40a..884e03965a5 100644 --- a/src/ctapipe/atmosphere.py +++ b/src/ctapipe/atmosphere.py @@ -12,7 +12,6 @@ import abc from dataclasses import dataclass from functools import partial -from typing import Dict import numpy as np from astropy import units as u @@ -342,7 +341,7 @@ def __init__(self, table: Table): ] @classmethod - def from_array(cls, array: np.ndarray, metadata: Dict = None): + def from_array(cls, array: np.ndarray, metadata: dict = None): """construct from a 5x5 array as provided by eventio""" if metadata is None: diff --git a/src/ctapipe/coordinates/impact_distance.py b/src/ctapipe/coordinates/impact_distance.py index ec9cfd6af3e..19b99130d1d 100644 --- a/src/ctapipe/coordinates/impact_distance.py +++ b/src/ctapipe/coordinates/impact_distance.py @@ -1,12 +1,8 @@ -#!/usr/bin/env python3 """ Functions to compute the impact distance from a simulated or reconstructed shower axis (Defined by the line from the impact point on the ground in the reconstructed sky direction) to each telescope's ground position. """ - -from typing import Union - import numpy as np from astropy import units as u from astropy.coordinates import SkyCoord @@ -37,7 +33,7 @@ def impact_distance(point: np.ndarray, direction: np.ndarray, test_points: np.nd def shower_impact_distance( - shower_geom: Union[ReconstructedGeometryContainer, SimulatedShowerContainer], + shower_geom: ReconstructedGeometryContainer | SimulatedShowerContainer, subarray, ): """computes the impact distance of the shower axis to the telescope positions diff --git a/src/ctapipe/core/container.py b/src/ctapipe/core/container.py index 4e30c8e3b9a..70ce1cc66ea 100644 --- a/src/ctapipe/core/container.py +++ b/src/ctapipe/core/container.py @@ -389,7 +389,7 @@ def as_dict(self, recursive=False, flatten=False, add_prefix=False, add_key=Fals d = dict() for key, val in self.items(add_prefix=add_prefix): - if isinstance(val, (Container, Map)): + if isinstance(val, Container | Map): if flatten: d.update(val.as_dict(**kwargs)) else: diff --git a/src/ctapipe/core/plugins.py b/src/ctapipe/core/plugins.py index 46184a0a2fb..342bc03e734 100644 --- a/src/ctapipe/core/plugins.py +++ b/src/ctapipe/core/plugins.py @@ -1,12 +1,6 @@ """ctapipe plugin system""" import logging -import sys - -if sys.version_info < (3, 10): - from importlib_metadata import entry_points -else: - from importlib.metadata import entry_points - +from importlib.metadata import entry_points log = logging.getLogger(__name__) installed_entry_points = entry_points() diff --git a/src/ctapipe/core/telescope_component.py b/src/ctapipe/core/telescope_component.py index 60e1841dc15..4fed7be811a 100644 --- a/src/ctapipe/core/telescope_component.py +++ b/src/ctapipe/core/telescope_component.py @@ -5,7 +5,6 @@ import logging from collections import UserList from fnmatch import fnmatch -from typing import Optional, Union import numpy as np from traitlets import List, TraitError, TraitType, Undefined @@ -197,7 +196,7 @@ def attach_subarray(self, subarray): else: raise ValueError(f"Unrecognized command: {command}") - def __getitem__(self, tel: Optional[Union[int, str]]): + def __getitem__(self, tel: int | str | None): """ Returns the resolved parameter for the given telescope id """ @@ -213,7 +212,7 @@ def __getitem__(self, tel: Optional[Union[int, str]]): "`attach_subarray` first before trying to access a value by tel_id" ) - if isinstance(tel, (int, np.integer)): + if isinstance(tel, int | np.integer): try: return self._value_for_tel_id[tel] except KeyError: @@ -331,7 +330,7 @@ def _validate_entry(self, obj, value): def validate(self, obj, value): # Support a single value for all (check and convert into a default value) - if not isinstance(value, (list, List, UserList, TelescopePatternList)): + if not isinstance(value, list | List | UserList | TelescopePatternList): value = [("type", "*", self._validate_entry(obj, value))] # Check each value of list @@ -374,7 +373,7 @@ def validate(self, obj, value): def set(self, obj, value): # Support a single value for all (check and convert into a default value) - if not isinstance(value, (list, List, UserList, TelescopePatternList)): + if not isinstance(value, list | List | UserList | TelescopePatternList): value = [("type", "*", self._validate_entry(obj, value))] # Retain existing subarray description diff --git a/src/ctapipe/core/tool.py b/src/ctapipe/core/tool.py index aeb11873db1..9221ae9fb6e 100644 --- a/src/ctapipe/core/tool.py +++ b/src/ctapipe/core/tool.py @@ -11,7 +11,6 @@ from inspect import cleandoc from subprocess import CalledProcessError from tempfile import mkdtemp -from typing import Union import yaml from docutils.core import publish_parts @@ -258,7 +257,7 @@ def initialize(self, argv=None): self.log.info(f"ctapipe version {self.version_string}") - def load_config_file(self, path: Union[str, pathlib.Path]) -> None: + def load_config_file(self, path: str | pathlib.Path) -> None: """ Load a configuration file in one of the supported formats, and merge it with the current config if it exists. @@ -272,7 +271,7 @@ def load_config_file(self, path: Union[str, pathlib.Path]) -> None: if path.suffix in [".yaml", ".yml"]: # do our own YAML loading - with open(path, "r") as infile: + with open(path) as infile: config = Config(yaml.safe_load(infile)) self.update_config(config) elif path.suffix == ".toml" and HAS_TOML: diff --git a/src/ctapipe/core/traits.py b/src/ctapipe/core/traits.py index d9f704f8b60..ebbc6b46461 100644 --- a/src/ctapipe/core/traits.py +++ b/src/ctapipe/core/traits.py @@ -149,7 +149,7 @@ def validate(self, obj, value): else: self.error(obj, value) - if not isinstance(value, (str, pathlib.Path)): + if not isinstance(value, str | pathlib.Path): return self.error(obj, value) # expand any environment variables in the path: diff --git a/src/ctapipe/image/extractor.py b/src/ctapipe/image/extractor.py index 1b10ed79db4..d02280a50ca 100644 --- a/src/ctapipe/image/extractor.py +++ b/src/ctapipe/image/extractor.py @@ -22,8 +22,8 @@ from abc import abstractmethod +from collections.abc import Callable from functools import lru_cache -from typing import Callable, List, Optional, Tuple import astropy.units as u import numpy as np @@ -937,7 +937,7 @@ def _calculate_correction(self, tel_id, width, shift): def _apply_first_pass( self, waveforms, tel_id - ) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: + ) -> tuple[np.ndarray, np.ndarray, np.ndarray]: """ Execute step 1. @@ -1015,7 +1015,7 @@ def _apply_second_pass( pulse_time_1stpass, correction, broken_pixels, - ) -> Tuple[np.ndarray, np.ndarray]: + ) -> tuple[np.ndarray, np.ndarray]: """ Follow steps from 2 to 7. @@ -1299,8 +1299,8 @@ def deconvolution_parameters( window_shift: int, leading_edge_timing: bool, leading_edge_rel_descend_limit: float, - time_profile_pdf: Optional[Callable[[npt.ArrayLike], npt.ArrayLike]] = None, -) -> Tuple[List[float], List[float], List[float]]: + time_profile_pdf: Callable[[npt.ArrayLike], npt.ArrayLike] | None = None, +) -> tuple[list[float], list[float], list[float]]: """ Estimates deconvolution and recalibration parameters from the camera's reference single-p.e. pulse shape for the given configuration of FlashCamExtractor. diff --git a/src/ctapipe/image/invalid_pixels.py b/src/ctapipe/image/invalid_pixels.py index 94b922596a1..10d22cb50a0 100644 --- a/src/ctapipe/image/invalid_pixels.py +++ b/src/ctapipe/image/invalid_pixels.py @@ -2,7 +2,6 @@ Methods to interpolate broken pixels """ from abc import ABCMeta, abstractmethod -from typing import Tuple import numpy as np @@ -22,7 +21,7 @@ class InvalidPixelHandler(TelescopeComponent, metaclass=ABCMeta): @abstractmethod def __call__( self, tel_id, image, peak_time, pixel_mask - ) -> Tuple[np.ndarray, np.ndarray]: + ) -> tuple[np.ndarray, np.ndarray]: """ Handle invalid (broken, high noise, ...) pixels. diff --git a/src/ctapipe/instrument/camera/readout.py b/src/ctapipe/instrument/camera/readout.py index 3a886942d9e..48e0bf40cd9 100644 --- a/src/ctapipe/instrument/camera/readout.py +++ b/src/ctapipe/instrument/camera/readout.py @@ -209,7 +209,7 @@ def from_table(cls, url_or_table, **kwargs): version = tab.meta.get("TAB_VER", "") if version not in cls.SUPPORTED_TAB_VERSIONS: - raise IOError( + raise OSError( f"CameraReadout table has unsupported version: {version}," f" supported are: {cls.SUPPORTED_TAB_VERSIONS}." ) diff --git a/src/ctapipe/instrument/subarray.py b/src/ctapipe/instrument/subarray.py index a4191c178da..f4797aa7c1a 100644 --- a/src/ctapipe/instrument/subarray.py +++ b/src/ctapipe/instrument/subarray.py @@ -2,10 +2,10 @@ Description of Arrays or Subarrays of telescopes """ import warnings +from collections.abc import Iterable from contextlib import ExitStack from copy import copy from itertools import groupby -from typing import Dict, Iterable, Tuple, Union import numpy as np import tables @@ -81,10 +81,10 @@ def __init__( ---------- name : str name of this subarray - tel_positions : Dict[int, np.ndarray] + tel_positions : dict[int, np.ndarray] dict of x,y,z telescope positions on the ground by tel_id. These are converted internally to a coordinate in the `~ctapipe.coordinates.GroundFrame` - tel_descriptions : Dict[TelescopeDescription] + tel_descriptions : dict[TelescopeDescription] dict of TelescopeDescriptions by tel_id reference_location : `astropy.coordinates.EarthLocation` EarthLocation of the array reference position, (0, 0, 0) of the @@ -92,7 +92,7 @@ def __init__( """ self.name = name self.positions = tel_positions or dict() - self.tels: Dict[int, TelescopeDescription] = tel_descriptions or dict() + self.tels: dict[int, TelescopeDescription] = tel_descriptions or dict() self.reference_location = reference_location if self.positions.keys() != self.tels.keys(): @@ -107,7 +107,7 @@ def __repr__(self): ) @property - def tel(self) -> Dict[int, TelescopeDescription]: + def tel(self) -> dict[int, TelescopeDescription]: """Dictionary mapping tel_ids to TelescopeDescriptions""" return self.tels @@ -427,7 +427,7 @@ def peek(self, ax=None): return ad @lazyproperty - def telescope_types(self) -> Tuple[TelescopeDescription]: + def telescope_types(self) -> tuple[TelescopeDescription]: """ Tuple of unique telescope types in the array @@ -438,7 +438,7 @@ def telescope_types(self) -> Tuple[TelescopeDescription]: return tuple(sorted(unique_types, key=lambda tel: tel.name)) @lazyproperty - def camera_types(self) -> Tuple[CameraDescription]: + def camera_types(self) -> tuple[CameraDescription]: """ Tuple of unique camera types in the array @@ -449,7 +449,7 @@ def camera_types(self) -> Tuple[CameraDescription]: return tuple(sorted(unique_cameras, key=lambda camera: camera.name)) @lazyproperty - def optics_types(self) -> Tuple[OpticsDescription]: + def optics_types(self) -> tuple[OpticsDescription]: """ Tuple of unique optics types in the array @@ -459,7 +459,7 @@ def optics_types(self) -> Tuple[OpticsDescription]: unique_optics = {tel.optics for tel in self.tel.values()} return tuple(sorted(unique_optics, key=lambda optics: optics.name)) - def get_tel_ids_for_type(self, tel_type) -> Tuple[int]: + def get_tel_ids_for_type(self, tel_type) -> tuple[int]: """ return tuple of tel_ids that have the given tel_type @@ -520,8 +520,8 @@ def multiplicity(self, tel_mask, tel_type=None): ) def get_tel_ids( - self, telescopes: Iterable[Union[int, str, TelescopeDescription]] - ) -> Tuple[int]: + self, telescopes: Iterable[int | str | TelescopeDescription] + ) -> tuple[int]: """ Convert a list of telescope ids and telescope descriptions to a list of unique telescope ids. @@ -542,11 +542,11 @@ def get_tel_ids( ids = set() # support single telescope element - if isinstance(telescopes, (int, str, TelescopeDescription)): + if isinstance(telescopes, int | str | TelescopeDescription): telescopes = (telescopes,) for telescope in telescopes: - if isinstance(telescope, (int, np.integer)): + if isinstance(telescope, int | np.integer): if telescope not in self.tel: raise ValueError( f"Telescope with tel_id={telescope} not in subarray." @@ -607,7 +607,7 @@ def to_hdf(self, h5file, overwrite=False, mode="a"): h5file = stack.enter_context(tables.open_file(h5file, mode=mode)) if "/configuration/instrument/subarray" in h5file.root and not overwrite: - raise IOError( + raise OSError( "File already contains a SubarrayDescription and overwrite=False" ) @@ -661,7 +661,7 @@ def from_hdf(cls, path, focal_length_choice=FocalLengthKind.EFFECTIVE): version = layout.meta.get("TAB_VER") if version not in cls.COMPATIBLE_VERSIONS: - raise IOError(f"Unsupported version of subarray table: {version}") + raise OSError(f"Unsupported version of subarray table: {version}") cameras = {} @@ -690,7 +690,7 @@ def from_hdf(cls, path, focal_length_choice=FocalLengthKind.EFFECTIVE): optics_version = optics_table.meta.get("TAB_VER") if optics_version not in OpticsDescription.COMPATIBLE_VERSIONS: - raise IOError(f"Unsupported version of optics table: {optics_version}") + raise OSError(f"Unsupported version of optics table: {optics_version}") # for backwards compatibility # if optics_index not in table, guess via telescope_description string diff --git a/src/ctapipe/instrument/tests/__init__.py b/src/ctapipe/instrument/tests/__init__.py index 94607dd7af6..92e50e4cdb4 100644 --- a/src/ctapipe/instrument/tests/__init__.py +++ b/src/ctapipe/instrument/tests/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ Created on Wed Nov 18 18:32:25 2015 diff --git a/src/ctapipe/io/astropy_helpers.py b/src/ctapipe/io/astropy_helpers.py index 2b2040958ca..e8077e96895 100644 --- a/src/ctapipe/io/astropy_helpers.py +++ b/src/ctapipe/io/astropy_helpers.py @@ -81,7 +81,7 @@ def read_table( path = os.path.join("/", path) table = h5file.get_node(path) if not isinstance(table, tables.Table): - raise IOError( + raise OSError( f"Node {path} is a {table.__class__.__name__}, must be a Table" ) transforms, descriptions, meta = _parse_hdf5_attrs(table) @@ -163,7 +163,7 @@ def write_table( already_exists = False elif not overwrite and not append: - raise IOError( + raise OSError( f"Table {path} already exists in output file, use append or overwrite" ) diff --git a/src/ctapipe/io/datawriter.py b/src/ctapipe/io/datawriter.py index 637177a4eb5..eb353d43620 100644 --- a/src/ctapipe/io/datawriter.py +++ b/src/ctapipe/io/datawriter.py @@ -6,7 +6,6 @@ import pathlib from collections import defaultdict -from typing import List import numpy as np import tables @@ -83,7 +82,7 @@ def _get_tel_index(event, tel_id): def write_reference_metadata_headers( - obs_ids: List[int], + obs_ids: list[int], subarray: SubarrayDescription, writer: "DataWriter", is_simulation: bool, diff --git a/src/ctapipe/io/eventseeker.py b/src/ctapipe/io/eventseeker.py index 428460a3ce8..73df8f27fef 100644 --- a/src/ctapipe/io/eventseeker.py +++ b/src/ctapipe/io/eventseeker.py @@ -88,7 +88,7 @@ def _reset(self): Recreate the generator so it starts from the beginning """ if self._event_source.is_stream: - raise IOError("Back-seeking is not possible for event source") + raise OSError("Back-seeking is not possible for event source") self._source = self._event_source.__iter__() self._current_event = None diff --git a/src/ctapipe/io/eventsource.py b/src/ctapipe/io/eventsource.py index c9998320b31..b6edb9859fe 100644 --- a/src/ctapipe/io/eventsource.py +++ b/src/ctapipe/io/eventsource.py @@ -3,7 +3,7 @@ """ import warnings from abc import abstractmethod -from typing import Dict, Generator, List, Tuple +from collections.abc import Generator from traitlets.config.loader import LazyConfigValue @@ -215,19 +215,19 @@ def subarray(self) -> SubarrayDescription: """ @property - def simulation_config(self) -> Dict[int, SimulationConfigContainer]: + def simulation_config(self) -> dict[int, SimulationConfigContainer] | None: """The simulation configurations of all observations provided by the EventSource, or None if the source does not provide simulated data Returns ------- - Dict[int,ctapipe.containers.SimulationConfigContainer] | None + dict[int,ctapipe.containers.SimulationConfigContainer] | None """ return None @property @abstractmethod - def observation_blocks(self) -> Dict[int, ObservationBlockContainer]: + def observation_blocks(self) -> dict[int, ObservationBlockContainer]: """ Obtain the ObservationConfigurations from the EventSource, indexed by obs_id """ @@ -235,7 +235,7 @@ def observation_blocks(self) -> Dict[int, ObservationBlockContainer]: @property @abstractmethod - def scheduling_blocks(self) -> Dict[int, SchedulingBlockContainer]: + def scheduling_blocks(self) -> dict[int, SchedulingBlockContainer]: """ Obtain the ObservationConfigurations from the EventSource, indexed by obs_id """ @@ -255,7 +255,7 @@ def is_simulation(self) -> bool: @property @abstractmethod - def datalevels(self) -> Tuple[DataLevel]: + def datalevels(self) -> tuple[DataLevel]: """ The datalevels provided by this event source @@ -276,7 +276,7 @@ def has_any_datalevel(self, datalevels) -> bool: return any(dl in self.datalevels for dl in datalevels) @property - def obs_ids(self) -> List[int]: + def obs_ids(self) -> list[int]: """ The observation ids of the runs located in the file Unmerged files should only contain a single obs id. diff --git a/src/ctapipe/io/hdf5eventsource.py b/src/ctapipe/io/hdf5eventsource.py index f17041fe2b2..c270a612f12 100644 --- a/src/ctapipe/io/hdf5eventsource.py +++ b/src/ctapipe/io/hdf5eventsource.py @@ -1,7 +1,6 @@ import logging from contextlib import ExitStack from pathlib import Path -from typing import Dict, Union import numpy as np import tables @@ -77,7 +76,7 @@ ] -def get_hdf5_datalevels(h5file: Union[tables.File, str, Path]): +def get_hdf5_datalevels(h5file: tables.File | str | Path): """Get the data levels present in the hdf5 file""" datalevels = [] @@ -326,15 +325,15 @@ def obs_ids(self): return self._obs_ids @property - def scheduling_blocks(self) -> Dict[int, SchedulingBlockContainer]: + def scheduling_blocks(self) -> dict[int, SchedulingBlockContainer]: return self._scheduling_block @property - def observation_blocks(self) -> Dict[int, ObservationBlockContainer]: + def observation_blocks(self) -> dict[int, ObservationBlockContainer]: return self._observation_block @property - def simulation_config(self) -> Dict[int, SimulationConfigContainer]: + def simulation_config(self) -> dict[int, SimulationConfigContainer]: """ Returns the simulation config(s) as a dict mapping obs_id to the respective config. diff --git a/src/ctapipe/io/hdf5merger.py b/src/ctapipe/io/hdf5merger.py index 4650173c267..acf7322722d 100644 --- a/src/ctapipe/io/hdf5merger.py +++ b/src/ctapipe/io/hdf5merger.py @@ -3,7 +3,6 @@ import warnings from contextlib import ExitStack from pathlib import Path -from typing import Union import tables from astropy.time import Time @@ -204,7 +203,7 @@ def __init__(self, output_path=None, **kwargs): ) self.required_nodes = _get_required_nodes(self.h5file) - def __call__(self, other: Union[str, Path, tables.File]): + def __call__(self, other: str | Path | tables.File): """ Append file ``other`` to the output file """ diff --git a/src/ctapipe/io/hdf5tableio.py b/src/ctapipe/io/hdf5tableio.py index 8d695080480..988318a74ff 100644 --- a/src/ctapipe/io/hdf5tableio.py +++ b/src/ctapipe/io/hdf5tableio.py @@ -93,7 +93,7 @@ def get_column_attrs(table): key = full_key[len(prefix) :] # convert numpy scalars to plain python objects - if isinstance(value, (np.str_, np.bool_, np.number)): + if isinstance(value, np.str_ | np.bool_ | np.number): value = value.item() current[key] = value @@ -124,7 +124,7 @@ def _ignore_column_descriptions(attr_name): value = attrs[key] # convert numpy scalars to plain python objects - if isinstance(value, (np.str_, np.bool_, np.number)): + if isinstance(value, np.str_ | np.bool_ | np.number): value = value.item() meta[key] = value @@ -240,7 +240,7 @@ def __init__( self._tables = {} if mode not in ["a", "w", "r+"]: - raise IOError(f"The mode '{mode}' is not supported for writing") + raise OSError(f"The mode '{mode}' is not supported for writing") kwargs.update(mode=mode, root_uep=root_uep, filters=filters) @@ -603,7 +603,7 @@ def _map_table_to_containers( ) else: if col_name in cols_to_read: - raise IOError( + raise OSError( "Mapping of column names to container fields is not unique" ", prefixes are required." f" Duplicated column: {col_name} / {field_name}" diff --git a/src/ctapipe/io/metadata.py b/src/ctapipe/io/metadata.py index ba8513e0ff3..655d9b45a7a 100644 --- a/src/ctapipe/io/metadata.py +++ b/src/ctapipe/io/metadata.py @@ -343,4 +343,4 @@ def read_metadata(h5file, path="/"): metadata = {key: node._v_attrs[key] for key in node._v_attrs._f_list()} return metadata - raise IOError("Could not read metadata") + raise OSError("Could not read metadata") diff --git a/src/ctapipe/io/simteleventsource.py b/src/ctapipe/io/simteleventsource.py index f49245a7826..4cda9ae6b59 100644 --- a/src/ctapipe/io/simteleventsource.py +++ b/src/ctapipe/io/simteleventsource.py @@ -5,7 +5,6 @@ from gzip import GzipFile from io import BufferedReader from pathlib import Path -from typing import Dict, Optional, Union import numpy as np from astropy import units as u @@ -315,8 +314,8 @@ class AtmosphereProfileKind(Enum): def read_atmosphere_profile_from_simtel( - simtelfile: Union[str, Path, SimTelFile], kind=AtmosphereProfileKind.AUTO -) -> Optional[TableAtmosphereDensityProfile]: + simtelfile: str | Path | SimTelFile, kind=AtmosphereProfileKind.AUTO +) -> TableAtmosphereDensityProfile | None: """Read an atmosphere profile from a SimTelArray file as an astropy Table Parameters @@ -343,7 +342,7 @@ def read_atmosphere_profile_from_simtel( if kind == AtmosphereProfileKind.NONE: return None - if isinstance(simtelfile, (str, Path)): + if isinstance(simtelfile, str | Path): context_manager = SimTelFile(simtelfile) Provenance().add_input_file( filename=simtelfile, role="ctapipe.atmosphere.AtmosphereDensityProfile" @@ -519,7 +518,7 @@ def __init__(self, input_url=Undefined, config=None, parent=None, **kwargs): zcat=not self.back_seekable, ) if self.back_seekable and self.is_stream: - raise IOError("back seekable was required but not possible for inputfile") + raise OSError("back seekable was required but not possible for inputfile") ( self._scheduling_blocks, self._observation_blocks, @@ -555,7 +554,7 @@ def datalevels(self): return (DataLevel.R0, DataLevel.R1) @property - def simulation_config(self) -> Dict[int, SimulationConfigContainer]: + def simulation_config(self) -> dict[int, SimulationConfigContainer]: return self._simulation_config @property @@ -563,14 +562,14 @@ def atmosphere_density_profile(self) -> AtmosphereDensityProfile: return self._atmosphere_density_profile @property - def observation_blocks(self) -> Dict[int, ObservationBlockContainer]: + def observation_blocks(self) -> dict[int, ObservationBlockContainer]: """ Obtain the ObservationConfigurations from the EventSource, indexed by obs_id """ return self._observation_blocks @property - def scheduling_blocks(self) -> Dict[int, SchedulingBlockContainer]: + def scheduling_blocks(self) -> dict[int, SchedulingBlockContainer]: """ Obtain the ObservationConfigurations from the EventSource, indexed by obs_id """ @@ -578,7 +577,7 @@ def scheduling_blocks(self) -> Dict[int, SchedulingBlockContainer]: @property def is_stream(self): - return not isinstance(self.file_._filehandle, (BufferedReader, GzipFile)) + return not isinstance(self.file_._filehandle, BufferedReader | GzipFile) def prepare_subarray_info(self, telescope_descriptions, header): """ diff --git a/src/ctapipe/io/tableloader.py b/src/ctapipe/io/tableloader.py index fb151e8c1f8..74a3903064d 100644 --- a/src/ctapipe/io/tableloader.py +++ b/src/ctapipe/io/tableloader.py @@ -5,7 +5,6 @@ import warnings from collections import defaultdict, namedtuple from pathlib import Path -from typing import Dict import numpy as np import tables @@ -874,7 +873,7 @@ def read_telescope_events_by_type( instrument=None, observation_info=None, pointing=None, - ) -> Dict[str, Table]: + ) -> dict[str, Table]: """Read subarray-based event information. Parameters @@ -1016,7 +1015,7 @@ def read_telescope_events_by_id( instrument=None, observation_info=None, pointing=None, - ) -> Dict[int, Table]: + ) -> dict[int, Table]: """Read subarray-based event information. Parameters diff --git a/src/ctapipe/io/toymodel.py b/src/ctapipe/io/toymodel.py index 7e3683814a4..16270c4eef8 100644 --- a/src/ctapipe/io/toymodel.py +++ b/src/ctapipe/io/toymodel.py @@ -3,7 +3,6 @@ Create a toymodel event stream of array events """ import logging -from typing import Dict import astropy.units as u import numpy as np @@ -73,12 +72,12 @@ def datalevels(self): return (DataLevel.DL1_IMAGES,) @property - def scheduling_blocks(self) -> Dict[int, SchedulingBlockContainer]: + def scheduling_blocks(self) -> dict[int, SchedulingBlockContainer]: sb = SchedulingBlockContainer(producer_id="ctapipe toymodel") return {sb.sb_id: sb} @property - def observation_blocks(self) -> Dict[int, ObservationBlockContainer]: + def observation_blocks(self) -> dict[int, ObservationBlockContainer]: ob = ObservationBlockContainer(producer_id="ctapipe toymodel") return {ob.ob_id: ob} diff --git a/src/ctapipe/reco/preprocessing.py b/src/ctapipe/reco/preprocessing.py index c815dfb44ba..eb887ea3906 100644 --- a/src/ctapipe/reco/preprocessing.py +++ b/src/ctapipe/reco/preprocessing.py @@ -7,7 +7,6 @@ """ import logging import warnings -from typing import List import astropy.units as u import numpy as np @@ -57,7 +56,7 @@ def check_valid_rows(table: Table, warn=True, log=LOG) -> np.ndarray: return valid -def table_to_X(table: Table, features: List[str], log=LOG): +def table_to_X(table: Table, features: list[str], log=LOG): """ Extract features as numpy ndarray to be given to sklearn from input table dropping all events for which one or more training features are nan diff --git a/src/ctapipe/reco/sklearn.py b/src/ctapipe/reco/sklearn.py index 51928b6db76..24c565c4f03 100644 --- a/src/ctapipe/reco/sklearn.py +++ b/src/ctapipe/reco/sklearn.py @@ -4,7 +4,6 @@ import pathlib from abc import abstractmethod from collections import defaultdict -from typing import Dict import astropy.units as u import joblib @@ -203,7 +202,7 @@ def write(self, path, overwrite=False): path = pathlib.Path(path) if path.exists() and not overwrite: - raise IOError(f"Path {path} exists and overwrite=False") + raise OSError(f"Path {path} exists and overwrite=False") with path.open("wb") as f: Provenance().add_output_file(path, role="ml-models") @@ -415,7 +414,7 @@ def __call__(self, event: ArrayEventContainer) -> None: self.stereo_combiner(event) - def predict_table(self, key, table: Table) -> Dict[ReconstructionProperty, Table]: + def predict_table(self, key, table: Table) -> dict[ReconstructionProperty, Table]: table = self.feature_generator(table, subarray=self.subarray) n_rows = len(table) @@ -478,7 +477,7 @@ def __call__(self, event: ArrayEventContainer) -> None: self.stereo_combiner(event) - def predict_table(self, key, table: Table) -> Dict[ReconstructionProperty, Table]: + def predict_table(self, key, table: Table) -> dict[ReconstructionProperty, Table]: table = self.feature_generator(table, subarray=self.subarray) n_rows = len(table) @@ -650,7 +649,7 @@ def write(self, path, overwrite=False): path = pathlib.Path(path) if path.exists() and not overwrite: - raise IOError(f"Path {path} exists and overwrite=False") + raise OSError(f"Path {path} exists and overwrite=False") with path.open("wb") as f: Provenance().add_output_file(path, role="ml-models") @@ -778,7 +777,7 @@ def __call__(self, event: ArrayEventContainer) -> None: self.stereo_combiner(event) - def predict_table(self, key, table: Table) -> Dict[ReconstructionProperty, Table]: + def predict_table(self, key, table: Table) -> dict[ReconstructionProperty, Table]: """ Predict on a table of events. diff --git a/src/ctapipe/tests/test_traitles_configurable.py b/src/ctapipe/tests/test_traitles_configurable.py index 0b363512af5..a078a0cf762 100644 --- a/src/ctapipe/tests/test_traitles_configurable.py +++ b/src/ctapipe/tests/test_traitles_configurable.py @@ -46,7 +46,7 @@ def find_all_traitlets(module, missing_config=None): obj = getattr(submodule, obj_name) try: - has_traits = issubclass(obj, (Component, Tool)) + has_traits = issubclass(obj, Component | Tool) except TypeError: has_traits = False diff --git a/src/ctapipe/tools/dump_instrument.py b/src/ctapipe/tools/dump_instrument.py index 09f72309714..7f169acdefb 100644 --- a/src/ctapipe/tools/dump_instrument.py +++ b/src/ctapipe/tools/dump_instrument.py @@ -94,7 +94,7 @@ def write_camera_definitions(self): readout_table.write(readout_filename, **args) Provenance().add_output_file(geom_filename, "CameraGeometry") Provenance().add_output_file(readout_filename, "CameraReadout") - except IOError as err: + except OSError as err: self.log.warning("couldn't write camera definition because: %s", err) def write_optics_descriptions(self): @@ -108,7 +108,7 @@ def write_optics_descriptions(self): try: tab.write(filename, **args) Provenance().add_output_file(filename, "OpticsDescription") - except IOError as err: + except OSError as err: self.log.warning( "couldn't write optics description '%s' because: %s", filename, err ) @@ -122,7 +122,7 @@ def write_subarray_description(self): try: tab.write(filename, **args) Provenance().add_output_file(filename, "SubarrayDescription") - except IOError as err: + except OSError as err: self.log.warning( "couldn't write subarray description '%s' because: %s", filename, err ) diff --git a/src/ctapipe/tools/tests/test_train.py b/src/ctapipe/tools/tests/test_train.py index a58acb22924..acdb60925c3 100644 --- a/src/ctapipe/tools/tests/test_train.py +++ b/src/ctapipe/tools/tests/test_train.py @@ -104,12 +104,12 @@ def test_signal_fraction(tmp_path, gamma_train_clf, proton_train_clf): ], ) - with open(log_file, "r") as f: + with open(log_file) as f: log = f.readlines() for line in log[::-1]: if "Train on" in line: - n_signal, n_background = [int(line.split(" ")[i]) for i in (7, 10)] + n_signal, n_background = (int(line.split(" ")[i]) for i in (7, 10)) break assert np.allclose(n_signal / (n_signal + n_background), frac, atol=1e-4) diff --git a/src/ctapipe/tools/utils.py b/src/ctapipe/tools/utils.py index 2c6c3ff5bd9..5b1e468db93 100644 --- a/src/ctapipe/tools/utils.py +++ b/src/ctapipe/tools/utils.py @@ -3,9 +3,8 @@ import argparse import importlib import logging -import sys from collections import OrderedDict -from typing import Type +from importlib.metadata import distribution import numpy as np from astropy.table import vstack @@ -20,10 +19,6 @@ LOG = logging.getLogger(__name__) -if sys.version_info < (3, 10): - from importlib_metadata import distribution -else: - from importlib.metadata import distribution __all__ = [ "ArgparseFormatter", @@ -91,7 +86,7 @@ def read_training_events( loader: TableLoader, chunk_size: Int, telescope_type: TelescopeDescription, - reconstructor: Type[SKLearnReconstructor], + reconstructor: type[SKLearnReconstructor], feature_names: list, rng: np.random.Generator, log=LOG,