From 7b4c8f37fd327aa40516efc9ce5337557acce6ce Mon Sep 17 00:00:00 2001 From: Matthias Diener Date: Sun, 22 Dec 2024 11:50:35 +0100 Subject: [PATCH] bump minimum Python to 3.10 --- .github/workflows/ci.yaml | 4 +- examples/log-mpi.py | 3 +- examples/optional-log.py | 3 +- logpyle/HTMLalyzer/main.py | 2 +- logpyle/__init__.py | 164 +++++++++++++++++------------------- logpyle/runalyzer.py | 49 +++++------ logpyle/runalyzer_gather.py | 34 ++++---- pyproject.toml | 3 +- test/test_quantities.py | 14 +-- 9 files changed, 128 insertions(+), 148 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 457d14b..3bd134a 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -32,7 +32,7 @@ jobs: strategy: fail-fast: true matrix: - python-version: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12', '3.x'] + python-version: ['3.10', '3.11', '3.12', '3.x'] os: [ubuntu-latest, macos-13] steps: @@ -175,6 +175,8 @@ jobs: python-version: '3.x' - name: "Main Script" run: | + set -x + sudo apt-get update && sudo apt-get install -y libopenmpi-dev curl -L -O -k https://gitlab.tiker.net/inducer/ci-support/raw/main/prepare-and-run-mypy.sh export EXTRA_INSTALL="pytools numpy types-psutil pymbolic mpi4py matplotlib pylab" . ./prepare-and-run-mypy.sh python3 diff --git a/examples/log-mpi.py b/examples/log-mpi.py index e68ef2b..232b71e 100755 --- a/examples/log-mpi.py +++ b/examples/log-mpi.py @@ -1,9 +1,10 @@ #!/usr/bin/env python3 import logging +from collections.abc import Callable from random import uniform from time import sleep -from typing import Any, Callable +from typing import Any from warnings import warn from mpi4py import MPI diff --git a/examples/optional-log.py b/examples/optional-log.py index 6739159..af30635 100755 --- a/examples/optional-log.py +++ b/examples/optional-log.py @@ -3,7 +3,6 @@ from contextlib import nullcontext from random import uniform from time import sleep -from typing import Union from logpyle import ( IntervalTimer, @@ -32,7 +31,7 @@ def main(use_logpyle: bool) -> None: # noqa: C901 if logmgr: vis_timer = IntervalTimer("t_vis", "Time spent visualizing") logmgr.add_quantity(vis_timer) - time_vis: Union[_SubTimer, nullcontext[None]] = vis_timer.get_sub_timer() + time_vis: _SubTimer | nullcontext[None] = vis_timer.get_sub_timer() else: time_vis = nullcontext() diff --git a/logpyle/HTMLalyzer/main.py b/logpyle/HTMLalyzer/main.py index ce924f0..2dd5060 100644 --- a/logpyle/HTMLalyzer/main.py +++ b/logpyle/HTMLalyzer/main.py @@ -265,7 +265,7 @@ async def store_file(event: Any) -> None: cursor = run_db.db.execute("select * from runs") columns = [col[0] for col in cursor.description] vals = list(next(iter(cursor))) - for (col, val) in zip(columns, vals): + for (col, val) in zip(columns, vals, strict=False): file_dict[id].constants[col] = val # extract quantities from sqlite file diff --git a/logpyle/__init__.py b/logpyle/__init__.py index ac2527c..a9b1074 100644 --- a/logpyle/__init__.py +++ b/logpyle/__init__.py @@ -63,33 +63,21 @@ THE SOFTWARE. """ -try: - import importlib.metadata as importlib_metadata -except ModuleNotFoundError: # pragma: no cover - # Python 3.7 - import importlib_metadata # type: ignore[no-redef] +import importlib -__version__ = importlib_metadata.version(__package__ or __name__) +__version__ = importlib.metadata.version(__package__ or __name__) import logging import sys +from collections.abc import Callable, Generator, Iterable, Sequence from dataclasses import dataclass from sqlite3 import Connection from time import monotonic as time_monotonic from typing import ( TYPE_CHECKING, Any, - Callable, - Dict, - Generator, - Iterable, - List, Optional, - Sequence, TextIO, - Tuple, - Type, - Union, cast, ) @@ -118,8 +106,8 @@ class LogQuantity: sort_weight = 0 - def __init__(self, name: str, unit: Optional[str] = None, - description: Optional[str] = None) -> None: + def __init__(self, name: str, unit: str | None = None, + description: str | None = None) -> None: """Create a new quantity. Parameters @@ -186,9 +174,9 @@ class MultiLogQuantity: """ sort_weight = 0 - def __init__(self, names: List[str], - units: Optional[Sequence[Optional[str]]] = None, - descriptions: Optional[Sequence[Optional[str]]] = None) -> None: + def __init__(self, names: list[str], + units: Sequence[str | None] | None = None, + descriptions: Sequence[str | None] | None = None) -> None: """Create a new quantity. Parameters @@ -205,17 +193,17 @@ def __init__(self, names: List[str], self.names = names if units is None: - self.units: Sequence[Optional[str]] = len(names) * [None] + self.units: Sequence[str | None] = len(names) * [None] else: self.units = units if descriptions is None: - self.descriptions: Sequence[Optional[str]] = len(names) * [None] + self.descriptions: Sequence[str | None] = len(names) * [None] else: self.descriptions = descriptions @property - def default_aggregators(self) -> List[None]: + def default_aggregators(self) -> list[None]: """List of default aggregators.""" return [None] * len(self.names) @@ -223,7 +211,7 @@ def tick(self) -> None: """Perform updates required at every :class:`LogManager` tick.""" pass - def __call__(self) -> Iterable[Optional[float]]: + def __call__(self) -> Iterable[float | None]: """Return an iterable of the current values of the diagnostic represented by this :class:`MultiLogQuantity`. @@ -248,9 +236,9 @@ class MultiPostLogQuantity(MultiLogQuantity, PostLogQuantity): class DtConsumer: def __init__(self) -> None: - self.dt: Optional[float] = None + self.dt: float | None = None - def set_dt(self, dt: Optional[float]) -> None: + def set_dt(self, dt: float | None) -> None: self.dt = dt @@ -266,24 +254,24 @@ def tick(self) -> None: class SimulationLogQuantity(PostLogQuantity, DtConsumer): """A source of loggable scalars that needs to know the simulation timestep.""" - def __init__(self, name: str, unit: Optional[str] = None, - description: Optional[str] = None) -> None: + def __init__(self, name: str, unit: str | None = None, + description: str | None = None) -> None: PostLogQuantity.__init__(self, name, unit, description) DtConsumer.__init__(self) class PushLogQuantity(LogQuantity): - def __init__(self, name: str, unit: Optional[str] = None, - description: Optional[str] = None) -> None: + def __init__(self, name: str, unit: str | None = None, + description: str | None = None) -> None: LogQuantity.__init__(self, name, unit, description) - self.value: Optional[float] = None + self.value: float | None = None def push_value(self, value: float) -> None: if self.value is not None: raise RuntimeError("can't push two values per cycle") self.value = value - def __call__(self) -> Optional[float]: + def __call__(self) -> float | None: v = self.value self.value = None return v @@ -292,7 +280,7 @@ def __call__(self) -> Optional[float]: class CallableLogQuantityAdapter(LogQuantity): """Adapt a 0-ary callable as a :class:`LogQuantity`.""" def __init__(self, callable: Callable[[], float], name: str, - unit: Optional[str] = None, description: Optional[str] = None) \ + unit: str | None = None, description: str | None = None) \ -> None: self.callable = callable LogQuantity.__init__(self, name, unit, description) @@ -313,13 +301,13 @@ class _GatherDescriptor: @dataclass(frozen=True) class _QuantityData: - unit: Optional[str] - description: Optional[str] - default_aggregator: Optional[Callable[..., Any]] + unit: str | None + description: str | None + default_aggregator: Callable[..., Any] | None -def _join_by_first_of_tuple(list_of_iterables: List[Iterable[Any]]) \ - -> Generator[Tuple[int, List[Any]], None, None]: +def _join_by_first_of_tuple(list_of_iterables: list[Iterable[Any]]) \ + -> Generator[tuple[int, list[Any]], None, None]: loi = [i.__iter__() for i in list_of_iterables] if not loi: return @@ -420,16 +408,16 @@ class _DependencyData: varname: str expr: ExpressionNode nonlocal_agg: bool - table: Optional[DataTable] = None + table: DataTable | None = None @dataclass class _WatchInfo: parsed: ExpressionNode expr: ExpressionNode - dep_data: List[_DependencyData] + dep_data: list[_DependencyData] compiled: CompiledExpression - unit: Optional[str] + unit: str | None format: str @@ -504,7 +492,7 @@ class LogManager: .. automethod:: tick_after """ - def __init__(self, filename: Optional[str] = None, mode: str = "r", # noqa: C901 + def __init__(self, filename: str | None = None, mode: str = "r", # noqa: C901 mpi_comm: Optional["mpi4py.MPI.Comm"] = None, capture_warnings: bool = True, watch_interval: float = 1.0, @@ -534,13 +522,13 @@ def __init__(self, filename: Optional[str] = None, mode: str = "r", # noqa: C90 assert isinstance(mode, str), "mode must be a string" assert mode in ["w", "r", "wu", "wo"], "invalid mode" - self.quantity_data: Dict[str, _QuantityData] = {} - self.last_values: Dict[str, Optional[float]] = {} - self.before_gather_descriptors: List[_GatherDescriptor] = [] - self.after_gather_descriptors: List[_GatherDescriptor] = [] + self.quantity_data: dict[str, _QuantityData] = {} + self.last_values: dict[str, float | None] = {} + self.before_gather_descriptors: list[_GatherDescriptor] = [] + self.after_gather_descriptors: list[_GatherDescriptor] = [] self.tick_count = 0 - self.constants: Dict[str, object] = {} + self.constants: dict[str, object] = {} self.last_save_time = time_monotonic() @@ -563,7 +551,7 @@ def __init__(self, filename: Optional[str] = None, mode: str = "r", # noqa: C90 self.weakref_finalize: Callable[..., Any] = lambda: None # watch stuff - self.watches: List[_WatchInfo] = [] + self.watches: list[_WatchInfo] = [] self.have_nonlocal_watches = False # Interval between printing watches, in seconds @@ -572,7 +560,7 @@ def __init__(self, filename: Optional[str] = None, mode: str = "r", # noqa: C90 # database binding import sqlite3 as sqlite - self.sqlite_filename: Optional[str] = None + self.sqlite_filename: str | None = None if filename is None: file_base = ":memory:" file_extension = "" @@ -652,13 +640,13 @@ def __init__(self, filename: Optional[str] = None, mode: str = "r", # noqa: C90 # {{{ warnings/logging capture - self.warning_data: List[_LogWarningInfo] = [] - self.old_showwarning: Optional[Callable[..., Any]] = None + self.warning_data: list[_LogWarningInfo] = [] + self.old_showwarning: Callable[..., Any] | None = None if capture_warnings and self.mode[0] == "w": self.capture_warnings(True) - self.logging_data: List[_LogWarningInfo] = [] - self.logging_handler: Optional[logging.Handler] = None + self.logging_data: list[_LogWarningInfo] = [] + self.logging_handler: logging.Handler | None = None if capture_logging and self.mode[0] == "w": self.capture_logging(True) @@ -685,7 +673,7 @@ def __init__(self, filename: Optional[str] = None, mode: str = "r", # noqa: C90 def __del__(self) -> None: self.weakref_finalize() - def enable_save_on_sigterm(self) -> Union[Callable[..., Any], int, None]: + def enable_save_on_sigterm(self) -> Callable[..., Any] | int | None: """Enable saving the log on SIGTERM. :returns: The previous SIGTERM handler. @@ -704,9 +692,9 @@ def sighndl(_signo: int, _stackframe: Any) -> None: def capture_warnings(self, enable: bool = True) -> None: """Enable or disable :mod:`warnings` capture.""" - def _showwarning(message: Union[Warning, str], category: Type[Warning], - filename: str, lineno: int, file: Optional[TextIO] = None, - line: Optional[str] = None) -> None: + def _showwarning(message: Warning | str, category: type[Warning], + filename: str, lineno: int, file: TextIO | None = None, + line: str | None = None) -> None: assert self.old_showwarning self.old_showwarning(message, category, filename, lineno, file, line) @@ -863,7 +851,7 @@ def get_warnings(self) -> DataTable: return result - def add_watches(self, watches: List[Union[str, Tuple[str, str]]]) -> None: + def add_watches(self, watches: list[str | tuple[str, str]]) -> None: """Add quantities that are printed after every time step. :arg watches: @@ -931,7 +919,7 @@ def set_constant(self, name: str, value: Any) -> None: self.db_conn.execute("insert into constants values (?,?)", (name, value)) - def _insert_datapoint(self, name: str, value: Optional[float]) -> None: + def _insert_datapoint(self, name: str, value: float | None) -> None: if value is None: return @@ -961,7 +949,7 @@ def _gather_for_descriptor(self, gd: _GatherDescriptor) -> None: if self.tick_count % gd.interval == 0: q_value = gd.quantity() if isinstance(gd.quantity, MultiLogQuantity): - for name, value in zip(gd.quantity.names, q_value): + for name, value in zip(gd.quantity.names, q_value, strict=False): self._insert_datapoint(name, value) else: self._insert_datapoint(gd.quantity.name, q_value) @@ -1066,8 +1054,8 @@ def add_quantity(self, quantity: LogQuantity, interval: int = 1) -> None: :arg interval: interval (in time steps) when to gather this quantity. """ - def add_internal(name: str, unit: Optional[str], description: Optional[str], - def_agg: Optional[Callable[..., Any]]) -> None: + def add_internal(name: str, unit: str | None, description: str | None, + def_agg: Callable[..., Any] | None) -> None: logger.debug(f"adding log quantity '{name}'") if name in self.quantity_data: @@ -1095,7 +1083,7 @@ def add_internal(name: str, unit: Optional[str], description: Optional[str], quantity.names, quantity.units, quantity.descriptions, - quantity.default_aggregators): + quantity.default_aggregators, strict=False): add_internal(name, unit, description, def_agg) else: add_internal(quantity.name, @@ -1105,10 +1093,10 @@ def add_internal(name: str, unit: Optional[str], description: Optional[str], self.save() def get_expr_dataset(self, expression: ExpressionNode, - description: Optional[str] = None, - unit: Optional[str] = None) \ - -> Tuple[Union[str, Any], Union[str, Any], - List[Tuple[int, Any]]]: + description: str | None = None, + unit: str | None = None) \ + -> tuple[str | Any, str | Any, + list[tuple[int, Any]]]: """Prepare a time-series dataset for a given expression. :arg expression: A :mod:`pymbolic` expression that may involve @@ -1165,7 +1153,7 @@ def get_expr_dataset(self, expression: ExpressionNode, return (description, unit, data) - def get_joint_dataset(self, expressions: Sequence[ExpressionNode]) -> List[Any]: + def get_joint_dataset(self, expressions: Sequence[ExpressionNode]) -> list[Any]: """Return a joint data set for a list of expressions. :arg expressions: a list of either strings representing @@ -1192,16 +1180,16 @@ def get_joint_dataset(self, expressions: Sequence[ExpressionNode]) -> List[Any]: dubs.append(dub) - zipped_dubs = list(zip(*dubs)) + zipped_dubs = list(zip(*dubs, strict=False)) zipped_dubs[2] = list( _join_by_first_of_tuple(zipped_dubs[2])) return zipped_dubs def get_plot_data(self, expr_x: ExpressionNode, expr_y: ExpressionNode, - min_step: Optional[int] = None, - max_step: Optional[int] = None) \ - -> Tuple[Tuple[Any, str, str], Tuple[Any, str, str]]: + min_step: int | None = None, + max_step: int | None = None) \ + -> tuple[tuple[Any, str, str], tuple[Any, str, str]]: """Generate plot-ready data. :returns: ``(data_x, descr_x, unit_x), (data_y, descr_y, unit_y)`` @@ -1216,7 +1204,7 @@ def get_plot_data(self, expr_x: ExpressionNode, expr_y: ExpressionNode, stepless_data = [tup for _step, tup in data] if stepless_data: - data_x, data_y = list(zip(*stepless_data)) + data_x, data_y = list(zip(*stepless_data, strict=False)) else: data_x = () data_y = () @@ -1231,7 +1219,7 @@ def write_datafile(self, filename: str, expr_x: ExpressionNode, outf = open(filename, "w") outf.write(f"# {label_x} vs. {label_y}\n") - for dx, dy in zip(data_x, data_y): + for dx, dy in zip(data_x, data_y, strict=False): outf.write(f"{dx!r}\t{dy!r}\n") outf.close() @@ -1258,12 +1246,12 @@ def _parse_expr(self, expr: str) -> Any: def _get_expr_dep_data(self, # noqa: C901 parsed: ExpressionNode) \ - -> Tuple[ExpressionNode, List[_DependencyData]]: + -> tuple[ExpressionNode, list[_DependencyData]]: class Nth: def __init__(self, n: int) -> None: self.n = n - def __call__(self, lst: List[Any]) -> Any: + def __call__(self, lst: list[Any]) -> Any: return lst[self.n] import pymbolic.mapper.dependency as pmd @@ -1366,7 +1354,7 @@ def _watch_tick(self) -> None: if self.rank == self.head_rank: assert gathered_data - values: Dict[str, List[Optional[float]]] = {} + values: dict[str, list[float | None]] = {} for data_block in gathered_data: for name, value in data_block.items(): values.setdefault(name, []).append(value) @@ -1436,7 +1424,7 @@ class IntervalTimer(PostLogQuantity): .. automethod:: add_time """ - def __init__(self, name: str, description: Optional[str] = None) -> None: + def __init__(self, name: str, description: str | None = None) -> None: LogQuantity.__init__(self, name, "s", description) self.elapsed: float = 0 @@ -1482,7 +1470,7 @@ class EventCounter(PostLogQuantity): """ def __init__(self, name: str = "interval", - description: Optional[str] = None) -> None: + description: str | None = None) -> None: PostLogQuantity.__init__(self, name, "1", description) self.events = 0 @@ -1506,7 +1494,7 @@ def __call__(self) -> int: def time_and_count_function(f: Callable[..., Any], timer: IntervalTimer, - counter: Optional[EventCounter] = None, + counter: EventCounter | None = None, increment: int = 1) -> Callable[..., Any]: def inner_f(*args: Any, **kwargs: Any) -> Any: if counter is not None: @@ -1552,14 +1540,14 @@ class StepToStepDuration(PostLogQuantity): def __init__(self, name: str = "t_2step") -> None: PostLogQuantity.__init__(self, name, "s", "Step-to-step duration") - self.last_start_time: Optional[float] = None - self.last2_start_time: Optional[float] = None + self.last_start_time: float | None = None + self.last2_start_time: float | None = None def prepare_for_tick(self) -> None: self.last2_start_time = self.last_start_time self.last_start_time = time_monotonic() - def __call__(self) -> Optional[float]: + def __call__(self) -> float | None: if self.last2_start_time is None or self.last_start_time is None: return None else: @@ -1613,7 +1601,7 @@ def __init__(self, name: str = "t_init") -> None: self.create_time = psutil.Process().create_time() self.done = False - def __call__(self) -> Optional[float]: + def __call__(self) -> float | None: if self.done: return None @@ -1693,7 +1681,7 @@ class Timestep(SimulationLogQuantity): def __init__(self, name: str = "dt", unit: str = "s") -> None: SimulationLogQuantity.__init__(self, name, unit, "Simulation Timestep") - def __call__(self) -> Optional[float]: + def __call__(self) -> float | None: return self.dt @@ -1804,10 +1792,10 @@ def __init__(self) -> None: assert len(names) == len(units) == len(descriptions) == 13 - super().__init__(names, cast(List[Optional[str]], units), - cast(List[Optional[str]], descriptions)) + super().__init__(names, cast(list[str | None], units), + cast(list[str | None], descriptions)) - def __call__(self) -> Iterable[Optional[float]]: + def __call__(self) -> Iterable[float | None]: import gc enabled = gc.isenabled() diff --git a/logpyle/runalyzer.py b/logpyle/runalyzer.py index 58642cd..29ffeda 100644 --- a/logpyle/runalyzer.py +++ b/logpyle/runalyzer.py @@ -17,21 +17,12 @@ import logging +from collections.abc import Callable, Generator, Sequence from dataclasses import dataclass from itertools import product from sqlite3 import Connection, Cursor from typing import ( Any, - Callable, - Dict, - Generator, - List, - Optional, - Sequence, - Set, - Tuple, - Type, - Union, ) from pytools import Table @@ -41,7 +32,7 @@ @dataclass(frozen=True) class PlotStyle: - dashes: Tuple[int, ...] + dashes: tuple[int, ...] color: str @@ -57,7 +48,7 @@ class RunDB: def __init__(self, db: Connection, interactive: bool) -> None: self.db = db self.interactive = interactive - self.rank_agg_tables: Set[Tuple[str, Callable[..., Any]]] = set() + self.rank_agg_tables: set[tuple[str, Callable[..., Any]]] = set() def __del__(self) -> None: self.db.close() @@ -84,11 +75,11 @@ def get_rank_agg_table(self, qty: str, self.rank_agg_tables.add((qty, rank_aggregator)) return tbl_name - def scatter_cursor(self, cursor: Cursor, labels: Optional[List[str]] = None, + def scatter_cursor(self, cursor: Cursor, labels: list[str] | None = None, *args: Any, **kwargs: Any) -> None: import matplotlib.pyplot as plt - data_args = tuple(zip(*list(cursor))) + data_args = tuple(zip(*list(cursor), strict=False)) plt.scatter(*(data_args + args), **kwargs) if isinstance(labels, list) and len(labels) == 2: @@ -101,7 +92,7 @@ def scatter_cursor(self, cursor: Cursor, labels: Optional[List[str]] = None, if self.interactive: plt.show() - def plot_cursor(self, cursor: Cursor, labels: Optional[List[str]] = None, # noqa: C901 + def plot_cursor(self, cursor: Cursor, labels: list[str] | None = None, # noqa: C901 *args: Any, **kwargs: Any) -> None: from matplotlib.pyplot import legend, plot, show @@ -113,7 +104,7 @@ def plot_cursor(self, cursor: Cursor, labels: Optional[List[str]] = None, # noq kwargs["dashes"] = style.dashes kwargs["color"] = style.color - x, y = list(zip(*list(cursor))) + x, y = list(zip(*list(cursor), strict=False)) p = plot(x, y, *args, **kwargs) if isinstance(labels, list) and len(labels) == 2: @@ -126,13 +117,13 @@ def plot_cursor(self, cursor: Cursor, labels: Optional[List[str]] = None, # noq elif len(cursor.description) > 2: small_legend = kwargs.pop("small_legend", True) - def format_label(kv_pairs: Sequence[Tuple[str, Any]]) -> str: + def format_label(kv_pairs: Sequence[tuple[str, Any]]) -> str: return " ".join(f"{column}:{value}" for column, value in kv_pairs) format_label = kwargs.pop("format_label", format_label) - def do_plot(x: List[float], y: List[float], - row_rest: Tuple[Any, ...]) -> None: + def do_plot(x: list[float], y: list[float], + row_rest: tuple[Any, ...]) -> None: my_kwargs = kwargs.copy() style = PLOT_STYLES[style_idx[0] % len(PLOT_STYLES)] if auto_style: @@ -142,7 +133,7 @@ def do_plot(x: List[float], y: List[float], my_kwargs.setdefault("label", format_label(list(zip( (col[0] for col in cursor.description[2:]), - row_rest)))) + row_rest, strict=False)))) plot(x, y, *args, hold=True, **my_kwargs) style_idx[0] += 1 @@ -166,10 +157,10 @@ def print_cursor(self, cursor: Cursor) -> None: def split_cursor(cursor: Cursor) -> Generator[ - Tuple[List[Any], List[Any], Optional[Tuple[Any, ...]]], None, None]: + tuple[list[Any], list[Any], tuple[Any, ...] | None], None, None]: - x: List[Any] = [] - y: List[Any] = [] + x: list[Any] = [] + y: list[Any] = [] last_rest = None for row in cursor: row_tuple = tuple(row) @@ -255,7 +246,7 @@ def replace_magic_column(match: Any) -> str: f" on ({full_tbl}.run_id = runs.id{addendum}) " last_tbl = full_tbl - def get_clause_indices(qry: str) -> Dict[str, int]: + def get_clause_indices(qry: str) -> dict[str, int]: other_clauses = ["UNION", "INTERSECT", "EXCEPT", "WHERE", "GROUP", "HAVING", "ORDER", "LIMIT", ";"] @@ -287,7 +278,7 @@ def get_clause_indices(qry: str) -> Dict[str, int]: def make_runalyzer_symbols(db: RunDB) \ - -> Dict[str, Union[RunDB, str, Callable[..., Any], None]]: + -> dict[str, RunDB | str | Callable[..., Any] | None]: return { "__name__": "__console__", "__doc__": None, @@ -432,7 +423,7 @@ def __init__(self) -> None: class StdDeviation(Variance): - def finalize(self) -> Optional[float]: + def finalize(self) -> float | None: result = Variance.finalize(self) # type: ignore[no-untyped-call] if result is None: @@ -496,7 +487,7 @@ def is_gathered(conn: sqlite3.Connection) -> bool: return False -def auto_gather(filenames: List[str]) -> sqlite3.Connection: +def auto_gather(filenames: list[str]) -> sqlite3.Connection: # allow for creating ungathered files. # Check if database has been gathered, if not, create one in memory @@ -540,7 +531,7 @@ def auto_gather(filenames: List[str]) -> sqlite3.Connection: # {{{ main program def make_wrapped_db( - filenames: List[str], interactive: bool, + filenames: list[str], interactive: bool, mangle: bool, gather: bool = True ) -> RunDB: if gather: @@ -560,7 +551,7 @@ def make_wrapped_db( db.create_function("pow", 2, pow) if mangle: - db_wrap_class: Type[RunDB] = MagicRunDB + db_wrap_class: type[RunDB] = MagicRunDB else: db_wrap_class = RunDB diff --git a/logpyle/runalyzer_gather.py b/logpyle/runalyzer_gather.py index fc08cca..d1e0bc1 100644 --- a/logpyle/runalyzer_gather.py +++ b/logpyle/runalyzer_gather.py @@ -1,7 +1,7 @@ import re import sqlite3 from sqlite3 import Connection -from typing import Any, Dict, List, Optional, Tuple, Union, cast +from typing import Any, cast from pytools.datatable import DataTable @@ -29,7 +29,7 @@ def parse_dir_feature(feat: str, number: int) \ - -> Tuple[Union[str, Any], str, Union[str, Any]]: + -> tuple[str | Any, str, str | Any]: bool_match = bool_feat_re.match(feat) if bool_match is not None: return (bool_match.group(1), "integer", int(bool_match.group(2) == "True")) @@ -45,7 +45,7 @@ def parse_dir_feature(feat: str, number: int) \ return (f"dirfeat{number}", "text", feat) -def larger_sql_type(type_a: Optional[str], type_b: Optional[str]) -> Optional[str]: +def larger_sql_type(type_a: str | None, type_b: str | None) -> str | None: assert type_a in [None, "text", "real", "integer"] assert type_b in [None, "text", "real", "integer"] @@ -62,7 +62,7 @@ def larger_sql_type(type_a: Optional[str], type_b: Optional[str]) -> Optional[st def sql_type_and_value(value: Any) \ - -> Tuple[Optional[str], Union[int, float, str, None]]: + -> tuple[str | None, int | float | str | None]: if value is None: return None, None elif isinstance(value, bool): @@ -76,7 +76,7 @@ def sql_type_and_value(value: Any) \ def sql_type_and_value_from_str(value: str) \ - -> Tuple[Optional[str], Union[int, float, str, None]]: + -> tuple[str | None, int | float | str | None]: if value == "None": return None, None elif value in ["True", "False"]: @@ -95,7 +95,7 @@ def sql_type_and_value_from_str(value: str) \ class FeatureGatherer: def __init__(self, features_from_dir: bool = False, - features_file: Optional[str] = None) -> None: + features_file: str | None = None) -> None: self.features_from_dir = features_from_dir self.dir_to_features = {} @@ -115,7 +115,7 @@ def __init__(self, features_from_dir: bool = False, self.dir_to_features[line[:colon_idx]] = features - def get_db_features(self, dbname: str, logmgr: LogManager) -> List[Any]: + def get_db_features(self, dbname: str, logmgr: LogManager) -> list[Any]: from os.path import dirname dn = dirname(dbname) @@ -131,11 +131,11 @@ def get_db_features(self, dbname: str, logmgr: LogManager) -> List[Any]: return features -def scan(fg: FeatureGatherer, dbnames: List[str], # noqa: C901 - progress: bool = True) -> Tuple[Dict[str, Any], Dict[str, int]]: - features: Dict[str, Any] = {} +def scan(fg: FeatureGatherer, dbnames: list[str], # noqa: C901 + progress: bool = True) -> tuple[dict[str, Any], dict[str, int]]: + features: dict[str, Any] = {} dbname_to_run_id = {} - uid_to_run_id: Dict[str, int] = {} + uid_to_run_id: dict[str, int] = {} next_run_id = 1 from pytools import ProgressBar @@ -181,9 +181,9 @@ def scan(fg: FeatureGatherer, dbnames: List[str], # noqa: C901 return features, dbname_to_run_id -def make_name_map(map_str: str) -> Dict[str, str]: +def make_name_map(map_str: str) -> dict[str, str]: import re - result: Dict[str, str] = {} + result: dict[str, str] = {} if not map_str: return result @@ -208,10 +208,10 @@ def _normalize_types(x: Any) -> Any: return x -def gather_multi_file(outfile: str, infiles: List[str], fmap: Dict[str, str], - qmap: Dict[str, str], fg: FeatureGatherer, - features: Dict[str, Any], - dbname_to_run_id: Dict[str, int]) -> sqlite3.Connection: +def gather_multi_file(outfile: str, infiles: list[str], fmap: dict[str, str], + qmap: dict[str, str], fg: FeatureGatherer, + features: dict[str, Any], + dbname_to_run_id: dict[str, int]) -> sqlite3.Connection: from pytools import ProgressBar pb = ProgressBar("Importing...", len(infiles)) # type: ignore[no-untyped-call] diff --git a/pyproject.toml b/pyproject.toml index d67eb67..31b643e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,11 +21,10 @@ description = "Time series logging for Python" dependencies = [ "pytools>=2011.1", "pymbolic", - "importlib_metadata;python_version<'3.8'", ] readme = "README.md" license = { file="LICENSE" } -requires-python = ">=3.7" +requires-python = ">=3.10" classifiers = [ "Development Status :: 4 - Beta", "Intended Audience :: Developers", diff --git a/test/test_quantities.py b/test/test_quantities.py index 85d0a0e..1ca5de0 100644 --- a/test/test_quantities.py +++ b/test/test_quantities.py @@ -262,7 +262,7 @@ def custom_multi_log_quantity(request): class TestLogQuantity(MultiLogQuantity): def __init__(self, names, parameters) -> None: super().__init__(names, units, descriptions) - for name, parameter in zip(names, parameters): + for name, parameter in zip(names, parameters, strict=False): setattr(self, name, parameter) self.func = call_func @@ -272,7 +272,7 @@ def __call__(self): # update value every time quantity is called new_vals = self.func(*values) - for name, val in zip(self.names, new_vals): + for name, val in zip(self.names, new_vals, strict=False): setattr(self, name, val) return new_vals @@ -360,7 +360,7 @@ def custom_multi_post_logquantity(request): class TestLogQuantity(MultiPostLogQuantity): def __init__(self, names, parameters) -> None: super().__init__(names, units, descriptions) - for name, parameter in zip(names, parameters): + for name, parameter in zip(names, parameters, strict=False): setattr(self, name, parameter) self.func = call_func @@ -370,7 +370,7 @@ def __call__(self): # update value every time quantity is called new_vals = self.func(*values) - for name, val in zip(self.names, new_vals): + for name, val in zip(self.names, new_vals, strict=False): setattr(self, name, val) return new_vals @@ -509,7 +509,7 @@ def test_steptostep_and_timestepduration_quantity( print(actual_times, sleep_times) # assert that these quantities only differ by a max of tol # defined above - for (predicted, actual) in zip(sleep_times, actual_times): + for (predicted, actual) in zip(sleep_times, actual_times, strict=False): assert abs(actual - predicted) < tol @@ -650,7 +650,7 @@ def test_interval_timer_subtimer(basic_logmgr: LogManager): print(expected_timer_list) # enforce equality of durations - for tup in zip(val_list, expected_timer_list): + for tup in zip(val_list, expected_timer_list, strict=False): assert abs(tup[0] - tup[1]) < tol @@ -682,7 +682,7 @@ def test_interval_timer_subtimer_blocking(basic_logmgr: LogManager): print(expected_timer_list) # enforce equality of durations - for tup in zip(val_list, expected_timer_list): + for tup in zip(val_list, expected_timer_list, strict=False): assert abs(tup[0] - tup[1]) < tol