diff --git a/docs/conf.py b/docs/conf.py
index c35f7bb..47e060b 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -10,9 +10,10 @@
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
+from __future__ import annotations
+
import os
import sys
-from typing import List
sys.path.insert(0, os.path.abspath(".."))
@@ -47,7 +48,7 @@
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path.
-exclude_patterns: List[str] = []
+exclude_patterns: list[str] = []
# -- Options for HTML output -------------------------------------------------
diff --git a/h5grove/content.py b/h5grove/content.py
index f5b520f..eaa9e4d 100644
--- a/h5grove/content.py
+++ b/h5grove/content.py
@@ -1,16 +1,14 @@
-import contextlib
-from pathlib import Path
+from __future__ import annotations
+from collections.abc import Callable, Sequence
from typing import (
Any,
- Callable,
- Dict,
Generic,
- Optional,
- Sequence,
TypeVar,
- Union,
cast,
)
+
+import contextlib
+from pathlib import Path
import h5py
import numpy as np
@@ -131,8 +129,8 @@ def __init__(self, path: str, h5py_entity: T):
"""Resolved h5py entity"""
def attributes(
- self, attr_keys: Optional[Sequence[str]] = None
- ) -> Dict[str, AttributeMetadata]:
+ self, attr_keys: Sequence[str] | None = None
+ ) -> dict[str, AttributeMetadata]:
"""Attributes of the h5py entity. Can be filtered by keys."""
if attr_keys is None:
return dict((*self._h5py_entity.attrs.items(),))
@@ -169,9 +167,9 @@ def metadata(self, depth=None) -> DatasetMetadata:
def data(
self,
- selection: Selection = None,
+ selection: Selection | None = None,
flatten: bool = False,
- dtype: Optional[str] = "origin",
+ dtype: str | None = "origin",
):
"""Dataset data.
@@ -189,7 +187,7 @@ def data(
return result
- def data_stats(self, selection: Selection = None) -> Stats:
+ def data_stats(self, selection: Selection | None = None) -> Stats:
"""Statistics on the data. Providing a selection will compute stats only on the selected slice.
:param selection: NumPy-like indexing to define a selection as a slice
@@ -254,7 +252,7 @@ def metadata(self, depth=None) -> DatatypeMetadata:
def create_content(
h5file: h5py.File,
- path: Optional[str],
+ path: str | None,
resolve_links: LinkResolution = LinkResolution.ONLY_VALID,
):
"""
@@ -293,11 +291,11 @@ def create_content(
@contextlib.contextmanager
def get_content_from_file(
- filepath: Union[str, Path],
- path: Optional[str],
+ filepath: str | Path,
+ path: str | None,
create_error: Callable[[int, str], Exception],
- resolve_links_arg: Optional[str] = LinkResolution.ONLY_VALID,
- h5py_options: Dict[str, Any] = {},
+ resolve_links_arg: str | None = LinkResolution.ONLY_VALID,
+ h5py_options: dict[str, Any] = {},
):
f = open_file_with_error_fallback(filepath, create_error, h5py_options)
@@ -322,11 +320,11 @@ def get_content_from_file(
@contextlib.contextmanager
def get_list_of_paths(
- filepath: Union[str, Path],
- base_path: Optional[str],
+ filepath: str | Path,
+ base_path: str | None,
create_error: Callable[[int, str], Exception],
- resolve_links_arg: Optional[str] = LinkResolution.ONLY_VALID,
- h5py_options: Dict[str, Any] = {},
+ resolve_links_arg: str | None = LinkResolution.ONLY_VALID,
+ h5py_options: dict[str, Any] = {},
):
f = open_file_with_error_fallback(filepath, create_error, h5py_options)
diff --git a/h5grove/encoders.py b/h5grove/encoders.py
index 12a1b44..7bb2455 100644
--- a/h5grove/encoders.py
+++ b/h5grove/encoders.py
@@ -1,5 +1,8 @@
+from __future__ import annotations
+from collections.abc import Callable
+from typing import Any
+
import io
-from typing import Any, Callable, Dict, Optional, Union
import numpy as np
import orjson
import h5py
@@ -17,7 +20,7 @@ def bin_encode(array: np.ndarray) -> bytes:
return array.tobytes()
-def orjson_default(o: Any) -> Union[list, float, str, None]:
+def orjson_default(o: Any) -> list | float | str | None:
"""Converts Python objects to JSON-serializable objects.
:raises TypeError: if the object is not supported."""
@@ -37,7 +40,7 @@ def orjson_default(o: Any) -> Union[list, float, str, None]:
raise TypeError
-def orjson_encode(content: Any, default: Optional[Callable] = None) -> bytes:
+def orjson_encode(content: Any, default: Callable | None = None) -> bytes:
"""Encode in JSON using orjson.
:param: content: Content to encode
@@ -82,15 +85,15 @@ def tiff_encode(data: np.ndarray) -> bytes:
class Response:
content: bytes
""" Encoded `content` as bytes """
- headers: Dict[str, str]
+ headers: dict[str, str]
""" Associated headers """
- def __init__(self, content: bytes, headers: Dict[str, str]):
+ def __init__(self, content: bytes, headers: dict[str, str]):
self.content = content
self.headers = {**headers, "Content-Length": str(len(content))}
-def encode(content: Any, encoding: Optional[str] = "json") -> Response:
+def encode(content: Any, encoding: str | None = "json") -> Response:
"""Encode content in given encoding.
Warning: Not all encodings supports all types of content.
diff --git a/h5grove/fastapi_utils.py b/h5grove/fastapi_utils.py
index 6271caf..27d0da1 100644
--- a/h5grove/fastapi_utils.py
+++ b/h5grove/fastapi_utils.py
@@ -1,9 +1,11 @@
"""Helpers for usage with `FastAPI `_"""
+from __future__ import annotations
+from collections.abc import Callable
+
from fastapi import APIRouter, Depends, Response, Query, Request
from fastapi.routing import APIRoute
from pydantic_settings import BaseSettings
-from typing import List, Optional, Union, Callable
from .content import (
DatasetContent,
@@ -46,7 +48,7 @@ async def custom_route_handler(request: Request) -> Response:
class Settings(BaseSettings):
- base_dir: Union[str, None] = None
+ base_dir: str | None = None
settings = Settings()
@@ -86,7 +88,7 @@ async def get_root():
async def get_attr(
file: str = Depends(add_base_path),
path: str = "/",
- attr_keys: Optional[List[str]] = Query(default=None),
+ attr_keys: list[str] | None = Query(default=None),
):
"""`/attr/` endpoint handler"""
with get_content_from_file(file, path, create_error) as content:
diff --git a/h5grove/flask_utils.py b/h5grove/flask_utils.py
index 68d3ece..dbfe0f2 100644
--- a/h5grove/flask_utils.py
+++ b/h5grove/flask_utils.py
@@ -1,10 +1,12 @@
"""Helpers for usage with `Flask `_"""
+from __future__ import annotations
+from collections.abc import Callable, Mapping
+from typing import Any
+
from werkzeug.exceptions import HTTPException
from flask import Blueprint, current_app, request, Response, Request
import os
-from typing import Any, Callable, Mapping, Optional
-
from .content import (
DatasetContent,
@@ -29,7 +31,7 @@
def make_encoded_response(
- content, format_arg: Optional[str] = "json", status: Optional[int] = None
+ content, format_arg: str | None = "json", status: int | None = None
) -> Response:
"""Prepare flask Response according to format"""
h5grove_response = encode(content, format_arg)
diff --git a/h5grove/models.py b/h5grove/models.py
index b506e3f..3537d68 100644
--- a/h5grove/models.py
+++ b/h5grove/models.py
@@ -1,6 +1,7 @@
+from __future__ import annotations
from enum import Enum
-from typing import Dict, Tuple, Union, List
-from typing_extensions import TypedDict, NotRequired, Optional
+from typing import Union, Tuple, Dict, List
+from typing_extensions import TypedDict, NotRequired
import h5py
H5pyEntity = Union[
@@ -83,11 +84,11 @@ class DatatypeMetadata(ResolvedEntityMetadata):
Stats = TypedDict(
"Stats",
{
- "strict_positive_min": Optional[Union[int, float]],
- "positive_min": Optional[Union[int, float]],
- "min": Optional[Union[int, float]],
- "max": Optional[Union[int, float]],
- "mean": Optional[Union[int, float]],
- "std": Optional[Union[int, float]],
+ "strict_positive_min": Union[int, float, None],
+ "positive_min": Union[int, float, None],
+ "min": Union[int, float, None],
+ "max": Union[int, float, None],
+ "mean": Union[int, float, None],
+ "std": Union[int, float, None],
},
)
diff --git a/h5grove/tornado_utils.py b/h5grove/tornado_utils.py
index 7d026bc..4256e8c 100644
--- a/h5grove/tornado_utils.py
+++ b/h5grove/tornado_utils.py
@@ -1,8 +1,9 @@
"""Helpers for usage with `Tornado `_"""
-import os
-from typing import Any, Optional
+from __future__ import annotations
+from typing import Any
+import os
from tornado.web import HTTPError, MissingArgumentError, RequestHandler
from .content import (
@@ -33,7 +34,7 @@ def create_error(status_code: int, message: str):
class BaseHandler(RequestHandler):
"""Base class for h5grove handlers"""
- def initialize(self, base_dir: str, allow_origin: Optional[str] = None) -> None:
+ def initialize(self, base_dir: str, allow_origin: str | None = None) -> None:
self.base_dir = base_dir
self.allow_origin = allow_origin
@@ -57,7 +58,7 @@ def get(self):
self.finish()
def get_response(
- self, full_file_path: str, path: Optional[str], resolve_links: Optional[str]
+ self, full_file_path: str, path: str | None, resolve_links: str | None
) -> Response:
raise NotImplementedError
@@ -82,7 +83,7 @@ def head(self):
class ContentHandler(BaseHandler):
def get_response(
- self, full_file_path: str, path: Optional[str], resolve_links: Optional[str]
+ self, full_file_path: str, path: str | None, resolve_links: str | None
) -> Response:
with get_content_from_file(
full_file_path, path, create_error, resolve_links
@@ -141,7 +142,7 @@ def get_content_response(self, content: EntityContent) -> Response:
class PathsHandler(BaseHandler):
def get_response(
- self, full_file_path: str, path: Optional[str], resolve_links: Optional[str]
+ self, full_file_path: str, path: str | None, resolve_links: str | None
) -> Response:
with get_list_of_paths(
full_file_path, path, create_error, resolve_links
@@ -150,7 +151,7 @@ def get_response(
# TODO: Setting the return type raises mypy errors
-def get_handlers(base_dir: Optional[str], allow_origin: Optional[str] = None):
+def get_handlers(base_dir: str | None, allow_origin: str | None = None):
"""Build h5grove handlers (`/`, `/attr/`, `/data/`, `/meta/` and `/stats/`).
:param base_dir: Base directory from which the HDF5 files will be served
diff --git a/h5grove/utils.py b/h5grove/utils.py
index 56ef031..e3094e9 100644
--- a/h5grove/utils.py
+++ b/h5grove/utils.py
@@ -1,9 +1,12 @@
+from __future__ import annotations
+from collections.abc import Callable
+from typing import Any, TypeVar
+
from pathlib import Path
import h5py
from h5py.version import version_tuple as h5py_version
from os.path import basename
import numpy as np
-from typing import Any, Callable, Dict, List, Optional, Tuple, TypeVar, Union
from .models import (
H5pyEntity,
@@ -89,7 +92,7 @@ def get_entity_from_file(
return h5file[path]
-def parse_slice(slice_str: str) -> Tuple[Union[slice, int], ...]:
+def parse_slice(slice_str: str) -> tuple[slice | int, ...]:
"""
Parses a string containing a slice under NumPy format.
@@ -108,7 +111,7 @@ def parse_slice(slice_str: str) -> Tuple[Union[slice, int], ...]:
return tuple(parse_slice_member(s) for s in slice_members)
-def parse_slice_member(slice_member: str) -> Union[slice, int]:
+def parse_slice_member(slice_member: str) -> slice | int:
if ":" not in slice_member:
return int(slice_member)
@@ -132,7 +135,7 @@ def parse_slice_member(slice_member: str) -> Union[slice, int]:
raise TypeError(f"{slice_member} is not a valid slice")
-def sorted_dict(*args: Tuple[str, Any]):
+def sorted_dict(*args: tuple[str, Any]):
return dict(sorted(args, key=lambda entry: entry[0]))
@@ -229,7 +232,7 @@ def _sanitize_dtype(dtype: np.dtype) -> np.dtype:
T = TypeVar("T", np.ndarray, np.number, np.bool_)
-def convert(data: T, dtype: Optional[str] = "origin") -> T:
+def convert(data: T, dtype: str | None = "origin") -> T:
"""Convert array or numpy scalar to given dtype query param
:param data: nD array or scalar to convert
@@ -251,7 +254,7 @@ def convert(data: T, dtype: Optional[str] = "origin") -> T:
raise QueryArgumentError(f"Unsupported dtype {dtype}")
-def is_numeric_data(data: Union[np.ndarray, np.number, np.bool_, bytes]) -> bool:
+def is_numeric_data(data: np.ndarray | np.number | np.bool_ | bytes) -> bool:
if not isinstance(data, (np.ndarray, np.number, np.bool_)):
return False
@@ -288,14 +291,14 @@ def get_array_stats(data: np.ndarray) -> Stats:
}
-def hdf_path_join(prefix: Union[str, None], suffix: str):
+def hdf_path_join(prefix: str | None, suffix: str):
if prefix is None or prefix == "/":
return f"/{suffix}"
return f'{prefix.rstrip("/")}/{suffix}'
-def parse_bool_arg(query_arg: Union[str, None], fallback: bool) -> bool:
+def parse_bool_arg(query_arg: str | None, fallback: bool) -> bool:
if query_arg is None:
return fallback
@@ -303,7 +306,7 @@ def parse_bool_arg(query_arg: Union[str, None], fallback: bool) -> bool:
def parse_link_resolution_arg(
- raw_query_arg: Union[str, None], fallback: LinkResolution
+ raw_query_arg: str | None, fallback: LinkResolution
) -> LinkResolution:
if raw_query_arg is None:
return fallback
@@ -342,7 +345,7 @@ def get_dataset_slice(dataset: h5py.Dataset, selection: Selection):
def get_filters(
dataset: h5py.Dataset,
-) -> Optional[List[Dict[str, Union[int, str]]]]:
+) -> list[dict[str, int | str]] | None:
property_list = dataset.id.get_create_plist()
n_filters = property_list.get_nfilters()
@@ -353,8 +356,8 @@ def get_filters(
def get_filter_info(
- filter: Tuple[int, int, Tuple[int, ...], str]
-) -> Dict[str, Union[int, str]]:
+ filter: tuple[int, int, tuple[int, ...], str]
+) -> dict[str, int | str]:
# https://api.h5py.org/h5p.html#h5py.h5p.PropDCID.get_filter
(filter_id, _, _, name) = filter
@@ -371,9 +374,9 @@ def stringify_dtype(dtype: np.dtype) -> StrDtype:
def open_file_with_error_fallback(
- filepath: Union[str, Path],
+ filepath: str | Path,
create_error: Callable[[int, str], Exception],
- h5py_options: Dict[str, Any] = {},
+ h5py_options: dict[str, Any] = {},
) -> h5py.File:
try:
f = h5py.File(filepath, "r", **h5py_options)
diff --git a/test/base_test.py b/test/base_test.py
index df6d60d..24f2225 100644
--- a/test/base_test.py
+++ b/test/base_test.py
@@ -1,8 +1,10 @@
"""Base class for testing with different servers"""
+from __future__ import annotations
+from collections.abc import Generator
+
import os
import stat
-from typing import Generator
from urllib.parse import urlencode
import h5py
diff --git a/test/conftest.py b/test/conftest.py
index ce6c6dd..e031abb 100644
--- a/test/conftest.py
+++ b/test/conftest.py
@@ -1,10 +1,12 @@
+from __future__ import annotations
+from collections.abc import Callable
+
import os
import pathlib
import socketserver
import subprocess
import sys
import time
-from typing import Callable, Optional
from urllib.request import urlopen
from urllib.error import HTTPError
@@ -31,7 +33,7 @@ def _get_response(self, url: str, benchmark: Callable) -> Response:
def get(
self,
url: str,
- benchmark: Optional[Callable] = None,
+ benchmark: Callable | None = None,
) -> Response:
"""Request url and return retrieved response"""
if benchmark is None:
diff --git a/test/test_benchmark_data.py b/test/test_benchmark_data.py
index ae75af1..213cd26 100644
--- a/test/test_benchmark_data.py
+++ b/test/test_benchmark_data.py
@@ -1,7 +1,9 @@
"""Benchmark data requests with server apps in example/ folder"""
+from __future__ import annotations
+from collections.abc import Generator
+
import pathlib
-from typing import Generator
from urllib.parse import urlencode
import h5py
import numpy as np
diff --git a/test/test_fastapi.py b/test/test_fastapi.py
index 4fb7e46..71b7013 100644
--- a/test/test_fastapi.py
+++ b/test/test_fastapi.py
@@ -1,7 +1,9 @@
"""Test fastapi_utils with fastapi testing"""
+from __future__ import annotations
+from collections.abc import Callable
+
import pathlib
-from typing import Callable
from fastapi import FastAPI
from fastapi.testclient import TestClient
import pytest
diff --git a/test/test_flask.py b/test/test_flask.py
index 75b4500..ddec322 100644
--- a/test/test_flask.py
+++ b/test/test_flask.py
@@ -1,7 +1,9 @@
"""Test flask_utils blueprint with Flask testing"""
+from __future__ import annotations
+from collections.abc import Callable
+
import pathlib
-from typing import Callable
from flask import Flask
import pytest
diff --git a/test/test_tornado.py b/test/test_tornado.py
index 628a2a8..7de8fc5 100644
--- a/test/test_tornado.py
+++ b/test/test_tornado.py
@@ -1,7 +1,9 @@
"""Test tornado_utils using pytest-tornado"""
+from __future__ import annotations
+from collections.abc import Callable
+
import pathlib
-from typing import Callable
import pytest
from tornado.httpclient import HTTPClientError
import tornado.web
diff --git a/test/utils.py b/test/utils.py
index fcedcf2..124eb4d 100644
--- a/test/utils.py
+++ b/test/utils.py
@@ -1,7 +1,9 @@
+from __future__ import annotations
+from typing import NamedTuple
+
import io
import json
import numpy as np
-from typing import List, NamedTuple, Tuple
import tifffile
@@ -12,7 +14,7 @@ class Response(NamedTuple):
"""Return type of :meth:`get`"""
status: int
- headers: List[Tuple[str, str]]
+ headers: list[tuple[str, str]]
content: bytes
def find_header_value(self, key: str):
@@ -55,7 +57,7 @@ def decode_array_response(
response: Response,
format: str,
dtype: str,
- shape: Tuple[int, ...],
+ shape: tuple[int, ...],
) -> np.ndarray:
"""Decode data array response content according to given information"""
content_type = response.find_header_value("content-type")