import functools
import logging


def name_or_var(assert_fn):
    """
    Assert helpers marked with this decorator take a "name" argument, which is a
    markdown string describing the actual value being checked. It may be a generic
    name like "dataframe", or a prose description like "the result of calling `circle_area`",
    or a variable name wrapped in backticks.

    The latter case is extremely common, so this decorator adds an optional "var" keyword-arg
    to the function. Passing var="foo" is a convenient shorthand for name="`foo`". (If you
    pass a value for "var", you should not also pass a value for "name".)

    Example:

    @name_or_var
    def assert_negative(actual, name):
        assert actual < 0, "{} should have been negative, but was actually {}".format(name, actual)

    # The following are all valid calls
    assert_negative(x, "Bank balance")
    assert_negative(x, name="`Bank.balance` attribute")
    assert_negative(x, var="bank_balance") # Equivalent to assert_negative(x, "`bank_balance`")
    """
    @functools.wraps(assert_fn)
    def wrapped(*args, **kwargs):
        var = kwargs.pop('var', None)
        if var:
            if 'name' in kwargs:
                logging.warn("Function {} received values for keyword args name *and* var. Overwriting original name kwarg.".format(
                    assert_fn.__name__))
            kwargs['name'] = '`{}`'.format(var)
        return assert_fn(*args, **kwargs)
    return wrapped