From efe0c56af9409502257986dd72d38e8c7fb8c383 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Fri, 20 Feb 2026 13:03:34 -0600 Subject: [PATCH 1/2] Update config, fix lints for ruff 0.15.2 --- pyproject.toml | 7 + pytools/__init__.py | 87 +++++------ pytools/datatable.py | 6 +- pytools/obj_array.py | 218 ++++++++++++++------------- pytools/prefork.py | 4 +- pytools/py_codegen.py | 4 +- pytools/test/test_persistent_dict.py | 8 +- pytools/test/test_pytools.py | 4 +- 8 files changed, 178 insertions(+), 160 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 70cd5f5f..13721453 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -90,8 +90,15 @@ extend-ignore = [ # FIXME: This is a longer discussion... "RUF067", # __init__ should only contain reexports + "DTZ", + "TRY", + "BLE", ] +[tool.ruff.lint.per-file-ignores] +"pytools/test/*.py" = ["S102"] +"doc/conf.py" = ["S102"] + [tool.ruff.lint.flake8-quotes] docstring-quotes = "double" inline-quotes = "double" diff --git a/pytools/__init__.py b/pytools/__init__.py index 1e3c05ef..11ec8b1b 100644 --- a/pytools/__init__.py +++ b/pytools/__init__.py @@ -255,10 +255,13 @@ ------------------- .. class:: T -.. class:: R Generic unbound invariant :class:`typing.TypeVar`. +.. class:: R_co + + Generic unbound covariant :class:`typing.TypeVar`. + .. class:: F Generic invariant :class:`typing.TypeVar` bound to a :class:`typing.Callable`. @@ -284,7 +287,7 @@ T_co = TypeVar("T_co", covariant=True) T_contra = TypeVar("T_contra", contravariant=True) Ts = TypeVarTuple("Ts") -R = TypeVar("R", covariant=True) +R_co = TypeVar("R_co", covariant=True) F = TypeVar("F", bound=Callable[..., Any]) P = ParamSpec("P") K = TypeVar("K") @@ -299,12 +302,12 @@ # Undocumented on purpose for now, unclear that this is a great idea, given # that typing.deprecated exists. -class MovedFunctionDeprecationWrapper(Generic[P, R]): - f: Callable[P, R] +class MovedFunctionDeprecationWrapper(Generic[P, R_co]): + f: Callable[P, R_co] deadline: int | str def __init__(self, - f: Callable[P, R], + f: Callable[P, R_co], deadline: int | str | None = None) -> None: if deadline is None: deadline = "the future" @@ -312,7 +315,7 @@ def __init__(self, self.f = f self.deadline = deadline - def __call__(self, *args: P.args, **kwargs: P.kwargs) -> R: + def __call__(self, *args: P.args, **kwargs: P.kwargs) -> R_co: from warnings import warn warn(f"This function is deprecated and will go away in {self.deadline}. " f"Use {self.f.__module__}.{self.f.__name__} instead.", @@ -587,21 +590,21 @@ def set(self, value: T) -> None: self.value = value -class FakeList(Generic[R]): - def __init__(self, f: Callable[[int], R], length: int) -> None: +class FakeList(Generic[R_co]): + def __init__(self, f: Callable[[int], R_co], length: int) -> None: self._Length: int = length - self._Function: Callable[[int], R] = f + self._Function: Callable[[int], R_co] = f def __len__(self) -> int: return self._Length @overload - def __getitem__(self, index: int) -> R: ... + def __getitem__(self, index: int) -> R_co: ... @overload - def __getitem__(self, index: slice) -> Sequence[R]: ... + def __getitem__(self, index: slice) -> Sequence[R_co]: ... - def __getitem__(self, index: object) -> R | Sequence[R]: + def __getitem__(self, index: object) -> R_co | Sequence[R_co]: if isinstance(index, int): return self._Function(index) elif isinstance(index, slice): @@ -613,17 +616,17 @@ def __getitem__(self, index: object) -> R | Sequence[R]: # {{{ dependent dictionary -class DependentDictionary(Generic[T, R]): +class DependentDictionary(Generic[T, R_co]): def __init__(self, - f: Callable[[dict[T, R], T], R], - start: dict[T, R] | None = None) -> None: + f: Callable[[dict[T, R_co], T], R_co], + start: dict[T, R_co] | None = None) -> None: if start is None: start = {} - self._Function: Callable[[dict[T, R], T], R] = f - self._Dictionary: dict[T, R] = start.copy() + self._Function: Callable[[dict[T, R_co], T], R_co] = f + self._Dictionary: dict[T, R_co] = start.copy() - def copy(self) -> DependentDictionary[T, R]: + def copy(self) -> DependentDictionary[T, R_co]: return DependentDictionary(self._Function, self._Dictionary) def __contains__(self, key: T) -> bool: @@ -633,25 +636,25 @@ def __contains__(self, key: T) -> bool: except KeyError: return False - def __getitem__(self, key: T) -> R: + def __getitem__(self, key: T) -> R_co: try: return self._Dictionary[key] except KeyError: return self._Function(self._Dictionary, key) - def __setitem__(self, key: T, value: R) -> None: + def __setitem__(self, key: T, value: R_co) -> None: self._Dictionary[key] = value def genuineKeys(self): # noqa: N802 return list(self._Dictionary.keys()) - def iteritems(self) -> Iterable[tuple[T, R]]: + def iteritems(self) -> Iterable[tuple[T, R_co]]: return self._Dictionary.items() def iterkeys(self) -> Iterable[T]: return self._Dictionary.keys() - def itervalues(self) -> Iterable[R]: + def itervalues(self) -> Iterable[R_co]: return self._Dictionary.values() # }}} @@ -810,8 +813,8 @@ class _HasKwargs: def memoize_on_first_arg( - function: Callable[Concatenate[T, P], R], *, - cache_dict_name: str | None = None) -> Callable[Concatenate[T, P], R]: + function: Callable[Concatenate[T, P], R_co], *, + cache_dict_name: str | None = None) -> Callable[Concatenate[T, P], R_co]: """Like :func:`memoize_method`, but for functions that take the object in which do memoization information is stored as first argument. @@ -823,7 +826,7 @@ def memoize_on_first_arg( f"_memoize_dic_{function.__module__}{function.__name__}" ) - def wrapper(obj: T, *args: P.args, **kwargs: P.kwargs) -> R: + def wrapper(obj: T, *args: P.args, **kwargs: P.kwargs) -> R_co: key = (_HasKwargs, frozenset(kwargs.items()), *args) if kwargs else args assert cache_dict_name is not None @@ -856,8 +859,8 @@ def clear_cache(obj): def memoize_method( - method: Callable[Concatenate[T, P], R] - ) -> Callable[Concatenate[T, P], R]: + method: Callable[Concatenate[T, P], R_co] + ) -> Callable[Concatenate[T, P], R_co]: """Supports cache deletion via ``method_name.clear_cache(self)``. .. versionchanged:: 2021.2 @@ -870,7 +873,7 @@ def memoize_method( cache_dict_name=intern(f"_memoize_dic_{method.__name__}")) -class keyed_memoize_on_first_arg(Generic[T, P, R]): # noqa: N801 +class keyed_memoize_on_first_arg(Generic[T, P, R_co]): # noqa: N801 """Like :func:`memoize_method`, but for functions that take the object in which memoization information is stored as first argument. @@ -891,19 +894,19 @@ def __init__(self, self.cache_dict_name = cache_dict_name def _default_cache_dict_name(self, - function: Callable[Concatenate[T, P], R]) -> str: + function: Callable[Concatenate[T, P], R_co]) -> str: return intern(f"_memoize_dic_{function.__module__}{function.__name__}") def __call__( - self, function: Callable[Concatenate[T, P], R] - ) -> Callable[Concatenate[T, P], R]: + self, function: Callable[Concatenate[T, P], R_co] + ) -> Callable[Concatenate[T, P], R_co]: cache_dict_name = self.cache_dict_name key = self.key if cache_dict_name is None: cache_dict_name = self._default_cache_dict_name(function) - def wrapper(obj: T, *args: P.args, **kwargs: P.kwargs) -> R: + def wrapper(obj: T, *args: P.args, **kwargs: P.kwargs) -> R_co: cache_key = key(*args, **kwargs) assert cache_dict_name is not None @@ -983,9 +986,9 @@ def __init__(self, container: Any, identifier: Hashable) -> None: self.cache_dict = memoize_in_dict.setdefault(identifier, {}) - def __call__(self, inner: Callable[P, R]) -> Callable[P, R]: + def __call__(self, inner: Callable[P, R_co]) -> Callable[P, R_co]: @wraps(inner) - def new_inner(*args: P.args, **kwargs: P.kwargs) -> R: + def new_inner(*args: P.args, **kwargs: P.kwargs) -> R_co: assert not kwargs try: @@ -1021,9 +1024,9 @@ def __init__(self, self.cache_dict = memoize_in_dict.setdefault(identifier, {}) self.key = key - def __call__(self, inner: Callable[P, R]) -> Callable[P, R]: + def __call__(self, inner: Callable[P, R_co]) -> Callable[P, R_co]: @wraps(inner) - def new_inner(*args: P.args, **kwargs: P.kwargs) -> R: + def new_inner(*args: P.args, **kwargs: P.kwargs) -> R_co: assert not kwargs key = self.key(*args, **kwargs) @@ -1609,7 +1612,7 @@ def generate_all_integer_tuples_below( n, length, least_abs)) -class _ConcatenableSequence(Generic[T_co], Protocol): +class _ConcatenableSequence(Protocol, Generic[T_co]): """ A protocol that supports the following: @@ -2185,9 +2188,8 @@ def invoke_editor(s: str, filename: str = "edit.txt", descr: str = "the file"): "dropped directly into an editor next time.)") input(f"Edit {descr} at {full_path} now, then hit [Enter]:") - result = full_path.read_text() + return full_path.read_text() - return result # }}} @@ -2871,13 +2873,13 @@ def __init__(self, self.description = description self.long_threshold_seconds = long_threshold_seconds - def __call__(self, wrapped: Callable[P, R]) -> Callable[P, R]: + def __call__(self, wrapped: Callable[P, R_co]) -> Callable[P, R_co]: if self.description: description = f"{wrapped.__qualname__} ({self.description})" else: description = wrapped.__qualname__ - def wrapper(*args: P.args, **kwargs: P.kwargs) -> R: + def wrapper(*args: P.args, **kwargs: P.kwargs) -> R_co: with ProcessLogger( self.logger, description, @@ -2885,9 +2887,8 @@ def wrapper(*args: P.args, **kwargs: P.kwargs) -> R: return wrapped(*args, **kwargs) from functools import update_wrapper - new_wrapper = update_wrapper(wrapper, wrapped) + return update_wrapper(wrapper, wrapped) - return new_wrapper # }}} diff --git a/pytools/datatable.py b/pytools/datatable.py index 126ed4ba..494b5920 100644 --- a/pytools/datatable.py +++ b/pytools/datatable.py @@ -263,11 +263,11 @@ def without(indexable: tuple[str, ...], idx: int) -> tuple[str, ...]: other_batch = [(None,) * len(other_table.column_names)] for this_batch_row in this_batch: - for other_batch_row in other_batch: - result_data.append(( + result_data.extend(( key, *without(this_batch_row, this_key_idx), - *without(other_batch_row, other_key_idx))) + *without(other_batch_row, other_key_idx)) + for other_batch_row in other_batch) if outer: if this_over and other_over: diff --git a/pytools/obj_array.py b/pytools/obj_array.py index 5b52d65a..727223ae 100644 --- a/pytools/obj_array.py +++ b/pytools/obj_array.py @@ -4,7 +4,7 @@ Handling :mod:`numpy` Object Arrays =================================== -.. autoclass:: T +.. autoclass:: T_co .. autoclass:: ResultT .. autoclass:: ShapeT @@ -95,7 +95,7 @@ from numpy.typing import NDArray -T = TypeVar("T", covariant=True) +T_co = TypeVar("T_co", covariant=True) ResultT = TypeVar("ResultT") ShapeT = TypeVar("ShapeT", bound=tuple[int, ...]) @@ -111,7 +111,7 @@ def __instancecheck__(cls, instance: object) -> bool: return isinstance(instance, np.ndarray) and instance.dtype == object -class ObjectArray(Generic[ShapeT, T], metaclass=_ObjectArrayMetaclass): +class ObjectArray(Generic[ShapeT, T_co], metaclass=_ObjectArrayMetaclass): """This is a fake type used to distinguish :class:`numpy.ndarray` instances with a *dtype* of *object* for static type checking. Unlike :class:`numpy.ndarray`, this "type" can straightforwardly @@ -149,47 +149,53 @@ def size(self) -> int: ... def T(self) -> Self: ... # noqa: N802 @overload - def __getitem__(self, x: ShapeT, /) -> T: ... + def __getitem__(self, x: ShapeT, /) -> T_co: ... @overload - def __getitem__(self: ObjectArray1D[T], x: int, /) -> T: ... + def __getitem__(self: ObjectArray1D[T_co], x: int, /) -> T_co: ... @overload - def __getitem__(self: ObjectArray2D[T], x: int, /) -> ObjectArray1D[T]: ... + def __getitem__( + self: ObjectArray2D[T_co], + x: int, + /) -> ObjectArray1D[T_co]: ... @overload - def __getitem__(self: ObjectArrayND[T], x: int, /) -> ObjectArrayND[T] | T: ... + def __getitem__( + self: ObjectArrayND[T_co], + x: int, + /) -> ObjectArrayND[T_co] | T_co: ... @overload def __getitem__( - self: ObjectArray1D[T], - x: slice, /) -> ObjectArray1D[T]: ... + self: ObjectArray1D[T_co], + x: slice, /) -> ObjectArray1D[T_co]: ... @overload def __getitem__( - self: ObjectArray2D[T], - x: slice, /) -> ObjectArray2D[T]: ... + self: ObjectArray2D[T_co], + x: slice, /) -> ObjectArray2D[T_co]: ... @overload def __getitem__( - self: ObjectArray2D[T], - x: tuple[slice, int], /) -> ObjectArray1D[T]: ... + self: ObjectArray2D[T_co], + x: tuple[slice, int], /) -> ObjectArray1D[T_co]: ... @overload def __getitem__( - self: ObjectArray2D[T], - x: tuple[int, slice], /) -> ObjectArray1D[T]: ... + self: ObjectArray2D[T_co], + x: tuple[int, slice], /) -> ObjectArray1D[T_co]: ... @overload def __getitem__( - self: ObjectArrayND[T], + self: ObjectArrayND[T_co], x: tuple[int | slice, ...], - /) -> ObjectArrayND[T] | T: ... + /) -> ObjectArrayND[T_co] | T_co: ... @overload - def __iter__(self: ObjectArray1D[T]) -> Iterator[T]: ... + def __iter__(self: ObjectArray1D[T_co]) -> Iterator[T_co]: ... @overload - def __iter__(self: ObjectArray2D[T]) -> Iterator[ObjectArray1D[T]]: ... + def __iter__(self: ObjectArray2D[T_co]) -> Iterator[ObjectArray1D[T_co]]: ... def __pos__(self) -> Self: ... def __neg__(self) -> Self: ... @@ -201,111 +207,111 @@ def __abs__(self) -> Self: ... @overload def __add__(self, other: Self, /) -> Self: ... @overload - def __add__(self, other: T, /) -> Self: ... # pyright: ignore[reportGeneralTypeIssues] + def __add__(self, other: T_co, /) -> Self: ... # pyright: ignore[reportGeneralTypeIssues] @overload def __add__(self, other: complex, /) -> Self: ... @overload - def __radd__(self, other: T, /) -> Self: ... # pyright: ignore[reportGeneralTypeIssues] + def __radd__(self, other: T_co, /) -> Self: ... # pyright: ignore[reportGeneralTypeIssues] @overload def __radd__(self, other: complex, /) -> Self: ... @overload def __sub__(self, other: Self, /) -> Self: ... @overload - def __sub__(self, other: T, /) -> Self: ... # pyright: ignore[reportGeneralTypeIssues] + def __sub__(self, other: T_co, /) -> Self: ... # pyright: ignore[reportGeneralTypeIssues] @overload def __sub__(self, other: complex, /) -> Self: ... @overload - def __rsub__(self, other: T, /) -> Self: ... # pyright: ignore[reportGeneralTypeIssues] + def __rsub__(self, other: T_co, /) -> Self: ... # pyright: ignore[reportGeneralTypeIssues] @overload def __rsub__(self, other: complex, /) -> Self: ... @overload def __mul__(self, other: Self, /) -> Self: ... @overload - def __mul__(self, other: T, /) -> Self: ... # pyright: ignore[reportGeneralTypeIssues] + def __mul__(self, other: T_co, /) -> Self: ... # pyright: ignore[reportGeneralTypeIssues] @overload def __mul__(self, other: complex, /) -> Self: ... @overload - def __rmul__(self, other: T, /) -> Self: ... # pyright: ignore[reportGeneralTypeIssues] + def __rmul__(self, other: T_co, /) -> Self: ... # pyright: ignore[reportGeneralTypeIssues] @overload def __rmul__(self, other: complex, /) -> Self: ... @overload def __truediv__(self, other: Self, /) -> Self: ... @overload - def __truediv__(self, other: T, /) -> Self: ... # pyright: ignore[reportGeneralTypeIssues] + def __truediv__(self, other: T_co, /) -> Self: ... # pyright: ignore[reportGeneralTypeIssues] @overload def __truediv__(self, other: complex, /) -> Self: ... @overload - def __rtruediv__(self, other: T, /) -> Self: ... # pyright: ignore[reportGeneralTypeIssues] + def __rtruediv__(self, other: T_co, /) -> Self: ... # pyright: ignore[reportGeneralTypeIssues] @overload def __rtruediv__(self, other: complex, /) -> Self: ... @overload def __pow__(self, other: Self, /) -> Self: ... @overload - def __pow__(self, other: T, /) -> Self: ... # pyright: ignore[reportGeneralTypeIssues] + def __pow__(self, other: T_co, /) -> Self: ... # pyright: ignore[reportGeneralTypeIssues] @overload def __pow__(self, other: complex, /) -> Self: ... @overload - def __rpow__(self, other: T, /) -> Self: ... # pyright: ignore[reportGeneralTypeIssues] + def __rpow__(self, other: T_co, /) -> Self: ... # pyright: ignore[reportGeneralTypeIssues] @overload def __rpow__(self, other: complex, /) -> Self: ... @overload def __matmul__( - self: ObjectArray1D[T], - other: ObjectArray1D[T], - /) -> T: ... + self: ObjectArray1D[T_co], + other: ObjectArray1D[T_co], + /) -> T_co: ... @overload def __matmul__( - self: ObjectArray2D[T], - other: ObjectArray1D[T], - /) -> ObjectArray1D[T]: ... + self: ObjectArray2D[T_co], + other: ObjectArray1D[T_co], + /) -> ObjectArray1D[T_co]: ... @overload def __matmul__( - self: ObjectArray1D[T], - other: ObjectArray2D[T], - /) -> ObjectArray1D[T]: ... + self: ObjectArray1D[T_co], + other: ObjectArray2D[T_co], + /) -> ObjectArray1D[T_co]: ... @overload def __matmul__( - self: ObjectArray2D[T], - other: ObjectArray2D[T], - /) -> ObjectArray2D[T]: ... + self: ObjectArray2D[T_co], + other: ObjectArray2D[T_co], + /) -> ObjectArray2D[T_co]: ... @overload def __matmul__( - self: ObjectArray2D[T], + self: ObjectArray2D[T_co], other: np.ndarray[tuple[int, int], np.dtype[Any]], - /) -> ObjectArray2D[T]: ... + /) -> ObjectArray2D[T_co]: ... @overload def __matmul__( - self: ObjectArrayND[T], - other: ObjectArrayND[T], - /) -> ObjectArrayND[T] | T: ... + self: ObjectArrayND[T_co], + other: ObjectArrayND[T_co], + /) -> ObjectArrayND[T_co] | T_co: ... @property - def flat(self) -> Iterator[T]: ... + def flat(self) -> Iterator[T_co]: ... - def flatten(self) -> ObjectArray1D[T]: ... + def flatten(self) -> ObjectArray1D[T_co]: ... @overload - def tolist(self: ObjectArray0D[T]) -> T: ... + def tolist(self: ObjectArray0D[T_co]) -> T_co: ... @overload - def tolist(self: ObjectArray1D[T]) -> list[T]: ... + def tolist(self: ObjectArray1D[T_co]) -> list[T_co]: ... @overload - def tolist(self: ObjectArray2D[T]) -> list[list[T]]: ... + def tolist(self: ObjectArray2D[T_co]) -> list[list[T_co]]: ... @overload def tolist(self) -> list[Any]: ... -ObjectArray0D: TypeAlias = ObjectArray[tuple[()], T] -ObjectArray1D: TypeAlias = ObjectArray[tuple[int], T] -ObjectArray2D: TypeAlias = ObjectArray[tuple[int, int], T] -ObjectArrayND: TypeAlias = ObjectArray[tuple[int, ...], T] +ObjectArray0D: TypeAlias = ObjectArray[tuple[()], T_co] +ObjectArray1D: TypeAlias = ObjectArray[tuple[int], T_co] +ObjectArray2D: TypeAlias = ObjectArray[tuple[int, int], T_co] +ObjectArrayND: TypeAlias = ObjectArray[tuple[int, ...], T_co] -def to_numpy(ary: ObjectArray[ShapeT, T]) -> np.ndarray[ShapeT, Any]: +def to_numpy(ary: ObjectArray[ShapeT, T_co]) -> np.ndarray[ShapeT, Any]: return cast("np.ndarray[ShapeT, Any]", cast("object", ary)) @@ -319,22 +325,22 @@ def from_numpy( @overload def from_numpy( ary: np.ndarray[ShapeT, Any], - tp: type[T], + tp: type[T_co], / - ) -> ObjectArray[ShapeT, T]: ... + ) -> ObjectArray[ShapeT, T_co]: ... def from_numpy( ary: np.ndarray[ShapeT, Any], - tp: type[T] | None = None, # pyright: ignore[reportUnusedParameter] + tp: type[T_co] | None = None, # pyright: ignore[reportUnusedParameter] / - ) -> ObjectArray[ShapeT, T]: + ) -> ObjectArray[ShapeT, T_co]: if ary.dtype != object: # pyright: ignore[reportAny] ary = ary.astype(object) - return cast("ObjectArray[ShapeT, T]", cast("object", ary)) + return cast("ObjectArray[ShapeT, T_co]", cast("object", ary)) -def new_1d(res_list: Sequence[T]) -> ObjectArray1D[T]: +def new_1d(res_list: Sequence[T_co]) -> ObjectArray1D[T_co]: """Create a one-dimensional object array from *res_list*. This differs from ``numpy.array(res_list, dtype=object)`` by whether it tries to determine its shape by descending @@ -373,19 +379,19 @@ def new_1d(res_list: Sequence[T]) -> ObjectArray1D[T]: for idx in range(len(res_list)): result[idx] = res_list[idx] - return cast("ObjectArray1D[T]", cast("object", result)) + return cast("ObjectArray1D[T_co]", cast("object", result)) @deprecated("use obj_array.new_1d instead") -def make_obj_array(res_list: Sequence[T]) -> ObjectArray1D[T]: +def make_obj_array(res_list: Sequence[T_co]) -> ObjectArray1D[T_co]: # No run-time warning yet to avoid excessive warning spam. return new_1d(res_list) def stack( - arrays: Sequence[ObjectArray[ShapeT, T]], + arrays: Sequence[ObjectArray[ShapeT, T_co]], /, *, axis: Literal[0] = 0, - ) -> ObjectArray[tuple[int, Unpack[ShapeT]], T]: + ) -> ObjectArray[tuple[int, Unpack[ShapeT]], T_co]: """ .. versionadded:: 2025.2.2 """ @@ -393,14 +399,14 @@ def stack( raise NotImplementedError("axis != 0") import numpy as np - return cast("ObjectArray[tuple[int, Unpack[ShapeT]], T]", cast("object", + return cast("ObjectArray[tuple[int, Unpack[ShapeT]], T_co]", cast("object", np.stack(cast("Sequence[NDArray[Any]]", arrays)))) def concatenate( - arrays: Sequence[ObjectArray[tuple[int, Unpack[ShapeT]], T]], + arrays: Sequence[ObjectArray[tuple[int, Unpack[ShapeT]], T_co]], /, *, axis: Literal[0] = 0, - ) -> ObjectArray[tuple[int, Unpack[ShapeT]], T]: + ) -> ObjectArray[tuple[int, Unpack[ShapeT]], T_co]: """ .. versionadded:: 2025.2.2 """ @@ -408,65 +414,65 @@ def concatenate( raise NotImplementedError("axis != 0") import numpy as np - return cast("ObjectArray[tuple[int, Unpack[ShapeT]], T]", cast("object", + return cast("ObjectArray[tuple[int, Unpack[ShapeT]], T_co]", cast("object", np.concatenate(cast("Sequence[NDArray[Any]]", arrays)))) def trace( - array: ObjectArray2D[T], /, - ) -> T: + array: ObjectArray2D[T_co], /, + ) -> T_co: """ .. versionadded:: 2025.2.2 """ import numpy as np - return cast("T", np.trace(cast("NDArray[Any]", cast("object", array)))) + return cast("T_co", np.trace(cast("NDArray[Any]", cast("object", array)))) @overload def sum( - array: ObjectArrayND[T], + array: ObjectArrayND[T_co], axis: None, - ) -> T: ... + ) -> T_co: ... @overload def sum( - array: ObjectArray1D[T], + array: ObjectArray1D[T_co], axis: int, - ) -> T: ... + ) -> T_co: ... @overload def sum( - array: ObjectArray2D[T], + array: ObjectArray2D[T_co], axis: int, - ) -> ObjectArray1D[T]: ... + ) -> ObjectArray1D[T_co]: ... def sum( - array: ObjectArrayND[T], + array: ObjectArrayND[T_co], axis: int | None = None, - ) -> ObjectArrayND[T] | T: + ) -> ObjectArrayND[T_co] | T_co: import numpy as np - return cast("ObjectArrayND[T] | T", np.sum( + return cast("ObjectArrayND[T_co] | T_co", np.sum( cast("NDArray[Any]", cast("object", array)), axis=axis, )) -def to_hashable(ary: ObjectArray[ShapeT, T] | Hashable, /) -> Hashable: +def to_hashable(ary: ObjectArray[ShapeT, T_co] | Hashable, /) -> Hashable: if isinstance(ary, ObjectArray): - ary = cast("ObjectArray[ShapeT, T]", ary) + ary = cast("ObjectArray[ShapeT, T_co]", ary) return tuple(ary.flat) return ary @deprecated("use obj_array.to_hashable") -def obj_array_to_hashable(ary: ObjectArray[ShapeT, T] | Hashable, /) -> Hashable: +def obj_array_to_hashable(ary: ObjectArray[ShapeT, T_co] | Hashable, /) -> Hashable: return to_hashable(ary) -def flat(*args: ObjectArray[ShapeT, T] | list[T] | T) -> ObjectArray1D[T]: +def flat(*args: ObjectArray[ShapeT, T_co] | list[T_co] | T_co) -> ObjectArray1D[T_co]: """Return a one-dimensional flattened object array consisting of elements obtained by 'flattening' *args* as follows: @@ -476,23 +482,25 @@ def flat(*args: ObjectArray[ShapeT, T] | list[T] | T) -> ObjectArray1D[T]: - Any other type will appear in the list as-is. """ import numpy as np - res_list: list[T] = [] + res_list: list[T_co] = [] for arg in args: if isinstance(arg, list): - res_list.extend(cast("list[T]", arg)) + res_list.extend(cast("list[T_co]", arg)) # Only flatten genuine, non-subclassed object arrays. elif isinstance(arg, ObjectArray) and type(arg) is np.ndarray: # pyright: ignore[reportUnnecessaryComparison,reportUnknownArgumentType] res_list.extend(arg.flat) else: - res_list.append(cast("T", arg)) + res_list.append(cast("T_co", arg)) return new_1d(res_list) @deprecated("use obj_array.flat") -def flat_obj_array(*args: ObjectArray[ShapeT, T] | list[T] | T) -> ObjectArray1D[T]: +def flat_obj_array( + *args: ObjectArray[ShapeT, T_co] | list[T_co] | T_co + ) -> ObjectArray1D[T_co]: warn("flat_obj_array is deprecated, use obj_array.flat instead. " "This will stop working in 2027.", DeprecationWarning, stacklevel=2) return flat(*args) @@ -500,20 +508,20 @@ def flat_obj_array(*args: ObjectArray[ShapeT, T] | list[T] | T) -> ObjectArray1D @overload def vectorize( - f: Callable[[T], ResultT], - ary: ObjectArray[ShapeT, T], + f: Callable[[T_co], ResultT], + ary: ObjectArray[ShapeT, T_co], ) -> ObjectArray[ShapeT, ResultT]: ... @overload def vectorize( - f: Callable[[T], ResultT], - ary: T, + f: Callable[[T_co], ResultT], + ary: T_co, ) -> ResultT: ... def vectorize( - f: Callable[[T], ResultT], - ary: T | ObjectArray[ShapeT, T], + f: Callable[[T_co], ResultT], + ary: T_co | ObjectArray[ShapeT, T_co], ) -> ResultT | ObjectArray[ShapeT, ResultT]: """Apply the function *f* to all entries of the object array *ary*. Return an object array of the same shape consisting of the return @@ -529,7 +537,7 @@ def vectorize( import numpy as np if isinstance(ary, ObjectArray): result = np.empty_like(ary) - ary = cast("ObjectArray[ShapeT, T]", ary) + ary = cast("ObjectArray[ShapeT, T_co]", ary) for i in np.ndindex(ary.shape): result[i] = f(ary[i]) return cast("ObjectArray[ShapeT, ResultT]", cast("object", result)) @@ -539,8 +547,8 @@ def vectorize( @deprecated("use obj_array.vectorize") def obj_array_vectorize( - f: Callable[[T], ResultT], - ary: T | ObjectArray[ShapeT, T], + f: Callable[[T_co], ResultT], + ary: T_co | ObjectArray[ShapeT, T_co], ) -> ResultT | ObjectArray[ShapeT, ResultT]: warn("obj_array_vectorize is deprecated, use obj_array.vectorize instead. " "This will stop working in 2027.", DeprecationWarning, stacklevel=2) @@ -550,8 +558,10 @@ def obj_array_vectorize( # FIXME: It'd be nice to do make this more precise (T->T, ObjArray->ObjArray), # but I don't know how. def vectorized( - f: Callable[[T], T] - ) -> Callable[[T | ObjectArray[ShapeT, T]], T | ObjectArray[ShapeT, T]]: + f: Callable[[T_co], T_co] + ) -> Callable[ + [T_co | ObjectArray[ShapeT, T_co]], + T_co | ObjectArray[ShapeT, T_co]]: wrapper = partial(vectorize, f) update_wrapper(wrapper, f) return wrapper # pyright: ignore[reportReturnType] @@ -559,8 +569,10 @@ def vectorized( @deprecated("use obj_array.vectorized instead") def obj_array_vectorized( - f: Callable[[T], T] - ) -> Callable[[T | ObjectArray[ShapeT, T]], T | ObjectArray[ShapeT, T]]: + f: Callable[[T_co], T_co] + ) -> Callable[ + [T_co | ObjectArray[ShapeT, T_co]], + T_co | ObjectArray[ShapeT, T_co]]: warn("obj_array_vectorized is deprecated, use obj_array.vectorized instead. " "This will stop working in 2027.", DeprecationWarning, stacklevel=2) return vectorized(f) diff --git a/pytools/prefork.py b/pytools/prefork.py index 9d284075..bba73b9c 100644 --- a/pytools/prefork.py +++ b/pytools/prefork.py @@ -116,9 +116,7 @@ def call_capture_output(self, @override def wait(self, aid: int) -> int: proc = self.apids.pop(aid) - retc = proc.wait() - - return retc + return proc.wait() @override def waitall(self) -> dict[int, int]: diff --git a/pytools/py_codegen.py b/pytools/py_codegen.py index 253e1043..4c915f90 100644 --- a/pytools/py_codegen.py +++ b/pytools/py_codegen.py @@ -106,7 +106,7 @@ def _make_module( code_obj = compile( source_text.rstrip()+"\n", name, "exec") result_dict["__code__"] = code_obj - exec(code_obj, result_dict) + exec(code_obj, result_dict) # noqa: S102 return result_dict @@ -156,7 +156,7 @@ def _get_empty_module_dict(filename: str | None = None) -> dict[str, Any]: result_dict: dict[str, Any] = {} code_obj = compile("", filename, "exec") result_dict["__code__"] = code_obj - exec(code_obj, result_dict) + exec(code_obj, result_dict) # noqa: S102 return result_dict diff --git a/pytools/test/test_persistent_dict.py b/pytools/test/test_persistent_dict.py index 8f526ca8..49d7b02c 100644 --- a/pytools/test/test_persistent_dict.py +++ b/pytools/test/test_persistent_dict.py @@ -99,7 +99,7 @@ def rand_str(n=20): pdict[k] = v for k, v in d.items(): - assert d[k] == pdict[k] + assert v == pdict[k] assert v == pdict[k] # }}} @@ -110,7 +110,7 @@ def rand_str(n=20): pdict[k] = v + 1 for k, v in d.items(): - assert d[k] + 1 == pdict[k] + assert v + 1 == pdict[k] assert v + 1 == pdict[k] # }}} @@ -121,7 +121,7 @@ def rand_str(n=20): pdict.store_if_not_present(k, d[k] + 2) for k, v in d.items(): - assert d[k] + 1 == pdict[k] + assert v + 1 == pdict[k] assert v + 1 == pdict[k] pdict.store_if_not_present(2001, 2001) @@ -1019,7 +1019,7 @@ def test_nan_keys() -> None: pass for nan_value in nan_values: - assert nan_value != nan_value + assert nan_value != nan_value # noqa: PLR0124 with pytest.warns(NanKeyWarning): assert keyb(nan_value) == keyb(nan_value) diff --git a/pytools/test/test_pytools.py b/pytools/test/test_pytools.py index 4681f3a5..6affe010 100644 --- a/pytools/test/test_pytools.py +++ b/pytools/test/test_pytools.py @@ -416,7 +416,7 @@ def __getitem__(self, idx): def test_make_obj_array_iteration(): pytest.importorskip("numpy") - import pytools.obj_array as obj_array + from pytools import obj_array obj_array.new_1d([FakeArray()]) assert FakeArray.nopes == 0, FakeArray.nopes @@ -430,7 +430,7 @@ def test_obj_array_vectorize(c=1): np = pytest.importorskip("numpy") la = pytest.importorskip("numpy.linalg") - import pytools.obj_array as obj_array + from pytools import obj_array # {{{ functions From a16d3c6d1f874dcbfd6a1f7fd9c41e5ca832bfbe Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Fri, 20 Feb 2026 13:03:40 -0600 Subject: [PATCH 2/2] Update baseline --- .basedpyright/baseline.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.basedpyright/baseline.json b/.basedpyright/baseline.json index 5afafdf2..13cfaafd 100644 --- a/.basedpyright/baseline.json +++ b/.basedpyright/baseline.json @@ -823,7 +823,7 @@ "code": "reportGeneralTypeIssues", "range": { "startColumn": 41, - "endColumn": 42, + "endColumn": 45, "lineCount": 1 } }, @@ -4690,8 +4690,8 @@ { "code": "reportUnknownMemberType", "range": { - "startColumn": 20, - "endColumn": 38, + "startColumn": 16, + "endColumn": 34, "lineCount": 1 } },