import six


def upgrader(version=None):
    """
    A decorator for marking a method as an upgrader from an older
    version of a given object.  Can be used in two different ways:

    ``@upgrader``
        In this usage, the decorated method updates from the previous
        schema version to this schema version.

    ``@upgrader(number)``
        In this usage, the decorated method updates from the
        designated schema version to this schema version.

    Note that upgrader methods are implicitly class methods, as the
    ``Schema`` object has not been constructed at the time the
    upgrader method is called.  Also note that upgraders take a single
    argument--a dictionary of attributes--and must return a
    dictionary.  Upgraders may modify the argument in place, if
    desired.

    :param version: The version number the upgrader converts from.

    :returns: If called with no arguments or with an integer version,
              returns a decorator.  If called with a callable, returns
              the callable.
    """

    def decorator(func):
        # Save the version to update from
        func.__vers_upgrader__ = version
        return func

    # What is version?  It can be None, an int, or a callable,
    # depending on how @upgrader() was called
    if version is None:
        # Called as @upgrader(); return the decorator
        return decorator
    elif isinstance(version, six.integer_types):
        # Called as @upgrader(1); sanity-check version and return the
        # decorator
        if version < 1:
            raise TypeError("Invalid upgrader version number %r" % version)
        return decorator
    elif callable(version):
        # Called as @upgrader; use version = None and call the
        # decorator
        func = version
        version = None
        return decorator(func)
    else:
        # Called with an invalid version
        raise TypeError("Invalid upgrader version number %r" % version)