Repository: Unidata/MetPy

Issue:
Problems with xarray broadcasting/dimensions
I've encountered pretty easily some challenges trying to use XArray-based data, specifically when pulling out 1D pressure coordinates to use as a data field. I was able to easily fix `heat_index` by adding `broadcast` to the `preprocess_and_wrap` decorator, but I haven't quickly figured out this one with `dewpoint_from_specific_humidity`:
```python
import xarray as xr
import metpy.calc as mpcalc
from metpy.cbook import get_test_data

narr = xr.open_dataset(get_test_data('narr_example.nc', as_file_obj=False))
dewp = mpcalc.dewpoint_from_specific_humidity(narr.isobaric, narr.Temperature, narr.Specific_humidity)
```
This gives:
```pytb
ValueError                                Traceback (most recent call last)
/var/folders/r8/bhrhn4m56lgdckj491rm6_w40000gp/T/ipykernel_48975/3495402994.py in <module>
----> 1 dewp = mpcalc.dewpoint_from_specific_humidity(narr.isobaric, narr.Temperature, narr.Specific_humidity)

~/repos/metpy/src/metpy/xarray.py in wrapper(*args, **kwargs)
   1232                     return tuple(wrapping(*args) for args in zip(result, match))
   1233                 else:
-> 1234                     return wrapping(result, match)
   1235         return wrapper
   1236     return decorator

~/repos/metpy/src/metpy/xarray.py in _wrap_output_like_not_matching_units(result, match)
   1288             result = units.Quantity(result)
   1289         return (
-> 1290             xr.DataArray(result, coords=match.coords, dims=match.dims) if output_xarray
   1291             else result
   1292         )

~/miniconda3/envs/py39/lib/python3.9/site-packages/xarray/core/dataarray.py in __init__(self, data, coords, dims, name, attrs, indexes, fastpath)
    408             data = _check_data_shape(data, coords, dims)
    409             data = as_compatible_data(data)
--> 410             coords, dims = _infer_coords_and_dims(data.shape, coords, dims)
    411             variable = Variable(dims, data, attrs, fastpath=True)
    412             indexes = dict(

~/miniconda3/envs/py39/lib/python3.9/site-packages/xarray/core/dataarray.py in _infer_coords_and_dims(shape, coords, dims)
    156         for d, s in zip(v.dims, v.shape):
    157             if s != sizes[d]:
--> 158                 raise ValueError(
    159                     f"conflicting sizes for dimension {d!r}: "
    160                     f"length {sizes[d]} on the data but length {s} on "

ValueError: conflicting sizes for dimension 'time': length 29 on the data but length 1 on coordinate 'time'
```

It's also disconcerting to see this warning:
```
/Users/rmay/repos/metpy/src/metpy/calc/thermo.py:1047: UserWarning: Relative humidity >120%, ensure proper units.
  warnings.warn('Relative humidity >120%, ensure proper units.')
```

Discussion:

Patch:
diff --git a/src/metpy/calc/thermo.py b/src/metpy/calc/thermo.py
index 35053e8ef6..94b9aaac4b 100644
--- a/src/metpy/calc/thermo.py
+++ b/src/metpy/calc/thermo.py
@@ -3053,7 +3053,7 @@ def static_stability(pressure, temperature, vertical_dim=0):
 @exporter.export
 @preprocess_and_wrap(
     wrap_like='temperature',
-    broadcast=('pressure', 'temperature', 'specific_humdiity')
+    broadcast=('pressure', 'temperature', 'specific_humidity')
 )
 @check_units('[pressure]', '[temperature]', '[dimensionless]')
 def dewpoint_from_specific_humidity(pressure, temperature, specific_humidity):
diff --git a/src/metpy/xarray.py b/src/metpy/xarray.py
index a66f510bf2..4ae46a9b2e 100644
--- a/src/metpy/xarray.py
+++ b/src/metpy/xarray.py
@@ -1170,9 +1170,18 @@ def preprocess_and_wrap(broadcast=None, wrap_like=None, match_unit=False, to_mag
         xarray arguments to Quantity, and do not change other array-like arguments.
     """
     def decorator(func):
+        sig = signature(func)
+        if broadcast is not None:
+            for arg_name in broadcast:
+                if arg_name not in sig.parameters:
+                    raise ValueError(
+                        f'Cannot broadcast argument {arg_name} as it is not in function '
+                        'signature'
+                    )
+
         @functools.wraps(func)
         def wrapper(*args, **kwargs):
-            bound_args = signature(func).bind(*args, **kwargs)
+            bound_args = sig.bind(*args, **kwargs)
 
             # Auto-broadcast select xarray arguments, and update bound_args
             if broadcast is not None:


Test patch:
diff --git a/tests/test_xarray.py b/tests/test_xarray.py
index 45f7122a3e..a8dfe5e3f8 100644
--- a/tests/test_xarray.py
+++ b/tests/test_xarray.py
@@ -1298,6 +1298,14 @@ def test_preprocess_and_wrap_with_broadcasting():
     assert_array_equal(func(data, data2), [[0, 1, 2], [0, 0, 0], [0, 0, 0]] * units('N m'))
 
 
+def test_preprocess_and_wrap_broadcasting_error():
+    """Test that decorator with bad arguments specified to broadcast errors out."""
+    with pytest.raises(ValueError):
+        @preprocess_and_wrap(broadcast=('a', 'c'))
+        def func(a, b):
+            """Test a mismatch between arguments in signature and decorator."""
+
+
 def test_preprocess_and_wrap_with_to_magnitude():
     """Test preprocessing and wrapping with casting to magnitude."""
     data = xr.DataArray([1, 0, 1] * units.m)