prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  Incompatibility between `@angular_freq_to_hz` and var-keyword arguments
  ### Bug description
  
  While trying to decorate `gyrofrequency` with `@particle_input` in #2026, I found an issue with `@angular_freq_to_hz`.  It appears that `@angular_freq_to_hz` cannot decorate functions that accept var-keyword arguments.
  
  ### Expected outcome
  
  We should be able to use `@angular_freq_to_hz` to decorate functions with var-keyword parameters.
  
  ### Minimal complete verifiable example
  
  When declaring this function:
  
  ```Python
  from plasmapy.utils.decorators import angular_freq_to_hz
  @angular_freq_to_hz
  def f(**kwargs):
      return kwargs
  ```
  I get:
  ```python
  ---------------------------------------------------------------------------
  ValueError                                Traceback (most recent call last)
  Cell In[41], line 1
  ----> 1 @angular_freq_to_hz
        2 def f(**kwargs):
        3     return kwargs
  
  File ~/Projects/PlasmaPy/plasmapy/utils/decorators/converter.py:101, in angular_freq_to_hz(fn)
       97 new_params = sig.parameters.copy()
       98 new_params["to_hz"] = inspect.Parameter(
       99     "to_hz", inspect.Parameter.POSITIONAL_OR_KEYWORD, default=False
      100 )
  --> 101 new_sig = inspect.Signature(
      102     parameters=new_params.values(), return_annotation=sig.return_annotation
      103 )
      104 fn.__signature__ = new_sig
      106 @preserve_signature
      107 @functools.wraps(fn)
      108 def wrapper(*args, to_hz=False, **kwargs):
  
  File ~/miniconda3/envs/pldev/lib/python3.11/inspect.py:2994, in Signature.__init__(self, parameters, return_annotation, __validate_parameters__)
     2988     msg = (
     2989         'wrong parameter order: {} parameter before {} '
     2990         'parameter'
     2991     )
     2992     msg = msg.format(top_kind.description,
     2993                      kind.description)
  -> 2994     raise ValueError(msg)
     2995 elif kind > top_kind:
     2996     kind_defaults = False
  
  ValueError: wrong parameter order: variadic keyword parameter before positional or keyword parameter
  ```
  
  
  ### Package versions
  
  Development branch 
  
  ### Additional context
  
  This is medium priority to address since it's blocking #2026 and possibly also #2022.
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `plasmapy/utils/decorators/converter.py`
  Content:
  ```
  1 """Decorators to convert units."""
  2 
  3 __all__ = ["angular_freq_to_hz"]
  4 
  5 import astropy.units as u
  6 import functools
  7 import inspect
  8 
  9 from plasmapy.utils.decorators.helpers import preserve_signature
  10 
  11 
  12 def angular_freq_to_hz(fn):
  13     """
  14     A decorator that enables a function to convert its return
  15     value from angular frequency (rad/s) to frequency (Hz).
  16 
  17     A kwarg ``to_hz`` is added to the function's signature, with a
  18     default value of `False`.  The keyword is also added to the
  19     function's docstring under the **"Other Parameters"** heading.
  20 
  21     Parameters
  22     ----------
  23     fn : function
  24         The function to be decorated.
  25 
  26     Raises
  27     ------
  28     ValueError
  29         If ``fn`` has already defined a kwarg ``to_hz``.
  30 
  31     Returns
  32     -------
  33     callable
  34         The decorated function.
  35 
  36     Notes
  37     -----
  38     * If `~plasmapy.utils.decorators.converter.angular_freq_to_hz` is
  39       used with decorator
  40       :func:`~plasmapy.utils.decorators.validators.validate_quantities`,
  41       then `angular_freq_to_hz` should be used inside
  42       :func:`~plasmapy.utils.decorators.validators.validate_quantities`
  43       but special consideration is needed for setup.  The following is
  44       an example of an appropriate setup::
  45 
  46         import astropy.units as u
  47         from plasmapy.utils.decorators.converter import angular_freq_to_hz
  48         from plasmapy.utils.decorators.validators import validate_quantities
  49 
  50         @validate_quantities(validations_on_return={'units': [u.rad / u.s, u.Hz]})
  51         @angular_freq_to_hz
  52         def foo(x: u.rad / u.s) -> u.rad / u.s
  53             return x
  54 
  55       Adding ``u.Hz`` to the allowed units allows the converted
  56       quantity to pass the validations.
  57 
  58     Examples
  59     --------
  60         >>> import astropy.units as u
  61         >>> from plasmapy.utils.decorators.converter import angular_freq_to_hz
  62         >>>
  63         >>> @angular_freq_to_hz
  64         ... def foo(x):
  65         ...     return x
  66         >>>
  67         >>> foo(5 * u.rad / u.s, to_hz=True)
  68         <Quantity 0.79577472 Hz>
  69         >>>
  70         >>> foo(-1 * u.rad / u.s, to_hz=True)
  71         <Quantity -0.15915494 Hz>
  72 
  73     Decoration also works with methods
  74 
  75         >>> class Foo:
  76         ...     def __init__(self, x):
  77         ...         self.x = x
  78         ...
  79         ...     @angular_freq_to_hz
  80         ...     def bar(self):
  81         ...         return self.x
  82         >>>
  83         >>> foo = Foo(0.5 * u.rad / u.s)
  84         >>> foo.bar(to_hz=True)
  85         <Quantity 0.07957747 Hz>
  86 
  87     """
  88     # raise exception if fn uses the 'to_hz' kwarg
  89     sig = inspect.signature(fn)
  90     if "to_hz" in sig.parameters:
  91         raise ValueError(
  92             f"Wrapped function '{fn.__name__}' can not use keyword 'to_hz'."
  93             f" Keyword reserved for decorator functionality."
  94         )
  95 
  96     # make new signature for fn
  97     new_params = sig.parameters.copy()
  98     new_params["to_hz"] = inspect.Parameter(
  99         "to_hz", inspect.Parameter.POSITIONAL_OR_KEYWORD, default=False
  100     )
  101     new_sig = inspect.Signature(
  102         parameters=new_params.values(), return_annotation=sig.return_annotation
  103     )
  104     fn.__signature__ = new_sig
  105 
  106     @preserve_signature
  107     @functools.wraps(fn)
  108     def wrapper(*args, to_hz=False, **kwargs):
  109         _result = fn(*args, **kwargs)
  110         if to_hz:
  111             return _result.to(u.Hz, equivalencies=[(u.cy / u.s, u.Hz)])
  112         return _result
  113 
  114     added_doc_bit = """
  115     Other Parameters
  116     ----------------
  117     to_hz: bool
  118         Set `True` to to convert function output from angular frequency to Hz
  119     """
  120     if wrapper.__doc__ is not None:
  121         wrapper.__doc__ += added_doc_bit
  122     else:
  123         wrapper.__doc__ = added_doc_bit
  124 
  125     return wrapper
  126
  ```
  --- END FILES ---
  
  Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.
  
  Here is an example:
  
  ```diff
  diff --git a/examples/server_async.py b/examples/server_async.py
  --- a/examples/server_async.py
  +++ b/examples/server_async.py
  @@ -313,4 +313,4 @@
   
   
   if __name__ == "__main__":
  -    asyncio.run(run_async_server("."), debug=True)
  +    asyncio.run(run_async_server(), debug=True)
  diff --git a/examples/server_sync.py b/examples/server_sync.py
  --- a/examples/server_sync.py
  +++ b/examples/server_sync.py
  @@ -313,5 +313,5 @@
   
   
   if __name__ == "__main__":
  -    server = run_sync_server(".")
  +    server = run_sync_server()
       server.shutdown()
  
  ```
  
golden_diff: diff --git a/plasmapy/utils/decorators/converter.py b/plasmapy/utils/decorators/converter.py
  --- a/plasmapy/utils/decorators/converter.py
  +++ b/plasmapy/utils/decorators/converter.py
  @@ -3,10 +3,8 @@
   __all__ = ["angular_freq_to_hz"]
   
   import astropy.units as u
  -import functools
   import inspect
  -
  -from plasmapy.utils.decorators.helpers import preserve_signature
  +import wrapt
   
   
   def angular_freq_to_hz(fn):
  @@ -85,7 +83,6 @@
           <Quantity 0.07957747 Hz>
   
       """
  -    # raise exception if fn uses the 'to_hz' kwarg
       sig = inspect.signature(fn)
       if "to_hz" in sig.parameters:
           raise ValueError(
  @@ -94,32 +91,45 @@
           )
   
       # make new signature for fn
  -    new_params = sig.parameters.copy()
  -    new_params["to_hz"] = inspect.Parameter(
  -        "to_hz", inspect.Parameter.POSITIONAL_OR_KEYWORD, default=False
  +    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.values(), return_annotation=sig.return_annotation
  +        parameters=new_params, return_annotation=sig.return_annotation
       )
       fn.__signature__ = new_sig
   
  -    @preserve_signature
  -    @functools.wraps(fn)
  -    def wrapper(*args, to_hz=False, **kwargs):
  +    @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 to convert function output from angular frequency to Hz
  +        Set `True` to convert function output from angular frequency to Hz
       """
  -    if wrapper.__doc__ is not None:
  -        wrapper.__doc__ += added_doc_bit
  +    if fn.__doc__ is not None:
  +        fn.__doc__ += added_doc_bit
       else:
  -        wrapper.__doc__ = added_doc_bit
  +        fn.__doc__ = added_doc_bit
   
  -    return wrapper
  +    return fn
  
verification_info: {"golden_diff": "diff --git a/plasmapy/utils/decorators/converter.py b/plasmapy/utils/decorators/converter.py\n--- a/plasmapy/utils/decorators/converter.py\n+++ b/plasmapy/utils/decorators/converter.py\n@@ -3,10 +3,8 @@\n __all__ = [\"angular_freq_to_hz\"]\n \n import astropy.units as u\n-import functools\n import inspect\n-\n-from plasmapy.utils.decorators.helpers import preserve_signature\n+import wrapt\n \n \n def angular_freq_to_hz(fn):\n@@ -85,7 +83,6 @@\n         <Quantity 0.07957747 Hz>\n \n     \"\"\"\n-    # raise exception if fn uses the 'to_hz' kwarg\n     sig = inspect.signature(fn)\n     if \"to_hz\" in sig.parameters:\n         raise ValueError(\n@@ -94,32 +91,45 @@\n         )\n \n     # make new signature for fn\n-    new_params = sig.parameters.copy()\n-    new_params[\"to_hz\"] = inspect.Parameter(\n-        \"to_hz\", inspect.Parameter.POSITIONAL_OR_KEYWORD, default=False\n+    new_params = []\n+    var_keyword_param = None\n+    for param in sig.parameters.values():\n+        if param.kind == param.VAR_KEYWORD:\n+            var_keyword_param = param\n+        else:\n+            new_params.append(param)\n+\n+    new_params.append(\n+        inspect.Parameter(\"to_hz\", inspect.Parameter.KEYWORD_ONLY, default=False)\n     )\n+\n+    if var_keyword_param:\n+        new_params.append(var_keyword_param)\n+\n     new_sig = inspect.Signature(\n-        parameters=new_params.values(), return_annotation=sig.return_annotation\n+        parameters=new_params, return_annotation=sig.return_annotation\n     )\n     fn.__signature__ = new_sig\n \n-    @preserve_signature\n-    @functools.wraps(fn)\n-    def wrapper(*args, to_hz=False, **kwargs):\n+    @wrapt.decorator\n+    def wrapper(fn, instance, args, kwargs):  # noqa: ARG001\n+        to_hz = kwargs.pop(\"to_hz\", False)\n         _result = fn(*args, **kwargs)\n         if to_hz:\n             return _result.to(u.Hz, equivalencies=[(u.cy / u.s, u.Hz)])\n         return _result\n \n+    fn = wrapper(fn)\n+\n     added_doc_bit = \"\"\"\n     Other Parameters\n     ----------------\n     to_hz: bool\n-        Set `True` to to convert function output from angular frequency to Hz\n+        Set `True` to convert function output from angular frequency to Hz\n     \"\"\"\n-    if wrapper.__doc__ is not None:\n-        wrapper.__doc__ += added_doc_bit\n+    if fn.__doc__ is not None:\n+        fn.__doc__ += added_doc_bit\n     else:\n-        wrapper.__doc__ = added_doc_bit\n+        fn.__doc__ = added_doc_bit\n \n-    return wrapper\n+    return fn\n", "issue": "Incompatibility between `@angular_freq_to_hz` and var-keyword arguments\n### Bug description\r\n\r\nWhile trying to decorate `gyrofrequency` with `@particle_input` in #2026, I found an issue with `@angular_freq_to_hz`.  It appears that `@angular_freq_to_hz` cannot decorate functions that accept var-keyword arguments.\r\n\r\n### Expected outcome\r\n\r\nWe should be able to use `@angular_freq_to_hz` to decorate functions with var-keyword parameters.\r\n\r\n### Minimal complete verifiable example\r\n\r\nWhen declaring this function:\r\n\r\n```Python\r\nfrom plasmapy.utils.decorators import angular_freq_to_hz\r\n@angular_freq_to_hz\r\ndef f(**kwargs):\r\n    return kwargs\r\n```\r\nI get:\r\n```python\r\n---------------------------------------------------------------------------\r\nValueError                                Traceback (most recent call last)\r\nCell In[41], line 1\r\n----> 1 @angular_freq_to_hz\r\n      2 def f(**kwargs):\r\n      3     return kwargs\r\n\r\nFile ~/Projects/PlasmaPy/plasmapy/utils/decorators/converter.py:101, in angular_freq_to_hz(fn)\r\n     97 new_params = sig.parameters.copy()\r\n     98 new_params[\"to_hz\"] = inspect.Parameter(\r\n     99     \"to_hz\", inspect.Parameter.POSITIONAL_OR_KEYWORD, default=False\r\n    100 )\r\n--> 101 new_sig = inspect.Signature(\r\n    102     parameters=new_params.values(), return_annotation=sig.return_annotation\r\n    103 )\r\n    104 fn.__signature__ = new_sig\r\n    106 @preserve_signature\r\n    107 @functools.wraps(fn)\r\n    108 def wrapper(*args, to_hz=False, **kwargs):\r\n\r\nFile ~/miniconda3/envs/pldev/lib/python3.11/inspect.py:2994, in Signature.__init__(self, parameters, return_annotation, __validate_parameters__)\r\n   2988     msg = (\r\n   2989         'wrong parameter order: {} parameter before {} '\r\n   2990         'parameter'\r\n   2991     )\r\n   2992     msg = msg.format(top_kind.description,\r\n   2993                      kind.description)\r\n-> 2994     raise ValueError(msg)\r\n   2995 elif kind > top_kind:\r\n   2996     kind_defaults = False\r\n\r\nValueError: wrong parameter order: variadic keyword parameter before positional or keyword parameter\r\n```\r\n\r\n\r\n### Package versions\r\n\r\nDevelopment branch \r\n\r\n### Additional context\r\n\r\nThis is medium priority to address since it's blocking #2026 and possibly also #2022.\n", "before_files": [{"content": "\"\"\"Decorators to convert units.\"\"\"\n\n__all__ = [\"angular_freq_to_hz\"]\n\nimport astropy.units as u\nimport functools\nimport inspect\n\nfrom plasmapy.utils.decorators.helpers import preserve_signature\n\n\ndef angular_freq_to_hz(fn):\n    \"\"\"\n    A decorator that enables a function to convert its return\n    value from angular frequency (rad/s) to frequency (Hz).\n\n    A kwarg ``to_hz`` is added to the function's signature, with a\n    default value of `False`.  The keyword is also added to the\n    function's docstring under the **\"Other Parameters\"** heading.\n\n    Parameters\n    ----------\n    fn : function\n        The function to be decorated.\n\n    Raises\n    ------\n    ValueError\n        If ``fn`` has already defined a kwarg ``to_hz``.\n\n    Returns\n    -------\n    callable\n        The decorated function.\n\n    Notes\n    -----\n    * If `~plasmapy.utils.decorators.converter.angular_freq_to_hz` is\n      used with decorator\n      :func:`~plasmapy.utils.decorators.validators.validate_quantities`,\n      then `angular_freq_to_hz` should be used inside\n      :func:`~plasmapy.utils.decorators.validators.validate_quantities`\n      but special consideration is needed for setup.  The following is\n      an example of an appropriate setup::\n\n        import astropy.units as u\n        from plasmapy.utils.decorators.converter import angular_freq_to_hz\n        from plasmapy.utils.decorators.validators import validate_quantities\n\n        @validate_quantities(validations_on_return={'units': [u.rad / u.s, u.Hz]})\n        @angular_freq_to_hz\n        def foo(x: u.rad / u.s) -> u.rad / u.s\n            return x\n\n      Adding ``u.Hz`` to the allowed units allows the converted\n      quantity to pass the validations.\n\n    Examples\n    --------\n        >>> import astropy.units as u\n        >>> from plasmapy.utils.decorators.converter import angular_freq_to_hz\n        >>>\n        >>> @angular_freq_to_hz\n        ... def foo(x):\n        ...     return x\n        >>>\n        >>> foo(5 * u.rad / u.s, to_hz=True)\n        <Quantity 0.79577472 Hz>\n        >>>\n        >>> foo(-1 * u.rad / u.s, to_hz=True)\n        <Quantity -0.15915494 Hz>\n\n    Decoration also works with methods\n\n        >>> class Foo:\n        ...     def __init__(self, x):\n        ...         self.x = x\n        ...\n        ...     @angular_freq_to_hz\n        ...     def bar(self):\n        ...         return self.x\n        >>>\n        >>> foo = Foo(0.5 * u.rad / u.s)\n        >>> foo.bar(to_hz=True)\n        <Quantity 0.07957747 Hz>\n\n    \"\"\"\n    # raise exception if fn uses the 'to_hz' kwarg\n    sig = inspect.signature(fn)\n    if \"to_hz\" in sig.parameters:\n        raise ValueError(\n            f\"Wrapped function '{fn.__name__}' can not use keyword 'to_hz'.\"\n            f\" Keyword reserved for decorator functionality.\"\n        )\n\n    # make new signature for fn\n    new_params = sig.parameters.copy()\n    new_params[\"to_hz\"] = inspect.Parameter(\n        \"to_hz\", inspect.Parameter.POSITIONAL_OR_KEYWORD, default=False\n    )\n    new_sig = inspect.Signature(\n        parameters=new_params.values(), return_annotation=sig.return_annotation\n    )\n    fn.__signature__ = new_sig\n\n    @preserve_signature\n    @functools.wraps(fn)\n    def wrapper(*args, to_hz=False, **kwargs):\n        _result = fn(*args, **kwargs)\n        if to_hz:\n            return _result.to(u.Hz, equivalencies=[(u.cy / u.s, u.Hz)])\n        return _result\n\n    added_doc_bit = \"\"\"\n    Other Parameters\n    ----------------\n    to_hz: bool\n        Set `True` to to convert function output from angular frequency to Hz\n    \"\"\"\n    if wrapper.__doc__ is not None:\n        wrapper.__doc__ += added_doc_bit\n    else:\n        wrapper.__doc__ = added_doc_bit\n\n    return wrapper\n", "path": "plasmapy/utils/decorators/converter.py"}], "after_files": [{"content": "\"\"\"Decorators to convert units.\"\"\"\n\n__all__ = [\"angular_freq_to_hz\"]\n\nimport astropy.units as u\nimport inspect\nimport wrapt\n\n\ndef angular_freq_to_hz(fn):\n    \"\"\"\n    A decorator that enables a function to convert its return\n    value from angular frequency (rad/s) to frequency (Hz).\n\n    A kwarg ``to_hz`` is added to the function's signature, with a\n    default value of `False`.  The keyword is also added to the\n    function's docstring under the **\"Other Parameters\"** heading.\n\n    Parameters\n    ----------\n    fn : function\n        The function to be decorated.\n\n    Raises\n    ------\n    ValueError\n        If ``fn`` has already defined a kwarg ``to_hz``.\n\n    Returns\n    -------\n    callable\n        The decorated function.\n\n    Notes\n    -----\n    * If `~plasmapy.utils.decorators.converter.angular_freq_to_hz` is\n      used with decorator\n      :func:`~plasmapy.utils.decorators.validators.validate_quantities`,\n      then `angular_freq_to_hz` should be used inside\n      :func:`~plasmapy.utils.decorators.validators.validate_quantities`\n      but special consideration is needed for setup.  The following is\n      an example of an appropriate setup::\n\n        import astropy.units as u\n        from plasmapy.utils.decorators.converter import angular_freq_to_hz\n        from plasmapy.utils.decorators.validators import validate_quantities\n\n        @validate_quantities(validations_on_return={'units': [u.rad / u.s, u.Hz]})\n        @angular_freq_to_hz\n        def foo(x: u.rad / u.s) -> u.rad / u.s\n            return x\n\n      Adding ``u.Hz`` to the allowed units allows the converted\n      quantity to pass the validations.\n\n    Examples\n    --------\n        >>> import astropy.units as u\n        >>> from plasmapy.utils.decorators.converter import angular_freq_to_hz\n        >>>\n        >>> @angular_freq_to_hz\n        ... def foo(x):\n        ...     return x\n        >>>\n        >>> foo(5 * u.rad / u.s, to_hz=True)\n        <Quantity 0.79577472 Hz>\n        >>>\n        >>> foo(-1 * u.rad / u.s, to_hz=True)\n        <Quantity -0.15915494 Hz>\n\n    Decoration also works with methods\n\n        >>> class Foo:\n        ...     def __init__(self, x):\n        ...         self.x = x\n        ...\n        ...     @angular_freq_to_hz\n        ...     def bar(self):\n        ...         return self.x\n        >>>\n        >>> foo = Foo(0.5 * u.rad / u.s)\n        >>> foo.bar(to_hz=True)\n        <Quantity 0.07957747 Hz>\n\n    \"\"\"\n    sig = inspect.signature(fn)\n    if \"to_hz\" in sig.parameters:\n        raise ValueError(\n            f\"Wrapped function '{fn.__name__}' can not use keyword 'to_hz'.\"\n            f\" Keyword reserved for decorator functionality.\"\n        )\n\n    # make new signature for fn\n    new_params = []\n    var_keyword_param = None\n    for param in sig.parameters.values():\n        if param.kind == param.VAR_KEYWORD:\n            var_keyword_param = param\n        else:\n            new_params.append(param)\n\n    new_params.append(\n        inspect.Parameter(\"to_hz\", inspect.Parameter.KEYWORD_ONLY, default=False)\n    )\n\n    if var_keyword_param:\n        new_params.append(var_keyword_param)\n\n    new_sig = inspect.Signature(\n        parameters=new_params, return_annotation=sig.return_annotation\n    )\n    fn.__signature__ = new_sig\n\n    @wrapt.decorator\n    def wrapper(fn, instance, args, kwargs):  # noqa: ARG001\n        to_hz = kwargs.pop(\"to_hz\", False)\n        _result = fn(*args, **kwargs)\n        if to_hz:\n            return _result.to(u.Hz, equivalencies=[(u.cy / u.s, u.Hz)])\n        return _result\n\n    fn = wrapper(fn)\n\n    added_doc_bit = \"\"\"\n    Other Parameters\n    ----------------\n    to_hz: bool\n        Set `True` to convert function output from angular frequency to Hz\n    \"\"\"\n    if fn.__doc__ is not None:\n        fn.__doc__ += added_doc_bit\n    else:\n        fn.__doc__ = added_doc_bit\n\n    return fn\n", "path": "plasmapy/utils/decorators/converter.py"}]}