Skip to content

Commit

Permalink
Use modern type annotations
Browse files Browse the repository at this point in the history
  • Loading branch information
axelboc committed Aug 29, 2024
1 parent af9a9b9 commit c3fc2b8
Show file tree
Hide file tree
Showing 16 changed files with 106 additions and 82 deletions.
5 changes: 3 additions & 2 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(".."))

Expand Down Expand Up @@ -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 -------------------------------------------------
Expand Down
40 changes: 19 additions & 21 deletions h5grove/content.py
Original file line number Diff line number Diff line change
@@ -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

Expand Down Expand Up @@ -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(),))
Expand Down Expand Up @@ -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.
Expand All @@ -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
Expand Down Expand Up @@ -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,
):
"""
Expand Down Expand Up @@ -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)

Expand All @@ -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)

Expand Down
15 changes: 9 additions & 6 deletions h5grove/encoders.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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."""
Expand All @@ -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
Expand Down Expand Up @@ -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.
Expand Down
8 changes: 5 additions & 3 deletions h5grove/fastapi_utils.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
"""Helpers for usage with `FastAPI <https://fastapi.tiangolo.com/>`_"""

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,
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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:
Expand Down
8 changes: 5 additions & 3 deletions h5grove/flask_utils.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
"""Helpers for usage with `Flask <https://flask.palletsprojects.com/>`_"""

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,
Expand All @@ -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)
Expand Down
31 changes: 15 additions & 16 deletions h5grove/models.py
Original file line number Diff line number Diff line change
@@ -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[
Expand All @@ -22,6 +23,7 @@ class LinkResolution(str, Enum):
StrDtype = Union[str, Dict[str, "StrDtype"]] # type: ignore

# https://api.h5py.org/h5t.html
# Must use functional `TypedDict` syntax because of `class` key
TypeMetadata = TypedDict(
"TypeMetadata",
{
Expand Down Expand Up @@ -56,9 +58,10 @@ class SoftLinkMetadata(EntityMetadata):
target_path: str


AttributeMetadata = TypedDict(
"AttributeMetadata", {"name": str, "shape": tuple, "type": TypeMetadata}
)
class AttributeMetadata(TypedDict):
name: str
shape: tuple
type: TypeMetadata


class ResolvedEntityMetadata(EntityMetadata):
Expand All @@ -80,14 +83,10 @@ class DatatypeMetadata(ResolvedEntityMetadata):
type: TypeMetadata


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]],
},
)
class Stats(TypedDict):
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]
15 changes: 8 additions & 7 deletions h5grove/tornado_utils.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
"""Helpers for usage with `Tornado <https://www.tornadoweb.org>`_"""

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 (
Expand Down Expand Up @@ -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

Expand All @@ -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

Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
Loading

0 comments on commit c3fc2b8

Please sign in to comment.