Return an iterator object.

    Args:
        obj: An object that implements the __iter__ or __aiter__ method.
        sentinel: An optional sentinel value to look for while iterator.

    Return:
        iterable: Some iterable that provides a __anext__ method.

    Raises:
        TypeError: If only the object is given and it is not iterable.
        TypeError: If two arguments are given and the first is not an async
            callable.

    This function behaves very differently based on the number of arguments
    given. If only the first argument is present the method will return
    an async iterable that implements the __anext__ method by called the
    given object's __aiter__. If the object does not define __aiter__ but does
    define __iter__ then the result will be an AsyncIterWrapper that contains
    the original iterable. This form of the function can be used to coerce all
    iterables, async or not, into async iterables for interoperablilty.

    If the second argument is given then the first argument _must_ be an async
    callable. The returned value will still be an iterable implementing the
    __aiter__ method, but each call to that method will call the underlying
    async callable. If the value returned from the async callable matches the
    sentinel value then StopAsyncIteration is raised. Otherwise the value is
    returned.

async def aiter(*args):
    """Return an iterator object.

    Args:
        obj: An object that implements the __iter__ or __aiter__ method.
        sentinel: An optional sentinel value to look for while iterator.

    Return:
        iterable: Some iterable that provides a __anext__ method.

    Raises:
        TypeError: If only the object is given and it is not iterable.
        TypeError: If two arguments are given and the first is not an async
            callable.

    This function behaves very differently based on the number of arguments
    given. If only the first argument is present the method will return
    an async iterable that implements the __anext__ method by called the
    given object's __aiter__. If the object does not define __aiter__ but does
    define __iter__ then the result will be an AsyncIterWrapper that contains
    the original iterable. This form of the function can be used to coerce all
    iterables, async or not, into async iterables for interoperablilty.

    If the second argument is given then the first argument _must_ be an async
    callable. The returned value will still be an iterable implementing the
    __aiter__ method, but each call to that method will call the underlying
    async callable. If the value returned from the async callable matches the
    sentinel value then StopAsyncIteration is raised. Otherwise the value is
    returned.
    """
    if not args:

        raise TypeError('aiter() expected at least 1 arguments, got 0')

    if len(args) > 2:

        raise TypeError(
            'aiter() expected at most 2 arguments, got {}'.format(len(args))
        )

    if len(args) == 2:

        func, sentinel = args
        if not isinstance(func, types.CoroutineType):

            raise TypeError('aiter(v, w): v must be async callable')
        # TODO: repeating call thing
        raise NotImplementedError()

    obj = args[0]
    if hasattr(obj, '__anext__'):

        return obj

    if hasattr(obj, '__aiter__'):

        return (await obj.__aiter__())

    if hasattr(obj, '__iter__') or hasattr(obj, '__next__'):

        return AsyncIterWrapper(iter(obj))

    raise TypeError("'{}' object is not iterable".format(type(args[0])))