Source code for plasmapy.utils.decorators.converter

"""Decorators to convert units."""

__all__ = ["angular_freq_to_hz"]

import inspect

import astropy.units as u
import wrapt


[docs] def angular_freq_to_hz(fn): """ A decorator that enables a function to convert its return value from angular frequency (rad/s) to frequency (Hz). A kwarg ``to_hz`` is added to the function's signature, with a default value of `False`. The keyword is also added to the function's docstring under the **"Other Parameters"** heading. Parameters ---------- fn : function The function to be decorated. Raises ------ ValueError If ``fn`` has already defined a kwarg ``to_hz``. Returns ------- callable The decorated function. Notes ----- * If `~plasmapy.utils.decorators.converter.angular_freq_to_hz` is used with decorator :func:`~plasmapy.utils.decorators.validators.validate_quantities`, then `angular_freq_to_hz` should be used inside :func:`~plasmapy.utils.decorators.validators.validate_quantities` but special consideration is needed for setup. The following is an example of an appropriate setup:: import astropy.units as u from plasmapy.utils.decorators.converter import angular_freq_to_hz from plasmapy.utils.decorators.validators import validate_quantities @validate_quantities(validations_on_return={"units": [u.rad / u.s, u.Hz]}) @angular_freq_to_hz def foo(x: u.rad / u.s) -> u.rad / u.s return x Adding ``u.Hz`` to the allowed units allows the converted quantity to pass the validations. Examples -------- >>> import astropy.units as u >>> from plasmapy.utils.decorators.converter import angular_freq_to_hz >>> >>> @angular_freq_to_hz ... def foo(x): ... return x >>> >>> foo(5 * u.rad / u.s, to_hz=True) <Quantity 0.79577472 Hz> >>> >>> foo(-1 * u.rad / u.s, to_hz=True) <Quantity -0.15915494 Hz> Decoration also works with methods >>> class Foo: ... def __init__(self, x): ... self.x = x ... ... @angular_freq_to_hz ... def bar(self): ... return self.x >>> >>> foo = Foo(0.5 * u.rad / u.s) >>> foo.bar(to_hz=True) <Quantity 0.07957747 Hz> """ sig = inspect.signature(fn) if "to_hz" in sig.parameters: raise ValueError( f"Wrapped function '{fn.__name__}' can not use keyword 'to_hz'." f" Keyword reserved for decorator functionality." ) # make new signature for fn new_params = [] var_keyword_param = None for param in sig.parameters.values(): if param.kind == param.VAR_KEYWORD: var_keyword_param = param else: new_params.append(param) new_params.append( inspect.Parameter("to_hz", inspect.Parameter.KEYWORD_ONLY, default=False) ) if var_keyword_param: new_params.append(var_keyword_param) new_sig = inspect.Signature( parameters=new_params, return_annotation=sig.return_annotation ) fn.__signature__ = new_sig @wrapt.decorator def wrapper(fn, instance, args, kwargs): # noqa: ARG001 to_hz = kwargs.pop("to_hz", False) _result = fn(*args, **kwargs) if to_hz: return _result.to(u.Hz, equivalencies=[(u.cy / u.s, u.Hz)]) return _result fn = wrapper(fn) added_doc_bit = """ Other Parameters ---------------- to_hz: bool Set `True` to convert function output from angular frequency to Hz """ if fn.__doc__ is not None: fn.__doc__ += added_doc_bit else: fn.__doc__ = added_doc_bit return fn