aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--discord/abc.py2
-rw-r--r--discord/iterators.py145
-rw-r--r--discord/reaction.py2
-rw-r--r--docs/api.rst84
4 files changed, 181 insertions, 52 deletions
diff --git a/discord/abc.py b/discord/abc.py
index 160cbb4b..0398da6f 100644
--- a/discord/abc.py
+++ b/discord/abc.py
@@ -740,7 +740,7 @@ class Messageable(metaclass=abc.ABCMeta):
return [state.create_message(channel=channel, data=m) for m in data]
def history(self, *, limit=100, before=None, after=None, around=None, reverse=None):
- """Return an async iterator that enables receiving the destination's message history.
+ """Return an :class:`AsyncIterator` that enables receiving the destination's message history.
You must have Read Message History permissions to use this.
diff --git a/discord/iterators.py b/discord/iterators.py
index 5aa78089..31d72569 100644
--- a/discord/iterators.py
+++ b/discord/iterators.py
@@ -35,7 +35,108 @@ from .object import Object
PY35 = sys.version_info >= (3, 5)
-class ReactionIterator:
+def _probably_coroutine(f, e):
+ if asyncio.iscoroutinefunction(f):
+ return (yield from f(e))
+ else:
+ return f(e)
+
+class _AsyncIterator:
+ __slots__ = ()
+
+ def get(self, **attrs):
+ def predicate(elem):
+ for attr, val in attrs.items():
+ nested = attr.split('__')
+ obj = elem
+ for attribute in nested:
+ obj = getattr(obj, attribute)
+
+ if obj != val:
+ return False
+ return True
+
+ return self.find(predicate)
+
+ @asyncio.coroutine
+ def find(self, predicate):
+ while True:
+ try:
+ elem = yield from self.get()
+ except NoMoreItems:
+ return None
+
+ ret = yield from _probably_coroutine(predicate, elem)
+ if ret:
+ return elem
+
+ def map(self, func):
+ return _MappedAsyncIterator(self, func)
+
+ def filter(self, predicate):
+ return _FilteredAsyncIterator(self, predicate)
+
+ @asyncio.coroutine
+ def flatten(self):
+ ret = []
+ while True:
+ try:
+ item = yield from self.get()
+ except NoMoreItems:
+ return ret
+ else:
+ ret.append(item)
+
+ if PY35:
+ @asyncio.coroutine
+ def __aiter__(self):
+ return self
+
+ @asyncio.coroutine
+ def __anext__(self):
+ try:
+ msg = yield from self.get()
+ except NoMoreItems:
+ raise StopAsyncIteration()
+ else:
+ return msg
+
+def _identity(x):
+ return x
+
+class _MappedAsyncIterator(_AsyncIterator):
+ def __init__(self, iterator, func):
+ self.iterator = iterator
+ self.func = func
+
+ @asyncio.coroutine
+ def get(self):
+ # this raises NoMoreItems and will propagate appropriately
+ item = yield from self.iterator.get()
+ return (yield from _probably_coroutine(self.func, item))
+
+class _FilteredAsyncIterator(_AsyncIterator):
+ def __init__(self, iterator, predicate):
+ self.iterator = iterator
+
+ if predicate is None:
+ predicate = _identity
+
+ self.predicate = predicate
+
+ @asyncio.coroutine
+ def get(self):
+ getter = self.iterator.get
+ pred = self.predicate
+ while True:
+ # propagate NoMoreItems similar to _MappedAsyncIterator
+ item = yield from getter()
+ ret = yield from _probably_coroutine(pred, item)
+ if ret:
+ return item
+
+class ReactionIterator(_AsyncIterator):
def __init__(self, message, emoji, limit=100, after=None):
self.message = message
self.limit = limit
@@ -85,32 +186,7 @@ class ReactionIterator:
else:
yield from self.users.put(User(state=self.state, data=element))
- @asyncio.coroutine
- def flatten(self):
- ret = []
- while True:
- try:
- user = yield from self.get()
- except NoMoreItems:
- return ret
- else:
- ret.append(user)
-
- if PY35:
- @asyncio.coroutine
- def __aiter__(self):
- return self
-
- @asyncio.coroutine
- def __anext__(self):
- try:
- msg = yield from self.get()
- except NoMoreItems:
- raise StopAsyncIteration()
- else:
- return msg
-
-class HistoryIterator:
+class HistoryIterator(_AsyncIterator):
"""Iterator for receiving a channel's message history.
The messages endpoint has two behaviours we care about here:
@@ -281,18 +357,3 @@ class HistoryIterator:
self.around = None
return data
return []
-
- if PY35:
- @asyncio.coroutine
- def __aiter__(self):
- return self
-
- @asyncio.coroutine
- def __anext__(self):
- try:
- msg = yield from self.get()
- return msg
- except NoMoreItems:
- # if we're still empty at this point...
- # we didn't get any new messages so stop looping
- raise StopAsyncIteration()
diff --git a/discord/reaction.py b/discord/reaction.py
index b5533776..1454ca8e 100644
--- a/discord/reaction.py
+++ b/discord/reaction.py
@@ -89,7 +89,7 @@ class Reaction:
def users(self, limit=None, after=None):
"""|coro|
- Returns an asynchronous iterator representing the
+ Returns an :class:`AsyncIterator` representing the
users that have reacted to the message.
The ``after`` parameter must represent a member
diff --git a/docs/api.rst b/docs/api.rst
index 87d31759..6da9c1ed 100644
--- a/docs/api.rst
+++ b/docs/api.rst
@@ -40,14 +40,6 @@ Client
.. autoclass:: AutoShardedClient
:members:
-
-Voice
------
-
-.. autoclass:: VoiceClient
- :members:
-
-
Opus Library
~~~~~~~~~~~~~
@@ -683,6 +675,82 @@ All enumerations are subclasses of `enum`_.
You have sent a friend request to this user.
+Async Iterator
+----------------
+
+Some API functions return an "async iterator". An async iterator is something that is
+capable of being used in an `async for <https://docs.python.org/3/reference/compound_stmts.html#the-async-for-statement>`_
+statement.
+
+These async iterators can be used as follows in 3.5 or higher: ::
+
+ async for elem in channel.history():
+ # do stuff with elem here
+
+If you are using 3.4 however, you will have to use the more verbose way: ::
+
+ iterator = channel.history() # or whatever returns an async iterator
+ while True:
+ try:
+ item = yield from iterator.get()
+ except discord.NoMoreItems:
+ break
+
+ # do stuff with item here
+
+Certain utilities make working with async iterators easier, detailed below.
+
+.. class:: AsyncIterator
+
+ Represents the "AsyncIterator" concept. Note that no such class exists,
+ it is purely abstract.
+
+ .. method:: get(**attrs)
+
+ |coro|
+
+ Similar to :func:`utils.get` except run over the async iterator.
+
+ .. method:: find(predicate)
+
+ |coro|
+
+ Similar to :func:`utils.find` except run over the async iterator.
+
+ Unlike :func:`utils.find`\, the predicate provided can be a
+ coroutine.
+
+ :param predicate: The predicate to use. Can be a coroutine.
+ :return: The first element that returns ``True`` for the predicate or ``None``.
+
+ .. method:: flatten()
+
+ |coro|
+
+ Flattens the async iterator into a ``list`` with all the elements.
+
+ :return: A list of every element in the async iterator.
+ :rtype: list
+
+ .. method:: map(func)
+
+ This is similar to the built-in ``map`` function. Another
+ :class:`AsyncIterator` is returned that executes the function on
+ every element it is iterating over. This function can either be a
+ regular function or a coroutine.
+
+ :param func: The function to call on every element. Could be a coroutine.
+ :return: An async iterator.
+
+ .. method:: filter(predicate)
+
+ This is similar to the built-in ``filter`` function. Another
+ :class:`AsyncIterator` is returned that filters over the original
+ async iterator. This predicate can be a regular function or a coroutine.
+
+ :param predicate: The predicate to call on every element. Could be a coroutine.
+ :return: An async iterator.
+
.. _discord_api_data:
Data Classes