Repository: PlasmaPy/PlasmaPy

Issue:
Enable `@particle_input` to handle `z_mean` and issue `DeprecationWarning`
There are going to be some PRs like #1869 that drop the `z_mean` parameter in favor of `Z` for some functions, since `Z` is the parameter expected by `@particle_input`.  We can make this change now since `@particle_input` now allows non-integer charge numbers, so we can use `Z` consistently.

Rather than having a sudden breaking change, we could make `@particle_input` accept `z_mean`, in which case it would issue a `DeprecationWarning` saying that users should switch to `Z`, while also setting `Z = z_mean`.

Given that `z_mean` is used in a bunch of places in `plasmapy.formulary`, we'd probably want to keep the `DeprecationWarning` in place for ∼3–5 releases.


Discussion:

Resolving patch:
diff --git a/plasmapy/particles/decorators.py b/plasmapy/particles/decorators.py
--- a/plasmapy/particles/decorators.py
+++ b/plasmapy/particles/decorators.py
@@ -4,6 +4,7 @@
 
 import functools
 import inspect
+import warnings
 import wrapt
 
 from collections.abc import Iterable
@@ -21,6 +22,7 @@
 )
 from plasmapy.particles.particle_class import CustomParticle, Particle, ParticleLike
 from plasmapy.particles.particle_collections import ParticleList, ParticleListLike
+from plasmapy.utils import PlasmaPyDeprecationWarning
 
 _basic_particle_input_annotations = (
     Particle,  # deprecated
@@ -100,6 +102,25 @@ def _bind_arguments(
     """
     wrapped_signature = inspect.signature(callable_)
 
+    # We should keep the warning about "z_mean" for perhaps ∼2
+    # releases following the last pull request that removes a "z_mean"
+    # parameter from a callable decorated with @particle_input. After
+    # that, we should change this warning to an exception for ∼2 more
+    # releases before deleting it.
+
+    if "z_mean" in kwargs and "Z" not in kwargs and "Z" in wrapped_signature.parameters:
+        function_name = getattr(callable_, "__name__", None)
+        name_clause = f"to '{function_name}' " if function_name else ""
+
+        warnings.warn(
+            f"The 'z_mean' parameter {name_clause}has been deprecated "
+            "and will be removed in a subsequent release. Define the "
+            "(mean) charge number with 'Z' instead.",
+            category=PlasmaPyDeprecationWarning,
+        )
+
+        kwargs["Z"] = kwargs.pop("z_mean")
+
     # When decorating a callable_ or staticmethod, instance will
     # be None. When decorating a class instance method, instance
     # will be the class instance, and will need to be bound to


Test patch:
diff --git a/plasmapy/particles/tests/test_decorators.py b/plasmapy/particles/tests/test_decorators.py
--- a/plasmapy/particles/tests/test_decorators.py
+++ b/plasmapy/particles/tests/test_decorators.py
@@ -1,3 +1,4 @@
+import astropy.constants as const
 import astropy.units as u
 import inspect
 import pytest
@@ -16,6 +17,7 @@
     ParticleError,
 )
 from plasmapy.particles.particle_class import CustomParticle, Particle, ParticleLike
+from plasmapy.utils import PlasmaPyDeprecationWarning
 from plasmapy.utils.code_repr import call_string
 from plasmapy.utils.decorators.validators import validate_quantities
 
@@ -589,3 +591,36 @@ def test_particle_list_all_in_category(self, case):
         with multiple particles at once which are all in the category.
         """
         case.function(case.particles_in_category)
+
+
+@particle_input
+def return_particle(particle: ParticleLike, Z=None, mass_numb=None):
+    """A simple function that is decorated by particle_input."""
+    return particle
+
+
+def test_particle_input_warning_for_integer_z_mean():
+    """
+    Test that if a function decorated by `particle_input` is passed
+    an integer called `z_mean`, then `z_mean` becomes `Z` and a warning
+    is issued.
+    """
+    with pytest.warns(PlasmaPyDeprecationWarning):
+        result = return_particle("H", z_mean=1, mass_numb=1)
+    assert result == "p+"
+
+
+def test_particle_input_warning_for_float_z_mean():
+    """
+    Test that if a function decorated by `particle_input` is passed
+    a float called `z_mean`, then `z_mean` becomes `Z` and a warning
+    is issued.
+    """
+    z_mean = 0.432
+
+    with pytest.warns(PlasmaPyDeprecationWarning):
+        result = return_particle("H", z_mean=z_mean, mass_numb=1)
+
+    Z = result.charge / const.e.si
+
+    assert u.isclose(Z, z_mean)