# Buggy code
def add_grid_arguments_from_xarray(func):
    """Fill in optional arguments like dx/dy from DataArray arguments."""

    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        bound_args = signature(func).bind(*args, **kwargs)
        bound_args.apply_defaults()

        # Search for DataArray with valid latitude and longitude coordinates to find grid
        # deltas and any other needed parameter
        dataarray_arguments = [
            value
            for value in bound_args.arguments.values()
            if isinstance(value, xr.DataArray)
        ]
        grid_prototype = None
        for da in dataarray_arguments:
            if hasattr(da.metpy, "latitude") and hasattr(da.metpy, "longitude"):
                grid_prototype = da
                break

        # Fill in x_dim/y_dim
        if (
            grid_prototype is not None
            and "x_dim" in bound_args.arguments
            and "y_dim" in bound_args.arguments
        ):
            try:
                bound_args.arguments["x_dim"] = grid_prototype.metpy.find_axis_number(
                    "x"
                )
                bound_args.arguments["y_dim"] = grid_prototype.metpy.find_axis_number(
                    "y"
                )
            except AttributeError:
                # If axis number not found, fall back to default but warn.
                warnings.warn(
                    "Horizontal dimension numbers not found. Defaulting to "
                    "(..., Y, X) order."
                )

        # Fill in vertical_dim
        if grid_prototype is not None and "vertical_dim" in bound_args.arguments:
            try:
                bound_args.arguments["vertical_dim"] = (
                    grid_prototype.metpy.find_axis_number("vertical")
                )
            except AttributeError:
                # If axis number not found, fall back to default but warn.
                warnings.warn(
                    "Vertical dimension number not found. Defaulting to (..., Z, Y, X) order."
                )

        # Fill in dz
        if (
            grid_prototype is not None
            and "dz" in bound_args.arguments
            and bound_args.arguments["dz"] is None
        ):
            try:
                vertical_coord = grid_prototype.metpy.vertical
                bound_args.arguments["dz"] = np.diff(vertical_coord.metpy.unit_array)
            except AttributeError:
                # Skip, since this only comes up in advection, where dz is optional (may not
                # need vertical at all)
                pass

        # Fill in dx/dy
        if (
            "dx" in bound_args.arguments
            and bound_args.arguments["dx"] is None
            and "dy" in bound_args.arguments
            and bound_args.arguments["dy"] is None
        ):
            if grid_prototype is not None:
                bound_args.arguments["dx"], bound_args.arguments["dy"] = (
                    grid_deltas_from_dataarray(grid_prototype, kind="actual")
                )
            elif "dz" in bound_args.arguments:
                # Handle advection case, allowing dx/dy to be None but dz to not be None
                if bound_args.arguments["dz"] is None:
                    raise ValueError(
                        "Must provide dx, dy, and/or dz arguments or input DataArray with "
                        "proper coordinates."
                    )
            else:
                raise ValueError(
                    "Must provide dx/dy arguments or input DataArray with "
                    "latitude/longitude coordinates."
                )

        # Fill in latitude
        if (
            "latitude" in bound_args.arguments
            and bound_args.arguments["latitude"] is None
        ):
            if grid_prototype is not None:
                bound_args.arguments["latitude"] = grid_prototype.metpy.latitude
            else:
                raise ValueError(
                    "Must provide latitude argument or input DataArray with "
                    "latitude/longitude coordinates."
                )

        return func(*bound_args.args, **bound_args.kwargs)

    return wrapper


# Traceback (ValueError)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-8-5722b3ee1007> in <module>
3
4 # Compute the temperature advection at 850 hPa
----> 5 t_adv_850 = mpcalc.advection(smooth_tmpc, uwnd_850, vwnd_850)

~/miniconda3/envs/new_main/lib/python3.7/site-packages/metpy/xarray.py in wrapper(*args, **kwargs)
1158
1159             # Evaluate inner calculation
-> 1160             result = func(*bound_args.args, **bound_args.kwargs)
1161
1162             # Wrap output based on match and match_unit

~/miniconda3/envs/new_main/lib/python3.7/site-packages/metpy/calc/kinematics.py in advection(scalar, u, v, w, dx, dy, dz, x_dim, y_dim, vertical_dim)
292             (u, dx, x_dim),
293             (v, dy, y_dim),
--> 294             (w, dz, vertical_dim)
295         )
296         if wind is not None

~/miniconda3/envs/new_main/lib/python3.7/site-packages/metpy/calc/kinematics.py in <genexpr>(.0)
294             (w, dz, vertical_dim)
295         )
--> 296         if wind is not None
297     )
298

~/miniconda3/envs/new_main/lib/python3.7/site-packages/metpy/calc/tools.py in wrapper(f, **kwargs)
894         if 'x' in kwargs or 'delta' in kwargs:
895             # Use the usual DataArray to pint.Quantity preprocessing wrapper
--> 896             return preprocess_and_wrap()(func)(f, **kwargs)
897         elif isinstance(f, xr.DataArray):
898             # Get axis argument, defaulting to first dimension

~/miniconda3/envs/new_main/lib/python3.7/site-packages/metpy/xarray.py in wrapper(*args, **kwargs)
1158
1159             # Evaluate inner calculation
-> 1160             result = func(*bound_args.args, **bound_args.kwargs)
1161
1162             # Wrap output based on match and match_unit

~/miniconda3/envs/new_main/lib/python3.7/site-packages/metpy/calc/tools.py in first_derivative(f, axis, x, delta)
970
971     """
--> 972     n, axis, delta = _process_deriv_args(f, axis, x, delta)
973     take = make_take(n, axis)
974

~/miniconda3/envs/new_main/lib/python3.7/site-packages/metpy/calc/tools.py in _process_deriv_args(f, axis, x, delta)
1273         delta = np.diff(x, axis=axis)
1274     else:
-> 1275         raise ValueError('Must specify either "x" or "delta" for value positions.')
1276
1277     return n, axis, delta

ValueError: Must specify either "x" or "delta" for value positions.

# Fixed code
def add_grid_arguments_from_xarray(func):
    """Fill in optional arguments like dx/dy from DataArray arguments."""

    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        bound_args = signature(func).bind(*args, **kwargs)
        bound_args.apply_defaults()

        # Search for DataArray with valid latitude and longitude coordinates to find grid
        # deltas and any other needed parameter
        dataarray_arguments = [
            value
            for value in bound_args.arguments.values()
            if isinstance(value, xr.DataArray)
        ]
        grid_prototype = None
        for da in dataarray_arguments:
            if hasattr(da.metpy, "latitude") and hasattr(da.metpy, "longitude"):
                grid_prototype = da
                break

        # Fill in x_dim/y_dim
        if (
            grid_prototype is not None
            and "x_dim" in bound_args.arguments
            and "y_dim" in bound_args.arguments
        ):
            try:
                bound_args.arguments["x_dim"] = grid_prototype.metpy.find_axis_number(
                    "x"
                )
                bound_args.arguments["y_dim"] = grid_prototype.metpy.find_axis_number(
                    "y"
                )
            except AttributeError:
                # If axis number not found, fall back to default but warn.
                warnings.warn(
                    "Horizontal dimension numbers not found. Defaulting to "
                    "(..., Y, X) order."
                )

        # Fill in vertical_dim
        if grid_prototype is not None and "vertical_dim" in bound_args.arguments:
            try:
                bound_args.arguments["vertical_dim"] = (
                    grid_prototype.metpy.find_axis_number("vertical")
                )
            except AttributeError:
                # If axis number not found, fall back to default but warn.
                warnings.warn(
                    "Vertical dimension number not found. Defaulting to (..., Z, Y, X) order."
                )

        # Fill in dz
        if (
            grid_prototype is not None
            and "dz" in bound_args.arguments
            and bound_args.arguments["dz"] is None
        ):
            try:
                vertical_coord = grid_prototype.metpy.vertical
                bound_args.arguments["dz"] = np.diff(vertical_coord.metpy.unit_array)
            except (AttributeError, ValueError):
                # Skip, since this only comes up in advection, where dz is optional (may not
                # need vertical at all)
                pass

        # Fill in dx/dy
        if (
            "dx" in bound_args.arguments
            and bound_args.arguments["dx"] is None
            and "dy" in bound_args.arguments
            and bound_args.arguments["dy"] is None
        ):
            if grid_prototype is not None:
                bound_args.arguments["dx"], bound_args.arguments["dy"] = (
                    grid_deltas_from_dataarray(grid_prototype, kind="actual")
                )
            elif "dz" in bound_args.arguments:
                # Handle advection case, allowing dx/dy to be None but dz to not be None
                if bound_args.arguments["dz"] is None:
                    raise ValueError(
                        "Must provide dx, dy, and/or dz arguments or input DataArray with "
                        "proper coordinates."
                    )
            else:
                raise ValueError(
                    "Must provide dx/dy arguments or input DataArray with "
                    "latitude/longitude coordinates."
                )

        # Fill in latitude
        if (
            "latitude" in bound_args.arguments
            and bound_args.arguments["latitude"] is None
        ):
            if grid_prototype is not None:
                bound_args.arguments["latitude"] = grid_prototype.metpy.latitude
            else:
                raise ValueError(
                    "Must provide latitude argument or input DataArray with "
                    "latitude/longitude coordinates."
                )

        return func(*bound_args.args, **bound_args.kwargs)

    return wrapper