From 7cb1f5401bf096bd5e434fb958bed7c072869716 Mon Sep 17 00:00:00 2001 From: derrynknife Date: Sat, 17 Oct 2026 22:01:20 +1000 Subject: [PATCH] 0.15: version, CHANGELOG, and what 0.14 deprecated removed NodeState's phase, renamed since_scheduled in 0.14 (#350), is refused; NEXT_REMOVAL moves to 0.16 (0.15 deprecates nothing). Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_019Km2eWBiPJ5JNkme3vRg4B --- CHANGELOG.md | 18 +++++++ CLAUDE.md | 13 +++-- CONTRIBUTING.md | 6 ++- docs/guide/saving.md | 2 +- repyability/_version.py | 2 +- repyability/rbd/node_state.py | 65 +---------------------- repyability/tests/test_api_consistency.py | 31 ----------- repyability/tests/test_removed_in_0_15.py | 15 ++++++ repyability/utils/deprecation.py | 41 +++++++------- 9 files changed, 65 insertions(+), 128 deletions(-) create mode 100644 repyability/tests/test_removed_in_0_15.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 7bca3af..abf071f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,19 @@ other release, fixes included, the minor. ## [Unreleased] +## [0.15] - 2026-10-17 + +surpyval 0.25, and a joint model in a diagram. A unit of a surpyval joint +model of readings and failures (`surpyval.WeibullJointPH`), given its +readings so far, is a block of a `NonRepairableRBD` (`JointNode`, or its +`DynamicPrediction` given as the node): its reliability is from now, and +it takes part in every analysis of the diagram. A `RepairableRBD` refuses +it, as the units that replace it are new and without its readings. What +0.14 deprecated is gone. + +Behaviour changes: surpyval 0.25 is required; `NodeState`'s `phase` is +refused (it is `since_scheduled`). + ### Added - A unit of a surpyval joint model of readings and failures, given its @@ -27,6 +40,11 @@ other release, fixes included, the minor. - surpyval 0.25 is required (for its joint models). +### Removed + +- `NodeState`'s `phase`, deprecated in 0.14 and renamed `since_scheduled` + (#350): giving it raises `TypeError`, and a state has no `phase`. + ## [0.14] - 2026-10-16 Fitted models in, every kind of model brought up to the diagram, and diff --git a/CLAUDE.md b/CLAUDE.md index 6a05810..8aba763 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -435,13 +435,12 @@ or later. - **Deprecations** follow CONTRIBUTING (one minor release's notice, a `FutureWarning` through `repyability/utils/deprecation.py`). Each release's removals stay gone through `test_removed_in_0_12.py`, - `_0_13.py` and `_0_14.py`: a removed argument raises `TypeError`. What - 0.14 deprecates goes in 0.15 (`NEXT_REMOVAL`): `NodeState`'s `phase`, - renamed `since_scheduled` (#350, through `deprecation.renamed` for the - argument and `renamed_attribute` for reading it). A new deprecation - warns through those, `called` or `ignored`; - `test_what_0_14_deprecates_goes_in_0_15` fails once the version reaches - it. + `_0_13.py`, `_0_14.py` and `_0_15.py` (`NodeState`'s `phase`, #350): + a removed argument raises `TypeError`. 0.15 deprecates nothing; what it + would deprecate goes in 0.16 (`NEXT_REMOVAL`). A new deprecation warns + through `deprecation.renamed`, `renamed_attribute`, `called` or + `ignored`, with a test that fails once the version reaches its removal + (as `test_what_0_14_deprecates_goes_in_0_15` did). - **No result depends on the unit of time (or of a covariate).** A step, grid or probe is sized from the models (a typical life, `failure_time_scale`), never in absolute time, and a proportional diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 853af90..b61c942 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -104,8 +104,10 @@ go once the minimum surpyval version has the fix. A deprecation warns (a `FutureWarning`, through `repyability/utils/deprecation.py`) for one minor release, and the next -removes it. `test_what_0_14_deprecates_goes_in_0_15` and its kind fail once -the version reaches the removal, as a reminder. +removes it. A test of the kind `test_what_0_14_deprecates_goes_in_0_15` +(in `test_api_consistency.py`) fails once the version reaches the removal, +as a reminder; the removed names then stay gone through +`test_removed_in_X_Y.py`. ## Releases (maintainers) diff --git a/docs/guide/saving.md b/docs/guide/saving.md index ad20eb8..8c50c0f 100644 --- a/docs/guide/saving.md +++ b/docs/guide/saving.md @@ -34,7 +34,7 @@ path = Path(tempfile.mkdtemp()) / "plant.json" rbd.to_json(path) # written; returns None NonRepairableRBD.from_json(path).sf(30) == rbd.sf(30) # True type(RBD.from_dict(data)).__name__ # 'NonRepairableRBD': the base class dispatches on type -data["type"], data["repyability_version"] # ('NonRepairableRBD', '0.14') +data["type"], data["repyability_version"] # ('NonRepairableRBD', '0.15') ``` What is saved: diff --git a/repyability/_version.py b/repyability/_version.py index 1e8b0e3..2d6e9aa 100644 --- a/repyability/_version.py +++ b/repyability/_version.py @@ -5,4 +5,4 @@ dependencies) via ``[tool.setuptools.dynamic]`` in ``pyproject.toml``. """ -__version__ = "0.14" +__version__ = "0.15" diff --git a/repyability/rbd/node_state.py b/repyability/rbd/node_state.py index fe88f81..7786b27 100644 --- a/repyability/rbd/node_state.py +++ b/repyability/rbd/node_state.py @@ -16,18 +16,8 @@ from dataclasses import dataclass from typing import Optional -from repyability.utils.deprecation import renamed, renamed_attribute -#: The release that refuses ``phase``, renamed ``since_scheduled`` in 0.14 -#: (#350). -_PHASE_REMOVAL = "0.15" -_PHASE_WHY = ( - "it is the time since the last scheduled replacement or test, and a " - "phased mission has phases of its own" -) - - -@dataclass(frozen=True, init=False) +@dataclass(frozen=True) class NodeState: """The current condition of a single RBD node. @@ -93,9 +83,6 @@ class NodeState: ``virtual_age`` now and ``since_failure``: give ``age= since_failure`` and ``virtual_age=virtual_age - since_failure``. The simulations take it. - phase : float, optional - Deprecated: ``since_scheduled``, which it is renamed (#350), as a - phased mission has phases of its own. Refused in 0.15. Raises ------ @@ -104,8 +91,6 @@ class NodeState: is negative or not finite, ``down_for`` or ``maintenance`` is given for a component that is alive, or another field is given with ``stationary``. - TypeError - If ``since_scheduled`` is given under both names. Notes ----- @@ -162,54 +147,6 @@ class NodeState: stationary: bool = False virtual_age: Optional[float] = None - def __init__( - self, - age: float = 0.0, - alive: bool = True, - down_for: float = 0.0, - maintenance: bool = False, - since_scheduled: Optional[float] = None, - stationary: bool = False, - virtual_age: Optional[float] = None, - *, - phase: Optional[float] = None, - ) -> None: - if phase is not None: - if since_scheduled is not None: - raise TypeError( - "NodeState takes since_scheduled alone: phase is its " - "old name." - ) - renamed( - "NodeState", - "phase", - "since_scheduled", - _PHASE_WHY, - removal=_PHASE_REMOVAL, - ) - since_scheduled = phase - object.__setattr__(self, "age", age) - object.__setattr__(self, "alive", alive) - object.__setattr__(self, "down_for", down_for) - object.__setattr__(self, "maintenance", maintenance) - object.__setattr__(self, "since_scheduled", since_scheduled) - object.__setattr__(self, "stationary", stationary) - object.__setattr__(self, "virtual_age", virtual_age) - self.__post_init__() - - @property - def phase(self) -> Optional[float]: - """``since_scheduled``, by its old name: deprecated, and refused in - 0.15 (#350).""" - renamed_attribute( - "NodeState", - "phase", - "since_scheduled", - _PHASE_WHY, - removal=_PHASE_REMOVAL, - ) - return self.since_scheduled - def __post_init__(self) -> None: if self.age < 0: raise ValueError( diff --git a/repyability/tests/test_api_consistency.py b/repyability/tests/test_api_consistency.py index 4df96b5..dd15a18 100644 --- a/repyability/tests/test_api_consistency.py +++ b/repyability/tests/test_api_consistency.py @@ -30,7 +30,6 @@ mtbf_pass_probability, mtbf_test_time, ) -from repyability._version import __version__ from repyability.rbd.ccf import with_parameters from repyability.rbd.uncertainty import draw_ccf_models from repyability.utils import deprecation @@ -261,13 +260,6 @@ def test_the_spares_demand_values_are_properties(): assert demand.std == pytest.approx(np.sqrt(10.0), rel=1e-4) -def test_what_0_14_deprecates_goes_in_0_15(): - # NodeState's phase, renamed since_scheduled (#350): giving phase= and - # reading .phase warn, and NEXT_REMOVAL (0.15) refuses them. - version = tuple(map(int, __version__.split("."))) - assert version < tuple(map(int, NEXT_REMOVAL.split("."))) - - def test_a_deprecation_warns_until_the_next_removal(): # The helpers a deprecation warns through. match = f"{NEXT_REMOVAL} will refuse it" @@ -284,29 +276,6 @@ def test_a_deprecation_warns_until_the_next_removal(): assert np.array_equal(array(), [1.0, 2.0]) -def test_a_node_state_takes_phase_as_since_scheduled_for_now(): - with pytest.warns( - FutureWarning, match="phase= is renamed since_scheduled" - ): - state = NodeState(age=10.0, phase=4.0) - assert state.since_scheduled == 4.0 - assert state == NodeState(age=10.0, since_scheduled=4.0) - assert ( - repr(state) == "NodeState(age=10.0, alive=True, since_scheduled=4.0)" - ) - with pytest.warns(FutureWarning, match="phase is renamed since_scheduled"): - assert state.phase == 4.0 - with pytest.raises(TypeError, match="since_scheduled alone"): - NodeState(phase=4.0, since_scheduled=4.0) - with warnings.catch_warnings(): - warnings.simplefilter("error") - assert NodeState(since_scheduled=4.0).since_scheduled == 4.0 - assert ( - NodeState(stationary=True, since_scheduled=4.0).since_scheduled - == 4.0 - ) - - def test_an_uncertainty_result_shows_a_summary(): pump = surv.Weibull.fit(np.linspace(200, 1800, 50)) rbd = NonRepairableRBD([("s", "pump"), ("pump", "t")], {"pump": pump}) diff --git a/repyability/tests/test_removed_in_0_15.py b/repyability/tests/test_removed_in_0_15.py new file mode 100644 index 0000000..f8b9aad --- /dev/null +++ b/repyability/tests/test_removed_in_0_15.py @@ -0,0 +1,15 @@ +"""What 0.14 deprecated is gone in 0.15: ``NodeState``'s ``phase``, +renamed ``since_scheduled`` (#350), as a phased mission has phases of its +own.""" + +import pytest + +from repyability import NodeState + + +def test_phase_is_refused_for_since_scheduled(): + with pytest.raises(TypeError, match="phase"): + NodeState(age=10.0, phase=4.0) + state = NodeState(age=10.0, since_scheduled=4.0) + assert state.since_scheduled == 4.0 + assert not hasattr(state, "phase") diff --git a/repyability/utils/deprecation.py b/repyability/utils/deprecation.py index d975974..7df5dc7 100644 --- a/repyability/utils/deprecation.py +++ b/repyability/utils/deprecation.py @@ -1,28 +1,25 @@ """Deprecated names, arguments and calls. -A deprecation gives one minor release's notice: what 0.14 deprecates goes -in 0.15 (``NEXT_REMOVAL``). Each warns with a ``FutureWarning``, which +A deprecation gives one minor release's notice: what 0.15 deprecates goes +in 0.16 (``NEXT_REMOVAL``). Each warns with a ``FutureWarning``, which Python always shows, as the notice is short: an argument renamed (``renamed``; an attribute, ``renamed_attribute``), a result's value called that is now a property (``called``), or arguments a method ignores -(``ignored``). 0.14 deprecates: - -- ``NodeState``'s ``phase``, renamed ``since_scheduled`` (``renamed`` for - the argument, ``renamed_attribute`` for reading it, #350). - -What 0.13 deprecated went in 0.14: ``optimal_inspection_intervals``' -``offsets``, renamed ``offset_shares`` (#222); calling -``CapacityDistribution.mean()``, a property since 0.13 (#235); the -simulation options given to the exact ``mean`` of a ``StandbyModel``, -``LoadSharingModel`` or ``DegradingNode``, which it would ignore (#233); -and ``rbd._sensitivity``'s ``levers`` and ``Lever``, the names callers -used before the levers were public (#244). What 0.12 deprecated went in -0.13: calling ``SparesDemand.mean()`` and ``std()``, properties since 0.12 -(#184), and ``StandbyModel``'s and ``LoadSharingModel``'s ``mc_samples``, -``lower`` and ``seed``, which set a fit to simulated lifetimes that 0.12 -removed (#149). What 0.11 deprecated went in 0.12 (#149): its old names -are refused with the names that took their place -(``refuses_removed_names``, #232). +(``ignored``). 0.15 deprecates nothing. + +What 0.14 deprecated went in 0.15: ``NodeState``'s ``phase``, renamed +``since_scheduled`` (#350). What 0.13 deprecated went in 0.14: +``optimal_inspection_intervals``' ``offsets``, renamed ``offset_shares`` +(#222); calling ``CapacityDistribution.mean()``, a property since 0.13 (#235); +the simulation options given to the exact ``mean`` of a ``StandbyModel``, +``LoadSharingModel`` or ``DegradingNode``, which it would ignore (#233); and +``rbd._sensitivity``'s ``levers`` and ``Lever``, the names callers used before +the levers were public (#244). What 0.12 deprecated went in 0.13: calling +``SparesDemand.mean()`` and ``std()``, properties since 0.12 (#184), and +``StandbyModel``'s and ``LoadSharingModel``'s ``mc_samples``, ``lower`` and +``seed``, which set a fit to simulated lifetimes that 0.12 removed (#149). What +0.11 deprecated went in 0.12 (#149): its old names are refused with the names +that took their place (``refuses_removed_names``, #232). """ import functools @@ -34,8 +31,8 @@ from repyability.utils.wrappers import outside_level -#: The release that removes what 0.14 deprecates. -NEXT_REMOVAL = "0.15" +#: The release that removes what 0.15 deprecates. +NEXT_REMOVAL = "0.16" def _called_warning(name: str, removal: str) -> None: