Skip to content

Commit

Permalink
Merge pull request #360 from BCG-Gamma/dev/2.1.1
Browse files Browse the repository at this point in the history
BUILD: Release pytools 2.1.1
  • Loading branch information
j-ittner authored Jun 5, 2023
2 parents 4e60319 + 02eecc5 commit 2ed53af
Show file tree
Hide file tree
Showing 11 changed files with 129 additions and 54 deletions.
9 changes: 8 additions & 1 deletion RELEASE_NOTES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,18 @@ Release Notes
*pytools* 2.1
-------------

2.1.1
~~~~~

- API: :class:`.AllTracker` now resolves forward references in type aliases
exported via ``__all__``


2.1.0
~~~~~

- API: new decorator :obj:`.fitted_only` to mark methods that may only be
called after their associated object has been fitted using :meth:`.FittableMixin.fit`.
called after their associated object has been fitted using :meth:`.FittableMixin.fit`
- API: remove method ``ensure_fitted`` from :class:`.FittableMixin`, which is no longer
needed due to the new decorator :obj:`.fitted_only`.
- API: new Sphinx callback :class:`.TrackCurrentClass` to keep track of the current
Expand Down
34 changes: 29 additions & 5 deletions azure-pipelines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ schedules:
displayName: Nightly full build
branches:
include:
- 2.0.x
- 2.1.x

resources:
repositories:
Expand Down Expand Up @@ -243,7 +243,15 @@ stages:
- script: dir $(Build.SourcesDirectory)

- script: |
conda install -y -c anaconda conda-build~=3.21 conda-verify~=3.4 toml~=0.10 flit~=3.6 packaging~=20.9
# install micromamba
curl -Ls https://micro.mamba.pm/api/micromamba/linux-64/latest | tar -xvj bin/micromamba
export MAMBA_ROOT_PREFIX=~/micromamba
eval "$(./bin/micromamba shell hook -s posix)"
# create and activate a build environment, then install the tools we need
micromamba create -n build
micromamba activate build
micromamba install -y -c conda-forge boa~=0.14 toml~=0.10 flit~=3.6 packaging~=20.9
displayName: 'Install conda-build, flit, toml'
condition: eq(variables['BUILD_SYSTEM'], 'conda')
Expand All @@ -261,7 +269,11 @@ stages:
targetType: 'inline'
script: |
set -eux
if [ "$BUILD_SYSTEM" = "conda" ] ; then eval "$(conda shell.bash hook)" ; fi
if [ "$BUILD_SYSTEM" = "conda" ] ; then
export MAMBA_ROOT_PREFIX=~/micromamba
eval "$(./bin/micromamba shell hook -s posix)"
micromamba activate build
fi
export RUN_PACKAGE_VERSION_TEST=$(project_name)
cd $(Build.SourcesDirectory)/$(project_root)
Expand Down Expand Up @@ -330,7 +342,15 @@ stages:
- script: dir $(Build.SourcesDirectory)

- script: |
conda install -y -c anaconda conda-build~=3.21 conda-verify~=3.4 toml~=0.10 flit~=3.6 packaging~=20.9
# install micromamba
curl -Ls https://micro.mamba.pm/api/micromamba/linux-64/latest | tar -xvj bin/micromamba
export MAMBA_ROOT_PREFIX=~/micromamba
eval "$(./bin/micromamba shell hook -s posix)"
# create and activate a build environment, then install the tools we need
micromamba create -n build
micromamba activate build
micromamba install -y -c conda-forge boa~=0.14 toml~=0.10 flit~=3.6 packaging~=20.9
displayName: 'Install conda-build, flit, toml'
condition: eq(variables['BUILD_SYSTEM'], 'conda')
Expand All @@ -348,7 +368,11 @@ stages:
targetType: 'inline'
script: |
set -eux
if [ "$BUILD_SYSTEM" = "conda" ] ; then eval "$(conda shell.bash hook)" ; fi
if [ "$BUILD_SYSTEM" = "conda" ] ; then
export MAMBA_ROOT_PREFIX=~/micromamba
eval "$(./bin/micromamba shell hook -s posix)"
micromamba activate build
fi
export RUN_PACKAGE_VERSION_TEST=$(project_name)
cd $(Build.SourcesDirectory)/$(project_root)
Expand Down
45 changes: 17 additions & 28 deletions environment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,36 +4,25 @@ channels:
- bcg_gamma
dependencies:
# run
- joblib ~= 1.2
- matplotlib ~= 3.6
- numpy ~= 1.23
- pandas ~= 2.0
- python ~= 3.9
- scipy ~= 1.10
- joblib ~= 1.2
- matplotlib ~= 3.7
- numpy ~= 1.24
- pandas ~= 2.0
- python ~= 3.9
- scipy ~= 1.10
- typing_extensions ~= 4.3
- typing_inspect ~= 0.7
# build/test
- conda-build ~= 3.23.3
- conda-verify ~= 3.1.1
- docutils ~= 0.17.1
- flit ~= 3.8.0
- jinja2 ~= 2.11.3
- markupsafe ~= 2.0.1 # markupsafe 2.1 breaks support for jinja2
- m2r ~= 0.3.1
- pluggy ~= 0.13.1
- pre-commit ~= 2.21.0
- pytest ~= 7.2.1
- typing_inspect ~= 0.7
# test
- pytest ~= 7.2.1
- pytest-cov ~= 2.12.1
- pyyaml ~= 5.4.1
- toml ~= 0.10.2
- tox ~= 3.27.1
- yaml ~= 0.2.5
# sphinx
- nbsphinx ~= 0.8.9
- sphinx ~= 4.5.0
- nbsphinx ~= 0.8.9
- sphinx ~= 4.5.0
- sphinx-autodoc-typehints ~= 1.19.2
- pydata-sphinx-theme ~= 0.8.1
- pydata-sphinx-theme ~= 0.8.1
# notebooks
- jupyterlab ~= 3.5.2
- openpyxl ~= 3.0.10
- seaborn ~= 0.12.2
- ipywidgets ~= 8.0
- jupyterlab ~= 3.5
- openpyxl ~= 3.0
- seaborn ~= 0.12
- tableone ~= 0.7
2 changes: 1 addition & 1 deletion make.py
Original file line number Diff line number Diff line change
Expand Up @@ -451,7 +451,7 @@ def build(self, exposed_package_dependencies: Mapping[str, str]) -> None:
)

os.makedirs(build_path, exist_ok=True)
build_cmd = f"conda-build -c conda-forge -c bcg_gamma {recipe_path}"
build_cmd = f"conda mambabuild -c conda-forge -c bcg_gamma {recipe_path}"
log(
f"Building: {self.project}\n"
f"Build path: {build_path}\n"
Expand Down
10 changes: 5 additions & 5 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ requires = [
"matplotlib ~=3.0",
"numpy >=1.21,<2a", # cannot use ~= due to conda bug
"pandas >=1",
"scipy ~=1.2",
"typing_inspect ~=0.4",
"scipy ~=1.6",
"typing_inspect ~=0.7",
"typing_extensions ~=4.0",
]

Expand Down Expand Up @@ -73,8 +73,8 @@ matplotlib = "~=3.0.3"
numpy = "==1.21.6" # cannot use ~= due to conda bug
pandas = "~=1.0.5"
python = ">=3.7.12,<3.8a" # cannot use ~= due to conda bug
scipy = "~=1.2.1"
typing_inspect = "~=0.4.0"
scipy = "~=1.6.3"
typing_inspect = "~=0.7.1"
typing_extensions = "~=4.0.0"

[build.matrix.max]
Expand All @@ -84,7 +84,7 @@ matplotlib = "~=3.5"
numpy = ">=1.24,<2a" # cannot use ~= due to conda bug
pandas = "~=2.0"
python = ">=3.9,<4a" # cannot use ~= due to conda bug
scipy = "~=1.8"
scipy = "~=1.10"
typing_inspect = "~=0.7"
typing_extensions = "~=4.3"

Expand Down
2 changes: 1 addition & 1 deletion src/pytools/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""
A collection of Python extensions and tools used in BCG GAMMA's open-source libraries.
"""
__version__ = "2.1.0"
__version__ = "2.1.1"
37 changes: 32 additions & 5 deletions src/pytools/api/_alltracker.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
get_type_hints,
)

from typing_inspect import get_args, is_forward_ref

log = logging.getLogger(__name__)

__all__ = [
Expand Down Expand Up @@ -162,6 +164,15 @@ def validate(self) -> None:
for name in all_expected:
obj = globals_[name]

if self.update_forward_references:
if is_forward_ref(obj) or get_args(obj):
# if the object is a forward reference or has type arguments,
# we have a type alias and will substitute all forward references
globals_[name] = obj = self._evaluate_type_alias(alias=obj)
else:
# update forward references in annotations
update_forward_references(obj, globals_=globals_)

# check that the object was defined locally
try:
obj_module = obj.__module__
Expand All @@ -176,7 +187,9 @@ def validate(self) -> None:
if forbid_imported_definitions and obj_module and obj_module != module:
raise AssertionError(
f"{_qualname(obj)} is exported by module {module} "
f"but defined in module {obj_module}"
f"but defined in module {obj_module}; "
"remove it from __all__, or create the AllTracker with parameter "
"allow_imported_definitions=True"
)

# set public module field
Expand All @@ -186,10 +199,6 @@ def validate(self) -> None:
# objects without a __dict__ will not permit setting the public module
pass

if self.update_forward_references:
# update forward references in annotations
update_forward_references(obj, globals_=globals_)

def get_tracked(self) -> List[str]:
"""
List the names of all locally tracked, public items.
Expand All @@ -216,6 +225,24 @@ def _is_eligible(self, name: str) -> bool:
# check if the given item is eligible for tracking by this tracker
return not (name.startswith("_") or name in self._imported)

def _evaluate_type_alias(self, alias: Any) -> Any:
# evaluate a type alias by creating a dummy class with a field annotation
# so we can use get_type_hints() to resolve the forward reference

# define the dummy name we'll use for the field we'll annotate
dummy_field = "x"
# create a dummy class with a field annotation
cls = type(
"ClassWithTypeAlias",
(),
dict(__annotations__={dummy_field: alias}),
)
# get the type hints for the class and return the evaluated type alias
# from the dummy field
alias_resolved = get_type_hints(cls, globalns=self._globals)[dummy_field]
alias_resolved.__module__ = self._module
return alias_resolved

def __getitem__(self, name: str) -> Any:
# get a tracked item by name

Expand Down
2 changes: 1 addition & 1 deletion src/pytools/meta/_meta.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ class SingletonMeta(type):
Singleton classes must not accept any parameters upon instantiation.
"""

__instance_ref: "Optional[ReferenceType[Any]]"
__instance_ref: Optional[ReferenceType] # type: ignore

def __init__(cls: SingletonMeta, *args: Any, **kwargs: Any) -> None:
"""
Expand Down
6 changes: 3 additions & 3 deletions src/pytools/viz/distribution/_distribution.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,16 +92,16 @@ def _iqr_annotation(multiple: float) -> str:

@inheritdoc(match="[see superclass]")
class ECDFDrawer(Drawer[ArrayLike, ECDFStyle]):
"""
r"""
Drawer for empirical cumulative density functions (ECDFs), highlighting
outliers using Tukey's outlier test.
The drawer highlights samples as `outliers` or `far outliers`.
A sample is considered an outlier if it is outside the range
:math:`[q_1 - m * \\mathit{iqr}, q_3 + m * \\mathit{iqr}]`
:math:`[q_1 - m \cdot \mathit{iqr}, q_3 + m \cdot \mathit{iqr}]`
where :math:`q_1` and :math:`q_3` are the lower and upper quartiles,
:math:`\\mathit{iqr} = q3 - q1` is the `inter-quartile range (IQR)`, and
:math:`\mathit{iqr} = q3 - q1` is the `inter-quartile range (IQR)`, and
:math:`m` is the `IQR multiple`.
By convention, common values for :math:`m` are :math:`m = 1.5` for outliers,
Expand Down
12 changes: 8 additions & 4 deletions test/test/pytools/test_api.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
"""
Basic test cases for the `pytools.api` module
"""
from typing import Any, Dict
from typing import Any, Dict, Union

import pytest
from typing_extensions import TypeAlias

from pytools.api import (
AllTracker,
Expand All @@ -18,6 +19,7 @@
)

PKG_TEST_PYTOOLS_TEST_API = "test.pytools.test_api"
MyTypeAlias: TypeAlias = Union["str", int]


def test_deprecated() -> None:
Expand Down Expand Up @@ -190,7 +192,7 @@ class _C:
# test with defaults, no constant declaration

mock_globals: Dict[str, Any] = dict(
__all__=["A", "B"],
__all__=["A", "B", "MyTypeAlias"],
__name__=PKG_TEST_PYTOOLS_TEST_API,
)
tracker = AllTracker(mock_globals)
Expand All @@ -200,14 +202,16 @@ class _C:
A=A,
B=B,
_C=_C,
MyTypeAlias=MyTypeAlias,
)
)
tracker.validate()

assert tracker.get_tracked() == ["A", "B"]
assert tracker.get_tracked() == ["A", "B", "MyTypeAlias"]
assert tracker.is_tracked("A", A)
assert tracker.is_tracked("B", B)
assert not tracker.is_tracked("_C", _C)
assert mock_globals["MyTypeAlias"] == Union[str, int]

# test with defaults, with constant declaration

Expand Down Expand Up @@ -267,7 +271,7 @@ class _C:
AssertionError,
match=(
r"A is exported by module test\.pytools\.test_api_other but defined in "
r"module test\.pytools\.test_api$"
r"module test\.pytools\.test_api"
),
):
tracker.validate()
Expand Down
24 changes: 24 additions & 0 deletions test/test/pytools/test_meta.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
"""
Tests for the pytools.meta package
"""
from pytools.meta import SingletonABCMeta, SingletonMeta


def test_singleton_meta() -> None:
"""
Test the singleton metaclasses
"""

class TestSingleton(metaclass=SingletonMeta):
"""
Test class for the singleton metaclass
"""

assert TestSingleton() is TestSingleton()

class TestSingletonABC(metaclass=SingletonABCMeta):
"""
Test class for the singleton ABC metaclass
"""

assert TestSingletonABC() is TestSingletonABC()

0 comments on commit 2ed53af

Please sign in to comment.