Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 16 additions & 10 deletions pyhdtoolkit/utils/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,19 @@
import inspect
import traceback
import warnings
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, ParamSpec, TypeVar

if TYPE_CHECKING:
from collections.abc import Callable


P = ParamSpec("P") # for params
R = TypeVar("R") # for returns

# ----- Utility deprecation decorator ----- #


def deprecated(message: str = "") -> Callable:
def deprecated(message: str = "") -> Callable[[Callable[P, R]], Callable[P, R]]:
"""
Decorator to mark a function as deprecated. It will result in an
informative `DeprecationWarning` being issued with the provided
Expand All @@ -49,22 +52,23 @@ def old_function():
return "I am old!"
"""

def decorator_wrapper(func):
def decorator_wrapper(func: Callable[P, R]) -> Callable[P, R]:
last_call_sources: set[str] = set()

@functools.wraps(func)
def function_wrapper(*args, **kwargs):
def function_wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
current_call_source = "|".join(traceback.format_stack(inspect.currentframe()))
if current_call_source not in function_wrapper.last_call_source:

if current_call_source not in last_call_sources:
warnings.warn(
f"Function {func.__name__} is now deprecated and will be removed in a future release! {message}",
f"Function {func.__name__} is now deprecated and will be removed in a future release! {message}", # ty:ignore[unresolved-attribute]
category=DeprecationWarning,
stacklevel=2,
)
function_wrapper.last_call_source.add(current_call_source)
last_call_sources.add(current_call_source)

return func(*args, **kwargs)

function_wrapper.last_call_source = set()

return function_wrapper

return decorator_wrapper
Expand All @@ -73,7 +77,9 @@ def function_wrapper(*args, **kwargs):
# ----- Utility JIT Compilation decorator ----- #


def maybe_jit(func: Callable, **kwargs) -> Callable:
# We type hint to specify we return a function with the same
# signature as the input function.
def maybe_jit(func: Callable[P, R], **kwargs) -> Callable[P, R]:
"""
.. versionadded:: 1.7.0

Expand Down
14 changes: 7 additions & 7 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ dependencies = [
"numpy >= 2.0",
"pandas >= 2.0",
"matplotlib >=3.7",
"scipy >= 1.6",
"scipy >= 1.10",
"tfs-pandas >= 3.8",
"loguru < 1.0",
"cpymad >= 1.16",
Expand All @@ -71,22 +71,22 @@ dependencies = [
[project.optional-dependencies]
test = [
"pytest >= 8.0",
"pytest-cov >= 5.0",
"pytest-cov >= 6.0",
"pytest-xdist >= 3.0",
"numba >= 0.60.0",
"flaky >= 3.5",
"pytest-randomly >= 3.3",
"pytest-randomly >= 3.10",
"coverage[toml] >= 7.0",
"pytest-mpl >= 0.14",
]
dev = [
"ruff >= 0.5",
"ruff >= 0.12",
]
docs = [
"joblib >= 1.0",
"Sphinx >= 7.0",
"sphinx-rtd-theme >= 2.0",
"sphinx-issues >= 4.0",
"Sphinx >= 8.0",
"sphinx-rtd-theme >= 3.0",
"sphinx-issues >= 5.0",
"sphinx_copybutton < 1.0",
"sphinxcontrib-bibtex >= 2.4",
"sphinx-design >= 0.6",
Expand Down
Binary file modified tests/inputs/utils/correct_user_tasks.pkl
Binary file not shown.
16 changes: 8 additions & 8 deletions tests/test_cpymadtools/test_lhc.py
Original file line number Diff line number Diff line change
Expand Up @@ -487,7 +487,7 @@ def test_rigidity_knob_fails_on_invalid_ir(_non_matched_lhc_madx, caplog):
def test_rigidity_knob_fails_on_invalid_side(caplog, _non_matched_lhc_madx):
madx = _non_matched_lhc_madx

with pytest.raises(ValueError, match="Invalid value for parameter 'side'."):
with pytest.raises(ValueError, match=r"Invalid value for parameter 'side'."):
apply_lhc_rigidity_waist_shift_knob(madx, 1, 1, "invalid")

for record in caplog.records:
Expand Down Expand Up @@ -712,10 +712,10 @@ def test_get_bpms_coupling_rdts(_non_matched_lhc_madx, _reference_twiss_rdts):

twiss_with_rdts = get_lhc_bpms_twiss_and_rdts(madx)
# We separate the complex components to compare to the reference
twiss_with_rdts["F1001R"] = twiss_with_rdts.F1001.apply(np.real)
twiss_with_rdts["F1001I"] = twiss_with_rdts.F1001.apply(np.imag)
twiss_with_rdts["F1010R"] = twiss_with_rdts.F1010.apply(np.real)
twiss_with_rdts["F1010I"] = twiss_with_rdts.F1010.apply(np.imag)
twiss_with_rdts["F1001R"] = twiss_with_rdts.F1001.apply(np.real) # ty:ignore[unresolved-attribute]
twiss_with_rdts["F1001I"] = twiss_with_rdts.F1001.apply(np.imag) # ty:ignore[unresolved-attribute]
twiss_with_rdts["F1010R"] = twiss_with_rdts.F1010.apply(np.real) # ty:ignore[unresolved-attribute]
twiss_with_rdts["F1010I"] = twiss_with_rdts.F1010.apply(np.imag) # ty:ignore[unresolved-attribute]
twiss_with_rdts = twiss_with_rdts.drop(columns=["F1001", "F1010"]).set_index("NAME")
# Only care to compare the coupling RDTs columns
twiss_with_rdts = twiss_with_rdts.loc[:, ["F1001R", "F1001I", "F1010R", "F1010I"]]
Expand All @@ -727,8 +727,8 @@ def test_get_bpms_coupling_rdts(_non_matched_lhc_madx, _reference_twiss_rdts):
def test_k_modulation(_non_matched_lhc_madx, _reference_kmodulation):
madx = _non_matched_lhc_madx
results = do_kmodulation(madx)
assert all(var == 0 for var in results.ERRTUNEX)
assert all(var == 0 for var in results.ERRTUNEY)
assert np.all(results.ERRTUNEX.to_numpy() == 0) # ty:ignore[unresolved-attribute]
assert np.all(results.ERRTUNEY.to_numpy() == 0) # ty:ignore[unresolved-attribute]

reference = tfs.read(_reference_kmodulation)
assert_frame_equal(results.convert_dtypes(), reference.convert_dtypes()) # avoid dtype comparison error on 0 cols
Expand Down Expand Up @@ -841,7 +841,7 @@ def test_lhc_run3_setup_context_manager_raises_on_wrong_b4_conditions():
@pytest.mark.skipif(not (TESTS_DIR.parent / "acc-models-lhc").is_dir(), reason="acc-models-lhc not found")
def test_lhc_run3_setup_context_manager_raises_on_wrong_run_value():
with pytest.raises( # noqa: SIM117
NotImplementedError, match="This setup is only possible for Run 2 and Run 3 configurations."
NotImplementedError, match=r"This setup is only possible for Run 2 and Run 3 configurations."
): # using b4 with beam1 setup crashes
with LHCSetup(run=1, opticsfile="R2022a_A30cmC30cmA10mL200cm.madx") as madx: # noqa: F841
pass
Expand Down
2 changes: 1 addition & 1 deletion tests/test_plotting/test_aperture.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,5 +87,5 @@ def test_plot_physical_apertures_ir5_collision_vertical(_collision_aperture_tole
def test_plot_physical_apertures_raises_on_wrong_plane():
madx = Madx(stdout=False)

with pytest.raises(ValueError, match="Invalid 'plane' argument."):
with pytest.raises(ValueError, match=r"Invalid 'plane' argument."):
plot_physical_apertures(madx, plane="invalid")
2 changes: 1 addition & 1 deletion tests/test_plotting/test_envelope.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
def test_plot_enveloppe_raises_on_wrong_plane():
madx = Madx(stdout=False)

with pytest.raises(ValueError, match="Invalid 'plane' argument."):
with pytest.raises(ValueError, match=r"Invalid 'plane' argument."):
plot_beam_envelope(madx, "lhcb1", plane="invalid")


Expand Down
2 changes: 1 addition & 1 deletion tests/test_plotting/test_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,5 @@ def test_confidence_ellipse_fails_on_mismatched_dimensions():


def test_default_sbs_coupling_label_raises_on_wrong_component():
with pytest.raises(ValueError, match="Invalid component for coupling RDT."):
with pytest.raises(ValueError, match=r"Invalid component for coupling RDT."):
_determine_default_sbs_coupling_ylabel(rdt="f1001", component="NONEXISTANT")
4 changes: 2 additions & 2 deletions tests/test_plotting/test_phasespace.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ def test_plot_courant_snyder_phase_space_wrong_plane_input():
match_cas3(madx)
x_coords_stable, px_coords_stable = np.array([]), np.array([]) # no need for tracking

with pytest.raises(ValueError, match="Invalid 'plane' argument."):
with pytest.raises(ValueError, match=r"Invalid 'plane' argument."):
plot_courant_snyder_phase_space(madx, x_coords_stable, px_coords_stable, plane="invalid_plane")


Expand All @@ -97,7 +97,7 @@ def test_plot_courant_snyder_phase_space_colored_wrong_plane_input():
madx.input(BASE_LATTICE)
match_cas3(madx)
x_coords_stable, px_coords_stable = np.array([]), np.array([]) # no need for tracking
with pytest.raises(ValueError, match="Invalid 'plane' argument."):
with pytest.raises(ValueError, match=r"Invalid 'plane' argument."):
plot_courant_snyder_phase_space_colored(madx, x_coords_stable, px_coords_stable, plane="invalid_plane")


Expand Down
4 changes: 2 additions & 2 deletions tests/test_plotting/test_plotting_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def test_coupling_ylabel(f1001, f1010, abs_, real, imag):

@pytest.mark.parametrize("rdt", ["invalid", "F1111", "nope"])
def test_coupling_ylabel_raises_on_invalid_rdt(rdt):
with pytest.raises(ValueError, match="Invalid RDT for coupling plot."):
with pytest.raises(ValueError, match=r"Invalid RDT for coupling plot."):
_determine_default_sbs_coupling_ylabel(rdt, "abs")


Expand All @@ -69,7 +69,7 @@ def test_phase_ylabel(plane):

@pytest.mark.parametrize("plane", ["a", "Fb1", "nope", "not a plane"])
def test_phase_ylabel_raises_on_invalid_plane(plane):
with pytest.raises(ValueError, match="Invalid plane for phase plot."):
with pytest.raises(ValueError, match=r"Invalid plane for phase plot."):
_determine_default_sbs_phase_ylabel(plane)


Expand Down
2 changes: 1 addition & 1 deletion tests/test_plotting/test_sbs_phase.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ def test_plot_both_beams(sbs_phasex, sbs_phasey, sbs_model_b2):

@pytest.mark.parametrize("wrongplane", ["not", "accepted", "incorrect", ""])
def test_plot_phase_segment_raises_on_wrong_plane(wrongplane, sbs_phasex, sbs_model_b2):
with pytest.raises(ValueError, match="Invalid 'plane' argument."):
with pytest.raises(ValueError, match=r"Invalid 'plane' argument."):
plot_phase_segment(segment_df=sbs_phasex, model_df=sbs_model_b2, plane=wrongplane)


Expand Down
2 changes: 1 addition & 1 deletion tests/test_plotting/test_tunediagram.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def test_plot_tune_diagram_colored_by_resonance_order():
@pytest.mark.parametrize("max_order", [2, 3, 4, 5])
@pytest.mark.parametrize("differentiate", [False, True])
def test_plot_tune_diagram_arguments(figure_title, legend_title, max_order, differentiate):
figure, ax = plt.subplots(figsize=(10, 10))
_figure, ax = plt.subplots(figsize=(10, 10))
plot_tune_diagram(
title=figure_title,
legend_title=legend_title,
Expand Down
24 changes: 14 additions & 10 deletions tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import subprocess
import sys
import time
from collections.abc import Iterator

import numpy as np
import pandas as pd
Expand Down Expand Up @@ -45,7 +46,7 @@ def test_check_pid(self):
assert CommandLine.check_pid_exists(0) is True
assert CommandLine.check_pid_exists(int(1e6)) is False # default max PID is 32768 on linux, 99999 on macOS
with pytest.raises(TypeError):
CommandLine.check_pid_exists("not_an_integer")
CommandLine.check_pid_exists("not_an_integer") # ty:ignore[invalid-argument-type]

def test_run_cmd(self):
assert isinstance(CommandLine.run("echo hello"), tuple)
Expand All @@ -70,7 +71,10 @@ def test_terminate_nonexistent_pid(self, pid):
@pytest.mark.parametrize("sleep_time", list(range(10, 60))) # each one will spawn a different process
def test_terminate_pid(self, sleep_time):
sacrificed_process = subprocess.Popen(f"sleep {sleep_time}", shell=True)
assert CommandLine.terminate(sacrificed_process.pid) is True
try:
assert CommandLine.terminate(sacrificed_process.pid) is True
finally: # the process would hang, we make sure to cleanup
sacrificed_process.wait()


class TestHTCMonitor:
Expand Down Expand Up @@ -101,11 +105,11 @@ def test_cluster_table_creation(self, _condor_q_output):
assert isinstance(cluster_table, Table)

def test_tasks_table_creation(self, _condor_q_output, _taskless_condor_q_output):
user_tasks, cluster_info = read_condor_q(_condor_q_output)
user_tasks, _ = read_condor_q(_condor_q_output)
tasks_table = _make_tasks_table(user_tasks)
assert isinstance(tasks_table, Table)

user_tasks, cluster_info = read_condor_q(_taskless_condor_q_output)
user_tasks, _ = read_condor_q(_taskless_condor_q_output)
tasks_table = _make_tasks_table(user_tasks)
assert isinstance(tasks_table, Table)

Expand Down Expand Up @@ -133,7 +137,7 @@ def test_query_betastar_from_opticsfile(self):

def test_query_betastar_from_opticsfile_raises_on_invalid_symmetry_if_required(self):
with pytest.raises(
AssertionError, match="The betastar values for IP1 and IP5 are not the same in both planes."
AssertionError, match=r"The betastar values for IP1 and IP5 are not the same in both planes."
):
_misc.get_betastar_from_opticsfile(INPUTS_DIR / "madx" / "opticsfile.asymmetric", check_symmetry=True)

Expand Down Expand Up @@ -239,24 +243,24 @@ def _taskless_condor_q_output() -> str:


@pytest.fixture
def _correct_user_tasks() -> list[HTCTaskSummary]:
def _correct_user_tasks() -> Iterator[list[HTCTaskSummary]]:
pickle_file_path = INPUTS_DIR / "utils" / "correct_user_tasks.pkl"
with pickle_file_path.open("rb") as file:
return pickle.load(file)
yield pickle.load(file)


@pytest.fixture
def _correct_cluster_summary() -> ClusterSummary:
def _correct_cluster_summary() -> Iterator[ClusterSummary]:
pickle_file_path = INPUTS_DIR / "utils" / "correct_cluster_summary.pkl"
with pickle_file_path.open("rb") as file:
return pickle.load(file)
yield pickle.load(file)


@pytest.fixture
def _complex_columns_df() -> pd.DataFrame:
rng = np.random.default_rng()
array = rng.random(size=(50, 5)) + 1j * rng.random(size=(50, 5))
return pd.DataFrame(data=array, columns=["A", "B", "C", "D", "E"])
return pd.DataFrame(data=array, columns=["A", "B", "C", "D", "E"]) # ty:ignore[invalid-argument-type]


@pytest.fixture
Expand Down
26 changes: 13 additions & 13 deletions uv.lock

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