# Buggy code
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)


# 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 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)