Skip to content

Commit

Permalink
Merge pull request #380 from BCG-X-Official/dev/3.0.2
Browse files Browse the repository at this point in the history
  • Loading branch information
j-ittner authored Jul 4, 2024
2 parents 8f16c28 + 8cbf1e9 commit 78b171d
Show file tree
Hide file tree
Showing 16 changed files with 146 additions and 55 deletions.
2 changes: 1 addition & 1 deletion .idea/pytools.iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,6 @@ repos:
language_version: python310
pass_filenames: false
additional_dependencies:
- numpy~=1.24
- numpy~=2.0
- pytest
- packaging
8 changes: 8 additions & 0 deletions RELEASE_NOTES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@ Release Notes
*pytools* 3.0 adds support for language features introduced up to and including
Python 3.10, and drops support for Python versions.

*pytools* 3.0.2
~~~~~~~~~~~~~~~

- BUILD: :mod:`numpy` |nbsp| 2 is now supported
- FIX: :func:`.issubclass_generic` now supports unions, tuples of types, and ``None``,
and uses clearer error messages if called with invalid arguments


*pytools* 3.0.1
~~~~~~~~~~~~~~~

Expand Down
2 changes: 1 addition & 1 deletion azure-pipelines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ stages:
- script: |
# package dependencies for mypy
dependencies=(
numpy~=1.24
numpy~=2.0
packaging
pytest
)
Expand Down
2 changes: 1 addition & 1 deletion environment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ dependencies:
# run
- joblib ~= 1.2
- matplotlib ~= 3.6
- numpy ~= 1.24
- numpy ~= 2.0
- pandas ~= 2.0
- python ~= 3.10.14
- scipy ~= 1.10
Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ license = "Apache Software License v2.0"
requires = [
"joblib ~=1.0",
"matplotlib ~=3.6",
"numpy >=1.23,<2a", # cannot use ~= due to conda bug
"numpy >=1.23,<3a", # cannot use ~= due to conda bug
"pandas >=1.5",
"scipy ~=1.9",
"typing_inspect ~=0.7",
Expand Down Expand Up @@ -80,7 +80,7 @@ typing_extensions = "~=4.0.0"
# maximum requirements of gamma-pytools
joblib = "~=1.3"
matplotlib = "~=3.8"
numpy = ">=1.26,<2a" # cannot use ~= due to conda bug
numpy = ">=2,<3a" # cannot use ~= due to conda bug
pandas = "~=2.0"
python = ">=3.12,<4a" # cannot use ~= due to conda bug
scipy = "~=1.12"
Expand Down
2 changes: 1 addition & 1 deletion src/pytools/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
A collection of Python extensions and tools used in BCG GAMMA's open-source libraries.
"""

__version__ = "3.0.1"
__version__ = "3.0.2"
2 changes: 1 addition & 1 deletion src/pytools/data/_linkage.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
# Type variables
#

LinkageMatrix = npt.NDArray[np.float_]
LinkageMatrix = npt.NDArray[np.float64]


#
Expand Down
16 changes: 8 additions & 8 deletions src/pytools/data/_matrix.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
#

T_Matrix = TypeVar("T_Matrix", bound="Matrix[Any]")
T_Number = TypeVar("T_Number", bound="np.number[npt.NBitBase]")
T_Number = TypeVar("T_Number", bound="np.number[Any]")

#
# Ensure all symbols introduced below are included in __all__
Expand All @@ -59,7 +59,7 @@ class Matrix(HasExpressionRepr, Generic[T_Number]):
names: tuple[npt.NDArray[Any] | None, npt.NDArray[Any] | None]

#: the weights of the rows and columns
weights: tuple[npt.NDArray[np.float_] | None, npt.NDArray[np.float_] | None]
weights: tuple[npt.NDArray[np.float64] | None, npt.NDArray[np.float64] | None]

#: the labels for the row and column axes
name_labels: tuple[str | None, str | None]
Expand Down Expand Up @@ -155,8 +155,8 @@ def _arg_to_array(
else:

def _ensure_positive(
w: npt.NDArray[np.float_] | None, axis: int
) -> npt.NDArray[np.float_] | None:
w: npt.NDArray[np.float64] | None, axis: int
) -> npt.NDArray[np.float64] | None:
if w is not None and (w < 0).any():
raise ValueError(
f"arg weights[{axis}] should be all positive, "
Expand Down Expand Up @@ -352,7 +352,7 @@ def _message(error: str) -> str:


def _top_items_mask(
weights: npt.NDArray[np.float_] | None,
weights: npt.NDArray[np.float64] | None,
current_size: int,
target_size: tuple[int | None, float | None],
) -> npt.NDArray[np.bool_]:
Expand Down Expand Up @@ -385,7 +385,7 @@ def _top_items_mask(
# THe target weight is expressed as a ratio of total weight
# (0 < target_ratio <= 1).

weights_sorted_cumsum: npt.NDArray[np.float_] = weights[
weights_sorted_cumsum: npt.NDArray[np.float64] = weights[
ix_weights_descending_stable
].cumsum()
mask[
Expand All @@ -401,12 +401,12 @@ def _top_items_mask(

def _resize_rows(
values: npt.NDArray[T_Number],
weights: npt.NDArray[np.float_] | None,
weights: npt.NDArray[np.float64] | None,
names: npt.NDArray[Any] | None,
current_size: int,
target_size: tuple[int | None, float | None],
) -> tuple[
npt.NDArray[T_Number], npt.NDArray[np.float_] | None, npt.NDArray[Any] | None
npt.NDArray[T_Number], npt.NDArray[np.float64] | None, npt.NDArray[Any] | None
]:
mask = _top_items_mask(
weights=weights, current_size=current_size, target_size=target_size
Expand Down
83 changes: 64 additions & 19 deletions src/pytools/typing/_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
Sequence,
ValuesView,
)
from types import GenericAlias
from types import GenericAlias, NoneType, UnionType
from typing import (
AbstractSet,
Any,
Expand Down Expand Up @@ -373,13 +373,13 @@ def get_type_arguments(obj: Any, base: type) -> list[tuple[type, ...]]:
return list(map(get_args, get_generic_instance(ti.get_generic_type(obj), base)))


def issubclass_generic(subclass: type | Never, base: type | Never) -> bool:
def issubclass_generic(subclass: Any, base: Any) -> bool:
"""
Check if a class is a subclass of a generic instance, i.e., it is a subclass of the
generic class, and has compatible type arguments.
:param subclass: the class to check
:param base: the generic class to check against
:param subclass: the (potentially generic) subclass to check
:param base: the (potentially generic) base class to check against
:return: ``True`` if the class is a subclass of the generic instance, ``False``
otherwise
"""
Expand All @@ -396,16 +396,56 @@ def issubclass_generic(subclass: type | Never, base: type | Never) -> bool:
elif base is Never:
return False

# Special case: if the subclass is a union type, check if all types in the union are
# subclasses of the base class
if get_origin(subclass) in (typing.Union, UnionType):
return all(issubclass_generic(arg, base) for arg in get_args(subclass))

# Special case: if the base class is a union type, check if the subclass is a
# subclass of at least one of the types in the union
if get_origin(base) in (typing.Union, UnionType):
return any(issubclass_generic(subclass, arg) for arg in get_args(base))

# Special case: if the base class is a tuple, check if the subclass is a subclass of
# at least one type in the tuple
if isinstance(base, tuple):
try:
return any(issubclass_generic(subclass, arg) for arg in base)
except TypeError as e:
raise TypeError(
f"isinstance_generic() arg 2 must be a type, type-like, or tuple of "
f"types or type-likes, but got {base!r}"
) from e

# Typehints can contain `None` as a shorthand for `NoneType`; replace it with the
# actual type
if subclass is None:
subclass = NoneType
if base is None:
base = NoneType

# Replace deprecated types in typing with their canonical replacements in
# collections.abc
subclass = _replace_deprecated_type(subclass)
base = _replace_deprecated_type(base)

# Get the non-generic origin of the base class
base_origin = get_origin(base) or base
if not isinstance(base_origin, type):
raise TypeError(
f"isinstance_generic() arg 2 must be a type, type-like, or tuple of types "
f"or type-likes, but got {base!r}"
)

# If the non-generic origin of the subclass is not a subclass of the non-generic
# origin of the base class, the subclass cannot be a subclass of the base class
subclass_origin = get_origin(subclass) or subclass
if not issubclass(subclass_origin, base_origin):
if not isinstance(subclass_origin, type):
raise TypeError(
f"isinstance_generic() arg 1 must be a type or type-like, but got "
f"{subclass!r}"
)
elif not issubclass(subclass_origin, base_origin):
return False

# If the base class is not a generic class, there are no type arguments to check
Expand Down Expand Up @@ -567,7 +607,7 @@ def _get_origin_parameters(
)


def _replace_deprecated_type(tp: type) -> type:
def _replace_deprecated_type(tp: T) -> T:
"""
Replace deprecated types in :mod:`typing` with their canonical replacements in
:mod:`collections.abc`.
Expand All @@ -577,18 +617,23 @@ def _replace_deprecated_type(tp: type) -> type:
deprecated
"""

if tp.__module__ == "typing":
origin: type | None = get_origin(tp)
if (
# Check if the same type is defined in collections.abc
origin: type | None = get_origin(tp)
if origin is not None and origin.__module__ == "collections.abc":
log.warning(
f"Type typing.{tp.__name__} is deprecated; "
f"please use {origin.__module__}.{origin.__name__} instead"
)
args: tuple[type, ...] = get_args(tp)
if args:
# If the type has arguments, apply the same arguments to the replacement
return cast(type, origin[args]) # type: ignore[index]
else:
return origin
origin is not None
and tp.__module__ == "typing"
and origin.__module__ == "collections.abc"
):
log.warning(
"Type typing.%s is deprecated; please use %s.%s instead",
tp.__name__, # type: ignore[attr-defined]
origin.__module__,
origin.__name__,
)
args: tuple[type, ...] = get_args(tp)
if args:
# If the type has arguments, apply the same arguments to the replacement
return cast(T, origin[args]) # type: ignore[index]
else:
return cast(T, origin)
return tp
6 changes: 3 additions & 3 deletions src/pytools/viz/_matplot.py
Original file line number Diff line number Diff line change
Expand Up @@ -304,13 +304,13 @@ def color_for_value(self, z: int | float) -> RgbaColor:
pass

@overload
def color_for_value(self, z: npt.NDArray[np.float_]) -> npt.NDArray[np.float_]:
def color_for_value(self, z: npt.NDArray[np.float64]) -> npt.NDArray[np.float64]:
"""[overload]"""
pass

def color_for_value(
self, z: int | float | npt.NDArray[np.float_]
) -> RgbaColor | npt.NDArray[np.float_]:
self, z: int | float | npt.NDArray[np.float64]
) -> RgbaColor | npt.NDArray[np.float64]:
"""
Get the color(s) associated with the given value(s), based on the color map and
normalization defined for this style.
Expand Down
4 changes: 2 additions & 2 deletions src/pytools/viz/dendrogram/_style.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ def draw_leaf_labels(

def _get_ytick_locations(
self, *, weights: Sequence[float]
) -> npt.NDArray[np.float_]:
) -> npt.NDArray[np.float64]:
"""
Get the tick locations for the y axis.
Expand All @@ -231,7 +231,7 @@ def _get_ytick_locations(
"""
weights_array = np.array(weights)
# noinspection PyTypeChecker
ytick_locations: npt.NDArray[np.float_] = -(
ytick_locations: npt.NDArray[np.float64] = -(
np.arange(len(weights)) * self.padding
+ weights_array.cumsum()
- weights_array / 2
Expand Down
22 changes: 11 additions & 11 deletions src/pytools/viz/matrix/_matrix.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,25 +155,25 @@ def draw_matrix(
npt.NDArray[Any] | None,
],
weights: tuple[
npt.NDArray[np.float_] | None,
npt.NDArray[np.float_] | None,
npt.NDArray[np.float64] | None,
npt.NDArray[np.float64] | None,
],
) -> None:
"""[see superclass]"""
ax: Axes = self.ax
colors = self.colors

weights_rows: npt.NDArray[np.float_]
weights_columns: npt.NDArray[np.float_]
weights_rows: npt.NDArray[np.float64]
weights_columns: npt.NDArray[np.float64]
# replace undefined weights with all ones
weights_rows, weights_columns = tuple(
np.ones(n) if w is None else w for w, n in zip(weights, data.shape)
)

# calculate the horizontal and vertical matrix cell bounds based on the
# cumulative sums of the axis weights; default all weights to 1 if not defined
column_bounds: npt.NDArray[np.float_]
row_bounds: npt.NDArray[np.float_]
column_bounds: npt.NDArray[np.float64]
row_bounds: npt.NDArray[np.float64]

row_bounds = -np.array([0, *weights_rows]).cumsum()
column_bounds = np.array([0, *weights_columns]).cumsum()
Expand All @@ -200,7 +200,7 @@ def draw_matrix(
# draw the matrix cells
for c, (x0, x1) in enumerate(zip(column_bounds, column_bounds[1:])):
for r, (y1, y0) in enumerate(zip(row_bounds, row_bounds[1:])):
color: npt.NDArray[np.float_] = cell_colors[r, c]
color: npt.NDArray[np.float64] = cell_colors[r, c]
ax.add_patch(
Rectangle(
(
Expand All @@ -224,7 +224,7 @@ def draw_matrix(
y_tick_locations = (row_bounds[:-1] + row_bounds[1:]) / 2

def _set_ticks(
tick_locations: npt.NDArray[np.float_],
tick_locations: npt.NDArray[np.float64],
tick_labels: npt.NDArray[Any],
axis: Axis,
tick_params: dict[str, Any],
Expand Down Expand Up @@ -461,15 +461,15 @@ def draw_matrix(
npt.NDArray[Any] | None,
],
weights: tuple[
npt.NDArray[np.float_] | None,
npt.NDArray[np.float_] | None,
npt.NDArray[np.float64] | None,
npt.NDArray[np.float64] | None,
],
) -> None:
"""[see superclass]"""

def _axis_marks(
axis_names: npt.NDArray[Any] | None,
axis_weights: npt.NDArray[np.float_] | None,
axis_weights: npt.NDArray[np.float64] | None,
) -> Iterable[str] | None:
axis_names_iter: Iterable[Any]

Expand Down
4 changes: 2 additions & 2 deletions src/pytools/viz/matrix/base/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,8 @@ def draw_matrix(
npt.NDArray[Any] | None,
],
weights: tuple[
npt.NDArray[np.float_] | None,
npt.NDArray[np.float_] | None,
npt.NDArray[np.float64] | None,
npt.NDArray[np.float64] | None,
],
) -> None:
"""
Expand Down
Loading

0 comments on commit 78b171d

Please sign in to comment.