aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRapptz <[email protected]>2018-06-10 18:09:14 -0400
committerRapptz <[email protected]>2018-06-10 18:10:00 -0400
commitf25091efe1281aebe70189c61f9cac405b21a72f (patch)
treed0d13dad1a89de9f45845a36ea475098b7a0b494
parentAdd Message.jump_to_url (diff)
downloaddiscord.py-f25091efe1281aebe70189c61f9cac405b21a72f.tar.xz
discord.py-f25091efe1281aebe70189c61f9cac405b21a72f.zip
Drop support for Python 3.4 and make minimum version 3.5.2.
-rw-r--r--README.rst4
-rw-r--r--discord/abc.py98
-rw-r--r--discord/channel.py85
-rw-r--r--discord/client.py128
-rw-r--r--discord/compat.py140
-rw-r--r--discord/context_managers.py19
-rw-r--r--discord/emoji.py10
-rw-r--r--discord/errors.py4
-rw-r--r--discord/ext/commands/bot.py72
-rw-r--r--discord/ext/commands/context.py13
-rw-r--r--discord/ext/commands/converter.py41
-rw-r--r--discord/ext/commands/core.py140
-rw-r--r--discord/ext/commands/formatter.py20
-rw-r--r--discord/gateway.py152
-rw-r--r--discord/guild.py89
-rw-r--r--discord/http.py63
-rw-r--r--discord/invite.py5
-rw-r--r--discord/iterators.py126
-rw-r--r--discord/member.py46
-rw-r--r--discord/message.py51
-rw-r--r--discord/reaction.py2
-rw-r--r--discord/relationship.py10
-rw-r--r--discord/role.py17
-rw-r--r--discord/shard.py91
-rw-r--r--discord/state.py47
-rw-r--r--discord/user.py45
-rw-r--r--discord/utils.py19
-rw-r--r--discord/voice_client.py61
-rw-r--r--discord/webhook.py21
-rw-r--r--docs/api.rst26
-rw-r--r--docs/faq.rst26
-rw-r--r--docs/intro.rst6
-rw-r--r--docs/migrating.rst13
-rw-r--r--requirements.txt4
-rw-r--r--setup.py1
35 files changed, 626 insertions, 1069 deletions
diff --git a/README.rst b/README.rst
index e703d124..5ed738c8 100644
--- a/README.rst
+++ b/README.rst
@@ -87,14 +87,12 @@ Quick Example
client = MyClient()
client.run('token')
-Note that in Python 3.4 you use ``@asyncio.coroutine`` instead of ``async def`` and ``yield from`` instead of ``await``.
-
You can find examples in the examples directory.
Requirements
------------
-* Python 3.4.2+
+* Python 3.5.2+
* ``aiohttp`` library
* ``websockets`` library
* ``PyNaCl`` library (optional, for voice only)
diff --git a/discord/abc.py b/discord/abc.py
index 3f88491c..59702394 100644
--- a/discord/abc.py
+++ b/discord/abc.py
@@ -188,8 +188,7 @@ class GuildChannel:
def __str__(self):
return self.name
- @asyncio.coroutine
- def _move(self, position, parent_id=None, lock_permissions=False, *, reason):
+ async def _move(self, position, parent_id=None, lock_permissions=False, *, reason):
if position < 0:
raise InvalidArgument('Channel position cannot be less than 0.')
@@ -219,13 +218,12 @@ class GuildChannel:
d.update(parent_id=parent_id, lock_permissions=lock_permissions)
payload.append(d)
- yield from http.bulk_channel_update(self.guild.id, payload, reason=reason)
+ await http.bulk_channel_update(self.guild.id, payload, reason=reason)
self.position = position
if parent_id is not _undefined:
self.category_id = int(parent_id) if parent_id else None
- @asyncio.coroutine
- def _edit(self, options, reason):
+ async def _edit(self, options, reason):
try:
parent = options.pop('category')
except KeyError:
@@ -249,10 +247,10 @@ class GuildChannel:
category = self.guild.get_channel(self.category_id)
options['permission_overwrites'] = [c._asdict() for c in category._overwrites]
else:
- yield from self._move(position, parent_id=parent_id, lock_permissions=lock_permissions, reason=reason)
+ await self._move(position, parent_id=parent_id, lock_permissions=lock_permissions, reason=reason)
if options:
- data = yield from self._state.http.edit_channel(self.id, reason=reason, **options)
+ data = await self._state.http.edit_channel(self.id, reason=reason, **options)
self._update(self.guild, data)
def _fill_overwrites(self, data):
@@ -466,8 +464,7 @@ class GuildChannel:
return base
- @asyncio.coroutine
- def delete(self, *, reason=None):
+ async def delete(self, *, reason=None):
"""|coro|
Deletes the channel.
@@ -489,10 +486,9 @@ class GuildChannel:
HTTPException
Deleting the channel failed.
"""
- yield from self._state.http.delete_channel(self.id, reason=reason)
+ await self._state.http.delete_channel(self.id, reason=reason)
- @asyncio.coroutine
- def set_permissions(self, target, *, overwrite=_undefined, reason=None, **permissions):
+ async def set_permissions(self, target, *, overwrite=_undefined, reason=None, **permissions):
"""|coro|
Sets the channel specific permission overwrites for a target in the
@@ -579,15 +575,14 @@ class GuildChannel:
# TODO: wait for event
if overwrite is None:
- yield from http.delete_channel_permissions(self.id, target.id, reason=reason)
+ await http.delete_channel_permissions(self.id, target.id, reason=reason)
elif isinstance(overwrite, PermissionOverwrite):
(allow, deny) = overwrite.pair()
- yield from http.edit_channel_permissions(self.id, target.id, allow.value, deny.value, perm_type, reason=reason)
+ await http.edit_channel_permissions(self.id, target.id, allow.value, deny.value, perm_type, reason=reason)
else:
raise InvalidArgument('Invalid overwrite type provided.')
- @asyncio.coroutine
- def create_invite(self, *, reason=None, **fields):
+ async def create_invite(self, *, reason=None, **fields):
"""|coro|
Creates an instant invite.
@@ -624,11 +619,10 @@ class GuildChannel:
The invite that was created.
"""
- data = yield from self._state.http.create_invite(self.id, reason=reason, **fields)
+ data = await self._state.http.create_invite(self.id, reason=reason, **fields)
return Invite.from_incomplete(data=data, state=self._state)
- @asyncio.coroutine
- def invites(self):
+ async def invites(self):
"""|coro|
Returns a list of all active instant invites from this channel.
@@ -649,7 +643,7 @@ class GuildChannel:
"""
state = self._state
- data = yield from state.http.invites_from_channel(self.id)
+ data = await state.http.invites_from_channel(self.id)
result = []
for invite in data:
@@ -676,13 +670,11 @@ class Messageable(metaclass=abc.ABCMeta):
__slots__ = ()
- @asyncio.coroutine
@abc.abstractmethod
- def _get_channel(self):
+ async def _get_channel(self):
raise NotImplementedError
- @asyncio.coroutine
- def send(self, content=None, *, tts=False, embed=None, file=None, files=None, delete_after=None, nonce=None):
+ async def send(self, content=None, *, tts=False, embed=None, file=None, files=None, delete_after=None, nonce=None):
"""|coro|
Sends a message to the destination with the content given.
@@ -735,7 +727,7 @@ class Messageable(metaclass=abc.ABCMeta):
The message that was sent.
"""
- channel = yield from self._get_channel()
+ channel = await self._get_channel()
state = self._state
content = str(content) if content is not None else None
if embed is not None:
@@ -749,7 +741,7 @@ class Messageable(metaclass=abc.ABCMeta):
raise InvalidArgument('file parameter must be File')
try:
- data = yield from state.http.send_files(channel.id, files=[(file.open_file(), file.filename)],
+ data = await state.http.send_files(channel.id, files=[(file.open_file(), file.filename)],
content=content, tts=tts, embed=embed, nonce=nonce)
finally:
file.close()
@@ -760,28 +752,26 @@ class Messageable(metaclass=abc.ABCMeta):
try:
param = [(f.open_file(), f.filename) for f in files]
- data = yield from state.http.send_files(channel.id, files=param, content=content, tts=tts,
+ data = await state.http.send_files(channel.id, files=param, content=content, tts=tts,
embed=embed, nonce=nonce)
finally:
for f in files:
f.close()
else:
- data = yield from state.http.send_message(channel.id, content, tts=tts, embed=embed, nonce=nonce)
+ data = await state.http.send_message(channel.id, content, tts=tts, embed=embed, nonce=nonce)
ret = state.create_message(channel=channel, data=data)
if delete_after is not None:
- @asyncio.coroutine
- def delete():
- yield from asyncio.sleep(delete_after, loop=state.loop)
+ async def delete():
+ await asyncio.sleep(delete_after, loop=state.loop)
try:
- yield from ret.delete()
+ await ret.delete()
except:
pass
- compat.create_task(delete(), loop=state.loop)
+ asyncio.ensure_future(delete(), loop=state.loop)
return ret
- @asyncio.coroutine
- def trigger_typing(self):
+ async def trigger_typing(self):
"""|coro|
Triggers a *typing* indicator to the destination.
@@ -789,8 +779,8 @@ class Messageable(metaclass=abc.ABCMeta):
*Typing* indicator will go away after 10 seconds, or after a message is sent.
"""
- channel = yield from self._get_channel()
- yield from self._state.http.send_typing(channel.id)
+ channel = await self._get_channel()
+ await self._state.http.send_typing(channel.id)
def typing(self):
"""Returns a context manager that allows you to type for an indefinite period of time.
@@ -811,8 +801,7 @@ class Messageable(metaclass=abc.ABCMeta):
"""
return Typing(self)
- @asyncio.coroutine
- def get_message(self, id):
+ async def get_message(self, id):
"""|coro|
Retrieves a single :class:`Message` from the destination.
@@ -839,12 +828,11 @@ class Messageable(metaclass=abc.ABCMeta):
Retrieving the message failed.
"""
- channel = yield from self._get_channel()
- data = yield from self._state.http.get_message(channel.id, id)
+ channel = await self._get_channel()
+ data = await self._state.http.get_message(channel.id, id)
return self._state.create_message(channel=channel, data=data)
- @asyncio.coroutine
- def pins(self):
+ async def pins(self):
"""|coro|
Returns a :class:`list` of :class:`Message` that are currently pinned.
@@ -855,9 +843,9 @@ class Messageable(metaclass=abc.ABCMeta):
Retrieving the pinned messages failed.
"""
- channel = yield from self._get_channel()
+ channel = await self._get_channel()
state = self._state
- data = yield from state.http.pins_from(channel.id)
+ data = await state.http.pins_from(channel.id)
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):
@@ -916,19 +904,6 @@ class Messageable(metaclass=abc.ABCMeta):
messages = await channel.history(limit=123).flatten()
# messages is now a list of Message...
-
- Python 3.4 Usage ::
-
- count = 0
- iterator = channel.history(limit=200)
- while True:
- try:
- message = yield from iterator.next()
- except discord.NoMoreItems:
- break
- else:
- if message.author == client.user:
- counter += 1
"""
return HistoryIterator(self, limit=limit, before=before, after=after, around=around, reverse=reverse)
@@ -951,8 +926,7 @@ class Connectable(metaclass=abc.ABCMeta):
def _get_voice_state_pair(self):
raise NotImplementedError
- @asyncio.coroutine
- def connect(self, *, timeout=60.0, reconnect=True):
+ async def connect(self, *, timeout=60.0, reconnect=True):
"""|coro|
Connects to voice and creates a :class:`VoiceClient` to establish
@@ -991,10 +965,10 @@ class Connectable(metaclass=abc.ABCMeta):
state._add_voice_client(key_id, voice)
try:
- yield from voice.connect(reconnect=reconnect)
+ await voice.connect(reconnect=reconnect)
except asyncio.TimeoutError as e:
try:
- yield from voice.disconnect(force=True)
+ await voice.disconnect(force=True)
except:
# we don't care if disconnect failed because connection failed
pass
diff --git a/discord/channel.py b/discord/channel.py
index ac5f3383..27b6301e 100644
--- a/discord/channel.py
+++ b/discord/channel.py
@@ -37,10 +37,9 @@ import asyncio
__all__ = ('TextChannel', 'VoiceChannel', 'DMChannel', 'CategoryChannel', 'GroupChannel', '_channel_factory')
-def _single_delete_strategy(messages):
+async def _single_delete_strategy(messages):
for m in messages:
- yield from m.delete()
+ await m.delete()
class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable):
"""Represents a Discord guild text channel.
@@ -100,8 +99,7 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable):
self.nsfw = data.get('nsfw', False)
self._fill_overwrites(data)
- @asyncio.coroutine
- def _get_channel(self):
+ async def _get_channel(self):
return self
def permissions_for(self, member):
@@ -124,8 +122,7 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable):
n = self.name
return self.nsfw or n == 'nsfw' or n[:5] == 'nsfw-'
- @asyncio.coroutine
- def edit(self, *, reason=None, **options):
+ async def edit(self, *, reason=None, **options):
"""|coro|
Edits the channel.
@@ -161,10 +158,9 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable):
HTTPException
Editing the channel failed.
"""
- yield from self._edit(options, reason=reason)
+ await self._edit(options, reason=reason)
- @asyncio.coroutine
- def delete_messages(self, messages):
+ async def delete_messages(self, messages):
"""|coro|
Deletes a list of messages. This is similar to :meth:`Message.delete`
@@ -205,17 +201,16 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable):
if len(messages) == 1:
message_id = messages[0].id
- yield from self._state.http.delete_message(self.id, message_id)
+ await self._state.http.delete_message(self.id, message_id)
return
if len(messages) > 100:
raise ClientException('Can only bulk delete messages up to 100 messages')
message_ids = [m.id for m in messages]
- yield from self._state.http.delete_messages(self.id, message_ids)
+ await self._state.http.delete_messages(self.id, message_ids)
- @asyncio.coroutine
- def purge(self, *, limit=100, check=None, before=None, after=None, around=None, reverse=False, bulk=True):
+ async def purge(self, *, limit=100, check=None, before=None, after=None, around=None, reverse=False, bulk=True):
"""|coro|
Purges a list of messages that meet the criteria given by the predicate
@@ -289,34 +284,34 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable):
while True:
try:
- msg = yield from iterator.next()
+ msg = await iterator.next()
except NoMoreItems:
# no more messages to poll
if count >= 2:
# more than 2 messages -> bulk delete
to_delete = ret[-count:]
- yield from strategy(to_delete)
+ await strategy(to_delete)
elif count == 1:
# delete a single message
- yield from ret[-1].delete()
+ await ret[-1].delete()
return ret
else:
if count == 100:
# we've reached a full 'queue'
to_delete = ret[-100:]
- yield from strategy(to_delete)
+ await strategy(to_delete)
count = 0
- yield from asyncio.sleep(1)
+ await asyncio.sleep(1)
if check(msg):
if msg.id < minimum_time:
# older than 14 days old
if count == 1:
- yield from ret[-1].delete()
+ await ret[-1].delete()
elif count >= 2:
to_delete = ret[-count:]
- yield from strategy(to_delete)
+ await strategy(to_delete)
count = 0
strategy = _single_delete_strategy
@@ -324,8 +319,7 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable):
count += 1
ret.append(msg)
- @asyncio.coroutine
- def webhooks(self):
+ async def webhooks(self):
"""|coro|
Gets the list of webhooks from this channel.
@@ -343,11 +337,10 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable):
The webhooks for this channel.
"""
- data = yield from self._state.http.channel_webhooks(self.id)
+ data = await self._state.http.channel_webhooks(self.id)
return [Webhook.from_state(d, state=self._state) for d in data]
- @asyncio.coroutine
- def create_webhook(self, *, name=None, avatar=None):
+ async def create_webhook(self, *, name=None, avatar=None):
"""|coro|
Creates a webhook for this channel.
@@ -381,7 +374,7 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable):
if name is not None:
name = str(name)
- data = yield from self._state.http.create_webhook(self.id, name=name, avatar=avatar)
+ data = await self._state.http.create_webhook(self.id, name=name, avatar=avatar)
return Webhook.from_state(data, state=self._state)
class VoiceChannel(discord.abc.Connectable, discord.abc.GuildChannel, Hashable):
@@ -461,8 +454,7 @@ class VoiceChannel(discord.abc.Connectable, discord.abc.GuildChannel, Hashable):
ret.append(member)
return ret
- @asyncio.coroutine
- def edit(self, *, reason=None, **options):
+ async def edit(self, *, reason=None, **options):
"""|coro|
Edits the channel.
@@ -497,7 +489,7 @@ class VoiceChannel(discord.abc.Connectable, discord.abc.GuildChannel, Hashable):
Editing the channel failed.
"""
- yield from self._edit(options, reason=reason)
+ await self._edit(options, reason=reason)
class CategoryChannel(discord.abc.GuildChannel, Hashable):
"""Represents a Discord channel category.
@@ -558,8 +550,7 @@ class CategoryChannel(discord.abc.GuildChannel, Hashable):
n = self.name
return self.nsfw or n == 'nsfw' or n[:5] == 'nsfw-'
- @asyncio.coroutine
- def edit(self, *, reason=None, **options):
+ async def edit(self, *, reason=None, **options):
"""|coro|
Edits the channel.
@@ -593,11 +584,11 @@ class CategoryChannel(discord.abc.GuildChannel, Hashable):
except KeyError:
pass
else:
- yield from self._move(position, reason=reason)
+ await self._move(position, reason=reason)
self.position = position
if options:
- data = yield from self._state.http.edit_channel(self.id, reason=reason, **options)
+ data = await self._state.http.edit_channel(self.id, reason=reason, **options)
self._update(self.guild, data)
@property
@@ -652,8 +643,7 @@ class DMChannel(discord.abc.Messageable, Hashable):
self.me = me
self.id = int(data['id'])
- @asyncio.coroutine
- def _get_channel(self):
+ async def _get_channel(self):
return self
def __str__(self):
@@ -756,8 +746,7 @@ class GroupChannel(discord.abc.Messageable, Hashable):
else:
self.owner = utils.find(lambda u: u.id == owner_id, self.recipients)
- @asyncio.coroutine
- def _get_channel(self):
+ async def _get_channel(self):
return self
def __str__(self):
@@ -820,8 +809,7 @@ class GroupChannel(discord.abc.Messageable, Hashable):
return base
- @asyncio.coroutine
- def add_recipients(self, *recipients):
+ async def add_recipients(self, *recipients):
"""|coro|
Adds recipients to this group.
@@ -846,10 +834,9 @@ class GroupChannel(discord.abc.Messageable, Hashable):
req = self._state.http.add_group_recipient
for recipient in recipients:
- yield from req(self.id, recipient.id)
+ await req(self.id, recipient.id)
- @asyncio.coroutine
- def remove_recipients(self, *recipients):
+ async def remove_recipients(self, *recipients):
"""|coro|
Removes recipients from this group.
@@ -869,10 +856,9 @@ class GroupChannel(discord.abc.Messageable, Hashable):
req = self._state.http.remove_group_recipient
for recipient in recipients:
- yield from req(self.id, recipient.id)
+ await req(self.id, recipient.id)
- @asyncio.coroutine
- def edit(self, **fields):
+ async def edit(self, **fields):
"""|coro|
Edits the group.
@@ -900,11 +886,10 @@ class GroupChannel(discord.abc.Messageable, Hashable):
if icon_bytes is not None:
fields['icon'] = utils._bytes_to_base64_data(icon_bytes)
- data = yield from self._state.http.edit_group(self.id, **fields)
+ data = await self._state.http.edit_group(self.id, **fields)
self._update_group(data)
- @asyncio.coroutine
- def leave(self):
+ async def leave(self):
"""|coro|
Leave the group.
@@ -917,7 +902,7 @@ class GroupChannel(discord.abc.Messageable, Hashable):
Leaving the group failed.
"""
- yield from self._state.http.leave_group(self.id)
+ await self._state.http.leave_group(self.id)
def _channel_factory(channel_type):
value = try_enum(ChannelType, channel_type)
diff --git a/discord/client.py b/discord/client.py
index 0434d1e2..a46f1637 100644
--- a/discord/client.py
+++ b/discord/client.py
@@ -48,7 +48,6 @@ import sys, re
import signal
from collections import namedtuple
-PY35 = sys.version_info >= (3, 5)
log = logging.getLogger(__name__)
AppInfo = namedtuple('AppInfo', 'id name description icon owner')
@@ -139,12 +138,10 @@ class Client:
# internals
- @asyncio.coroutine
- def _syncer(self, guilds):
- yield from self.ws.request_sync(guilds)
+ async def _syncer(self, guilds):
+ await self.ws.request_sync(guilds)
- @asyncio.coroutine
- def _chunker(self, guild):
+ async def _chunker(self, guild):
try:
guild_id = guild.id
except AttributeError:
@@ -159,7 +156,7 @@ class Client:
}
}
- yield from self.ws.send_as_json(payload)
+ await self.ws.send_as_json(payload)
def handle_ready(self):
self._ready.set()
@@ -218,15 +215,14 @@ class Client:
""":obj:`bool`: Specifies if the client's internal cache is ready for use."""
return self._ready.is_set()
- @asyncio.coroutine
- def _run_event(self, coro, event_name, *args, **kwargs):
+ async def _run_event(self, coro, event_name, *args, **kwargs):
try:
- yield from coro(*args, **kwargs)
+ await coro(*args, **kwargs)
except asyncio.CancelledError:
pass
except Exception:
try:
- yield from self.on_error(event_name, *args, **kwargs)
+ await self.on_error(event_name, *args, **kwargs)
except asyncio.CancelledError:
pass
@@ -276,10 +272,9 @@ class Client:
except AttributeError:
pass
else:
- compat.create_task(self._run_event(coro, method, *args, **kwargs), loop=self.loop)
+ asyncio.ensure_future(self._run_event(coro, method, *args, **kwargs), loop=self.loop)
- @asyncio.coroutine
- def on_error(self, event_method, *args, **kwargs):
+ async def on_error(self, event_method, *args, **kwargs):
"""|coro|
The default error handler provided by the client.
@@ -291,8 +286,7 @@ class Client:
print('Ignoring exception in {}'.format(event_method), file=sys.stderr)
traceback.print_exc()
- @asyncio.coroutine
- def request_offline_members(self, *guilds):
+ async def request_offline_members(self, *guilds):
"""|coro|
Requests previously offline members from the guild to be filled up
@@ -318,12 +312,11 @@ class Client:
if any(not g.large or g.unavailable for g in guilds):
raise InvalidArgument('An unavailable or non-large guild was passed.')
- yield from self._connection.request_offline_members(guilds)
+ await self._connection.request_offline_members(guilds)
# login state management
- @asyncio.coroutine
- def login(self, token, *, bot=True):
+ async def login(self, token, *, bot=True):
"""|coro|
Logs in the client with the specified credentials.
@@ -350,34 +343,31 @@ class Client:
"""
log.info('logging in using static token')
- yield from self.http.static_login(token, bot=bot)
+ await self.http.static_login(token, bot=bot)
self._connection.is_bot = bot
- @asyncio.coroutine
- def logout(self):
+ async def logout(self):
"""|coro|
Logs out of Discord and closes all connections.
"""
- yield from self.close()
+ await self.close()
- @asyncio.coroutine
- def _connect(self):
+ async def _connect(self):
coro = DiscordWebSocket.from_client(self, shard_id=self.shard_id)
- self.ws = yield from asyncio.wait_for(coro, timeout=180.0, loop=self.loop)
+ self.ws = await asyncio.wait_for(coro, timeout=180.0, loop=self.loop)
while True:
try:
- yield from self.ws.poll_event()
+ await self.ws.poll_event()
except ResumeWebSocket as e:
log.info('Got a request to RESUME the websocket.')
coro = DiscordWebSocket.from_client(self, shard_id=self.shard_id,
session=self.ws.session_id,
sequence=self.ws.sequence,
resume=True)
- self.ws = yield from asyncio.wait_for(coro, timeout=180.0, loop=self.loop)
+ self.ws = await asyncio.wait_for(coro, timeout=180.0, loop=self.loop)
- @asyncio.coroutine
- def connect(self, *, reconnect=True):
+ async def connect(self, *, reconnect=True):
"""|coro|
Creates a websocket connection and lets the websocket listen
@@ -405,7 +395,7 @@ class Client:
backoff = ExponentialBackoff()
while not self.is_closed():
try:
- yield from self._connect()
+ await self._connect()
except (OSError,
HTTPException,
GatewayNotFound,
@@ -416,7 +406,7 @@ class Client:
websockets.WebSocketProtocolError) as e:
if not reconnect:
- yield from self.close()
+ await self.close()
if isinstance(e, ConnectionClosed) and e.code == 1000:
# clean close, don't re-raise this
return
@@ -431,15 +421,14 @@ class Client:
# regardless and rely on is_closed instead
if isinstance(e, ConnectionClosed):
if e.code != 1000:
- yield from self.close()
+ await self.close()
raise
retry = backoff.delay()
log.exception("Attempting a reconnect in %.2fs", retry)
- yield from asyncio.sleep(retry, loop=self.loop)
+ await asyncio.sleep(retry, loop=self.loop)
- @asyncio.coroutine
- def close(self):
+ async def close(self):
"""|coro|
Closes the connection to discord.
@@ -451,16 +440,16 @@ class Client:
for voice in self.voice_clients:
try:
- yield from voice.disconnect()
+ await voice.disconnect()
except:
# if an error happens during disconnects, disregard it.
pass
if self.ws is not None and self.ws.open:
- yield from self.ws.close()
+ await self.ws.close()
- yield from self.http.close()
+ await self.http.close()
self._ready.clear()
def clear(self):
@@ -475,8 +464,7 @@ class Client:
self._connection.clear()
self.http.recreate()
- @asyncio.coroutine
- def start(self, *args, **kwargs):
+ async def start(self, *args, **kwargs):
"""|coro|
A shorthand coroutine for :meth:`login` + :meth:`connect`.
@@ -484,8 +472,8 @@ class Client:
bot = kwargs.pop('bot', True)
reconnect = kwargs.pop('reconnect', True)
- yield from self.login(*args, bot=bot)
- yield from self.connect(reconnect=reconnect)
+ await self.login(*args, bot=bot)
+ await self.connect(reconnect=reconnect)
def _do_cleanup(self):
log.info('Cleaning up event loop.')
@@ -493,7 +481,7 @@ class Client:
if loop.is_closed():
return # we're already cleaning up
- task = compat.create_task(self.close(), loop=loop)
+ task = asyncio.ensure_future(self.close(), loop=loop)
def _silence_gathered(fut):
try:
@@ -558,7 +546,7 @@ class Client:
loop.add_signal_handler(signal.SIGINT, self._do_cleanup)
loop.add_signal_handler(signal.SIGTERM, self._do_cleanup)
- task = compat.create_task(self.start(*args, **kwargs), loop=loop)
+ task = asyncio.ensure_future(self.start(*args, **kwargs), loop=loop)
def stop_loop_on_finish(fut):
loop.stop()
@@ -661,13 +649,12 @@ class Client:
# listeners/waiters
- @asyncio.coroutine
- def wait_until_ready(self):
+ async def wait_until_ready(self):
"""|coro|
Waits until the client's internal cache is all ready.
"""
- yield from self._ready.wait()
+ await self._ready.wait()
def wait_for(self, event, *, check=None, timeout=None):
"""|coro|
@@ -751,7 +738,7 @@ class Client:
:ref:`event reference <discord-api-events>`.
"""
- future = compat.create_future(self.loop)
+ future = self.loop.create_future()
if check is None:
def _check(*args):
return True
@@ -782,8 +769,7 @@ class Client:
Using the basic :meth:`event` decorator: ::
@client.event
- @asyncio.coroutine
- def on_ready():
+ async def on_ready():
print('Ready!')
Saving characters by using the :meth:`async_event` decorator: ::
@@ -808,8 +794,7 @@ class Client:
return self.event(coro)
- @asyncio.coroutine
- def change_presence(self, *, activity=None, status=None, afk=False):
+ async def change_presence(self, *, activity=None, status=None, afk=False):
"""|coro|
Changes the client's presence.
@@ -851,7 +836,7 @@ class Client:
status_enum = status
status = str(status)
- yield from self.ws.change_presence(activity=activity, status=status, afk=afk)
+ await self.ws.change_presence(activity=activity, status=status, afk=afk)
for guild in self._connection.guilds:
me = guild.me
@@ -863,8 +848,7 @@ class Client:
# Guild stuff
- @asyncio.coroutine
- def create_guild(self, name, region=None, icon=None):
+ async def create_guild(self, name, region=None, icon=None):
"""|coro|
Creates a :class:`Guild`.
@@ -903,13 +887,12 @@ class Client:
else:
region = region.value
- data = yield from self.http.create_guild(name, region, icon)
+ data = await self.http.create_guild(name, region, icon)
return Guild(data=data, state=self._connection)
# Invite management
- @asyncio.coroutine
- def get_invite(self, url):
+ async def get_invite(self, url):
"""|coro|
Gets an :class:`Invite` from a discord.gg URL or ID.
@@ -939,11 +922,10 @@ class Client:
"""
invite_id = self._resolve_invite(url)
- data = yield from self.http.get_invite(invite_id)
+ data = await self.http.get_invite(invite_id)
return Invite.from_incomplete(state=self._connection, data=data)
- @asyncio.coroutine
- def delete_invite(self, invite):
+ async def delete_invite(self, invite):
"""|coro|
Revokes an :class:`Invite`, URL, or ID to an invite.
@@ -967,12 +949,11 @@ class Client:
"""
invite_id = self._resolve_invite(invite)
- yield from self.http.delete_invite(invite_id)
+ await self.http.delete_invite(invite_id)
# Miscellaneous stuff
- @asyncio.coroutine
- def application_info(self):
+ async def application_info(self):
"""|coro|
Retrieve's the bot's application information.
@@ -987,13 +968,12 @@ class Client:
HTTPException
Retrieving the information failed somehow.
"""
- data = yield from self.http.application_info()
+ data = await self.http.application_info()
return AppInfo(id=int(data['id']), name=data['name'],
description=data['description'], icon=data['icon'],
owner=User(state=self._connection, data=data['owner']))
- @asyncio.coroutine
- def get_user_info(self, user_id):
+ async def get_user_info(self, user_id):
"""|coro|
Retrieves a :class:`User` based on their ID. This can only
@@ -1018,11 +998,10 @@ class Client:
HTTPException
Fetching the user failed.
"""
- data = yield from self.http.get_user_info(user_id)
+ data = await self.http.get_user_info(user_id)
return User(state=self._connection, data=data)
- @asyncio.coroutine
- def get_user_profile(self, user_id):
+ async def get_user_profile(self, user_id):
"""|coro|
Gets an arbitrary user's profile. This can only be used by non-bot accounts.
@@ -1046,7 +1025,7 @@ class Client:
"""
state = self._connection
- data = yield from self.http.get_user_profile(user_id)
+ data = await self.http.get_user_profile(user_id)
def transform(d):
return state._get_guild(int(d['id']))
@@ -1060,8 +1039,7 @@ class Client:
user=User(data=user, state=state),
connected_accounts=data['connected_accounts'])
- @asyncio.coroutine
- def get_webhook_info(self, webhook_id):
+ async def get_webhook_info(self, webhook_id):
"""|coro|
Retrieves a :class:`Webhook` with the specified ID.
@@ -1080,5 +1058,5 @@ class Client:
:class:`Webhook`
The webhook you requested.
"""
- data = yield from self.http.get_webhook(webhook_id)
+ data = await self.http.get_webhook(webhook_id)
return Webhook.from_state(data, state=self._connection)
diff --git a/discord/compat.py b/discord/compat.py
deleted file mode 100644
index 9c51e784..00000000
--- a/discord/compat.py
+++ /dev/null
@@ -1,140 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
-The MIT License (MIT)
-
-Copyright (c) 2015-2017 Rapptz
-
-Permission is hereby granted, free of charge, to any person obtaining a
-copy of this software and associated documentation files (the "Software"),
-to deal in the Software without restriction, including without limitation
-the rights to use, copy, modify, merge, publish, distribute, sublicense,
-and/or sell copies of the Software, and to permit persons to whom the
-Software is furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in
-all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
-OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
-DEALINGS IN THE SOFTWARE.
-"""
-
-import concurrent.futures
-import asyncio
-
-try:
- create_task = asyncio.ensure_future
-except AttributeError:
- create_task = getattr(asyncio, 'async')
-
-try:
- _create_future = asyncio.AbstractEventLoop.create_future
-except AttributeError:
- def create_future(loop):
- return asyncio.Future(loop=loop)
-else:
- def create_future(loop):
- return loop.create_future()
-
-try:
- run_coroutine_threadsafe = asyncio.run_coroutine_threadsafe
-except AttributeError:
- # the following code is slightly modified from the
- # official asyncio repository that could be found here:
- # https://github.com/python/asyncio/blob/master/asyncio/futures.py
- # with a commit hash of 5c7efbcdfbe6a5c25b4cd5df22d9a15ab4062c8e
- # this portion is licensed under Apache license 2.0
-
- def _set_concurrent_future_state(concurrent, source):
- """Copy state from a future to a concurrent.futures.Future."""
- assert source.done()
- if source.cancelled():
- concurrent.cancel()
- if not concurrent.set_running_or_notify_cancel():
- return
- exception = source.exception()
- if exception is not None:
- concurrent.set_exception(exception)
- else:
- result = source.result()
- concurrent.set_result(result)
-
-
- def _copy_future_state(source, dest):
- """Internal helper to copy state from another Future.
- The other Future may be a concurrent.futures.Future.
- """
- assert source.done()
- if dest.cancelled():
- return
- assert not dest.done()
- if source.cancelled():
- dest.cancel()
- else:
- exception = source.exception()
- if exception is not None:
- dest.set_exception(exception)
- else:
- result = source.result()
- dest.set_result(result)
-
- def _chain_future(source, destination):
- """Chain two futures so that when one completes, so does the other.
- The result (or exception) of source will be copied to destination.
- If destination is cancelled, source gets cancelled too.
- Compatible with both asyncio.Future and concurrent.futures.Future.
- """
- if not isinstance(source, (asyncio.Future, concurrent.futures.Future)):
- raise TypeError('A future is required for source argument')
-
- if not isinstance(destination, (asyncio.Future, concurrent.futures.Future)):
- raise TypeError('A future is required for destination argument')
-
- source_loop = source._loop if isinstance(source, asyncio.Future) else None
- dest_loop = destination._loop if isinstance(destination, asyncio.Future) else None
-
- def _set_state(future, other):
- if isinstance(future, asyncio.Future):
- _copy_future_state(other, future)
- else:
- _set_concurrent_future_state(future, other)
-
- def _call_check_cancel(destination):
- if destination.cancelled():
- if source_loop is None or source_loop is dest_loop:
- source.cancel()
- else:
- source_loop.call_soon_threadsafe(source.cancel)
-
- def _call_set_state(source):
- if dest_loop is None or dest_loop is source_loop:
- _set_state(destination, source)
- else:
- dest_loop.call_soon_threadsafe(_set_state, destination, source)
-
- destination.add_done_callback(_call_check_cancel)
- source.add_done_callback(_call_set_state)
-
- def run_coroutine_threadsafe(coro, loop):
- """Submit a coroutine object to a given event loop.
-
- Return a concurrent.futures.Future to access the result.
- """
- if not asyncio.iscoroutine(coro):
- raise TypeError('A coroutine object is required')
-
- future = concurrent.futures.Future()
-
- def callback():
- try:
- _chain_future(create_task(coro, loop=loop), future)
- except Exception as exc:
- if future.set_running_or_notify_cancel():
- future.set_exception(exc)
- raise
- loop.call_soon_threadsafe(callback)
- return future
diff --git a/discord/context_managers.py b/discord/context_managers.py
index 93d292c2..94f817d4 100644
--- a/discord/context_managers.py
+++ b/discord/context_managers.py
@@ -40,18 +40,17 @@ class Typing:
self.loop = messageable._state.loop
self.messageable = messageable
- @asyncio.coroutine
- def do_typing(self):
+ async def do_typing(self):
try:
channel = self._channel
except AttributeError:
- channel = yield from self.messageable._get_channel()
+ channel = await self.messageable._get_channel()
typing = channel._state.http.send_typing
while True:
- yield from typing(channel.id)
- yield from asyncio.sleep(5)
+ await typing(channel.id)
+ await asyncio.sleep(5)
def __enter__(self):
self.task = create_task(self.do_typing(), loop=self.loop)
@@ -61,12 +60,10 @@ class Typing:
def __exit__(self, exc_type, exc, tb):
self.task.cancel()
- @asyncio.coroutine
- def __aenter__(self):
- self._channel = channel = yield from self.messageable._get_channel()
- yield from channel._state.http.send_typing(channel.id)
+ async def __aenter__(self):
+ self._channel = channel = await self.messageable._get_channel()
+ await channel._state.http.send_typing(channel.id)
return self.__enter__()
- @asyncio.coroutine
- def __aexit__(self, exc_type, exc, tb):
+ async def __aexit__(self, exc_type, exc, tb):
self.task.cancel()
diff --git a/discord/emoji.py b/discord/emoji.py
index 13eeb307..21c11c71 100644
--- a/discord/emoji.py
+++ b/discord/emoji.py
@@ -203,8 +203,7 @@ class Emoji(Hashable):
""":class:`Guild`: The guild this emoji belongs to."""
return self._state._get_guild(self.guild_id)
- @asyncio.coroutine
- def delete(self, *, reason=None):
+ async def delete(self, *, reason=None):
"""|coro|
Deletes the custom emoji.
@@ -227,10 +226,9 @@ class Emoji(Hashable):
An error occurred deleting the emoji.
"""
- yield from self._state.http.delete_custom_emoji(self.guild.id, self.id, reason=reason)
+ await self._state.http.delete_custom_emoji(self.guild.id, self.id, reason=reason)
- @asyncio.coroutine
- def edit(self, *, name, reason=None):
+ async def edit(self, *, name, reason=None):
"""|coro|
Edits the custom emoji.
@@ -255,4 +253,4 @@ class Emoji(Hashable):
An error occurred editing the emoji.
"""
- yield from self._state.http.edit_custom_emoji(self.guild.id, self.id, name=name, reason=reason)
+ await self._state.http.edit_custom_emoji(self.guild.id, self.id, name=name, reason=reason)
diff --git a/discord/errors.py b/discord/errors.py
index 95e277d0..3111675a 100644
--- a/discord/errors.py
+++ b/discord/errors.py
@@ -40,9 +40,7 @@ class ClientException(DiscordException):
class NoMoreItems(DiscordException):
"""Exception that is thrown when an async iteration operation has no more
- items. This is mainly exposed for Python 3.4 support where `StopAsyncIteration`
- is not provided.
- """
+ items."""
pass
class GatewayNotFound(DiscordException):
diff --git a/discord/ext/commands/bot.py b/discord/ext/commands/bot.py
index 9ef4143c..f2b7a806 100644
--- a/discord/ext/commands/bot.py
+++ b/discord/ext/commands/bot.py
@@ -91,8 +91,7 @@ _mention_pattern = re.compile('|'.join(_mentions_transforms.keys()))
def _is_submodule(parent, child):
return parent == child or child.startswith(parent + ".")
-def _default_help_command(ctx, *commands : str):
+async def _default_help_command(ctx, *commands : str):
"""Shows this message."""
bot = ctx.bot
destination = ctx.message.author if bot.pm_help else ctx.message.channel
@@ -102,7 +101,7 @@ def _default_help_command(ctx, *commands : str):
# help by itself just lists our own commands.
if len(commands) == 0:
- pages = yield from bot.formatter.format_help_for(ctx, bot)
+ pages = await bot.formatter.format_help_for(ctx, bot)
elif len(commands) == 1:
# try to see if it is a cog name
name = _mention_pattern.sub(repl, commands[0])
@@ -112,15 +111,15 @@ def _default_help_command(ctx, *commands : str):
else:
command = bot.all_commands.get(name)
if command is None:
- yield from destination.send(bot.command_not_found.format(name))
+ await destination.send(bot.command_not_found.format(name))
return
- pages = yield from bot.formatter.format_help_for(ctx, command)
+ pages = await bot.formatter.format_help_for(ctx, command)
else:
name = _mention_pattern.sub(repl, commands[0])
command = bot.all_commands.get(name)
if command is None:
- yield from destination.send(bot.command_not_found.format(name))
+ await destination.send(bot.command_not_found.format(name))
return
for key in commands[1:]:
@@ -128,13 +127,13 @@ def _default_help_command(ctx, *commands : str):
key = _mention_pattern.sub(repl, key)
command = command.all_commands.get(key)
if command is None:
- yield from destination.send(bot.command_not_found.format(key))
+ await destination.send(bot.command_not_found.format(key))
return
except AttributeError:
- yield from destination.send(bot.command_has_no_subcommands.format(command, key))
+ await destination.send(bot.command_has_no_subcommands.format(command, key))
return
- pages = yield from bot.formatter.format_help_for(ctx, command)
+ pages = await bot.formatter.format_help_for(ctx, command)
if bot.pm_help is None:
characters = sum(map(lambda l: len(l), pages))
@@ -143,7 +142,7 @@ def _default_help_command(ctx, *commands : str):
destination = ctx.message.author
for page in pages:
- yield from destination.send(page)
+ await destination.send(page)
class BotBase(GroupMixin):
def __init__(self, command_prefix, formatter=None, description=None, pm_help=False, **options):
@@ -189,10 +188,9 @@ class BotBase(GroupMixin):
ev = 'on_' + event_name
for event in self.extra_events.get(ev, []):
coro = self._run_event(event, event_name, *args, **kwargs)
- discord.compat.create_task(coro, loop=self.loop)
+ asyncio.ensure_future(coro, loop=self.loop)
- @asyncio.coroutine
- def close(self):
+ async def close(self):
for extension in tuple(self.extensions):
try:
self.unload_extension(extension)
@@ -205,10 +203,9 @@ class BotBase(GroupMixin):
except:
pass
- yield from super().close()
+ await super().close()
- @asyncio.coroutine
- def on_command_error(self, context, exception):
+ async def on_command_error(self, context, exception):
"""|coro|
The default command error handler provided by the bot.
@@ -335,17 +332,15 @@ class BotBase(GroupMixin):
self.add_check(func, call_once=True)
return func
- @asyncio.coroutine
- def can_run(self, ctx, *, call_once=False):
+ async def can_run(self, ctx, *, call_once=False):
data = self._check_once if call_once else self._checks
if len(data) == 0:
return True
- return (yield from discord.utils.async_all(f(ctx) for f in data))
+ return (await discord.utils.async_all(f(ctx) for f in data))
- @asyncio.coroutine
- def is_owner(self, user):
+ async def is_owner(self, user):
"""Checks if a :class:`.User` or :class:`.Member` is the owner of
this bot.
@@ -359,7 +354,7 @@ class BotBase(GroupMixin):
"""
if self.owner_id is None:
- app = yield from self.application_info()
+ app = await self.application_info()
self.owner_id = owner_id = app.owner.id
return user.id == owner_id
return user.id == self.owner_id
@@ -773,8 +768,7 @@ class BotBase(GroupMixin):
# command processing
- @asyncio.coroutine
- def get_prefix(self, message):
+ async def get_prefix(self, message):
"""|coro|
Retrieves the prefix the bot is listening to
@@ -801,9 +795,7 @@ class BotBase(GroupMixin):
"""
prefix = ret = self.command_prefix
if callable(prefix):
- ret = prefix(self, message)
- if asyncio.iscoroutine(ret):
- ret = yield from ret
+ ret = await discord.utils.maybe_coroutine(prefix, self, ret)
if isinstance(ret, (list, tuple)):
ret = [p for p in ret if p]
@@ -813,8 +805,7 @@ class BotBase(GroupMixin):
return ret
- @asyncio.coroutine
- def get_context(self, message, *, cls=Context):
+ async def get_context(self, message, *, cls=Context):
"""|coro|
Returns the invocation context from the message.
@@ -850,7 +841,7 @@ class BotBase(GroupMixin):
if self._skip_check(message.author.id, self.user.id):
return ctx
- prefix = yield from self.get_prefix(message)
+ prefix = await self.get_prefix(message)
invoked_prefix = prefix
if isinstance(prefix, str):
@@ -867,8 +858,7 @@ class BotBase(GroupMixin):
ctx.command = self.all_commands.get(invoker)
return ctx
- @asyncio.coroutine
- def invoke(self, ctx):
+ async def invoke(self, ctx):
"""|coro|
Invokes the command given under the invocation context and
@@ -882,18 +872,17 @@ class BotBase(GroupMixin):
if ctx.command is not None:
self.dispatch('command', ctx)
try:
- if (yield from self.can_run(ctx, call_once=True)):
- yield from ctx.command.invoke(ctx)
+ if (await self.can_run(ctx, call_once=True)):
+ await ctx.command.invoke(ctx)
except CommandError as e:
- yield from ctx.command.dispatch_error(ctx, e)
+ await ctx.command.dispatch_error(ctx, e)
else:
self.dispatch('command_completion', ctx)
elif ctx.invoked_with:
exc = CommandNotFound('Command "{}" is not found'.format(ctx.invoked_with))
self.dispatch('command_error', ctx, exc)
- @asyncio.coroutine
- def process_commands(self, message):
+ async def process_commands(self, message):
"""|coro|
This function processes the commands that have been registered
@@ -912,12 +901,11 @@ class BotBase(GroupMixin):
message : discord.Message
The message to process commands for.
"""
- ctx = yield from self.get_context(message)
- yield from self.invoke(ctx)
+ ctx = await self.get_context(message)
+ await self.invoke(ctx)
- @asyncio.coroutine
- def on_message(self, message):
- yield from self.process_commands(message)
+ async def on_message(self, message):
+ await self.process_commands(message)
class Bot(BotBase, discord.Client):
"""Represents a discord bot.
diff --git a/discord/ext/commands/context.py b/discord/ext/commands/context.py
index 303c1051..dfe1a9ed 100644
--- a/discord/ext/commands/context.py
+++ b/discord/ext/commands/context.py
@@ -86,8 +86,7 @@ class Context(discord.abc.Messageable):
self.command_failed = attrs.pop('command_failed', False)
self._state = self.message._state
- @asyncio.coroutine
- def invoke(self, *args, **kwargs):
+ async def invoke(self, *args, **kwargs):
"""|coro|
Calls a command with the arguments given.
@@ -125,11 +124,10 @@ class Context(discord.abc.Messageable):
arguments.append(self)
arguments.extend(args[1:])
- ret = yield from command.callback(*arguments, **kwargs)
+ ret = await command.callback(*arguments, **kwargs)
return ret
- @asyncio.coroutine
- def reinvoke(self, *, call_hooks=False, restart=True):
+ async def reinvoke(self, *, call_hooks=False, restart=True):
"""|coro|
Calls the command again.
@@ -174,7 +172,7 @@ class Context(discord.abc.Messageable):
to_call = cmd
try:
- yield from to_call.reinvoke(self, call_hooks=call_hooks)
+ await to_call.reinvoke(self, call_hooks=call_hooks)
finally:
self.command = cmd
view.index = index
@@ -188,8 +186,7 @@ class Context(discord.abc.Messageable):
"""Checks if the invocation context is valid to be invoked with."""
return self.prefix is not None and self.command is not None
- @asyncio.coroutine
- def _get_channel(self):
+ async def _get_channel(self):
return self.channel
@property
diff --git a/discord/ext/commands/converter.py b/discord/ext/commands/converter.py
index 9e3718b0..72b2cbf8 100644
--- a/discord/ext/commands/converter.py
+++ b/discord/ext/commands/converter.py
@@ -57,8 +57,7 @@ class Converter:
method to do its conversion logic. This method must be a coroutine.
"""
- @asyncio.coroutine
- def convert(self, ctx, argument):
+ async def convert(self, ctx, argument):
"""|coro|
The method to override to do conversion logic.
@@ -99,8 +98,7 @@ class MemberConverter(IDConverter):
5. Lookup by nickname
"""
- @asyncio.coroutine
- def convert(self, ctx, argument):
+ async def convert(self, ctx, argument):
message = ctx.message
bot = ctx.bot
match = self._get_id_match(argument) or re.match(r'<@!?([0-9]+)>$', argument)
@@ -136,8 +134,7 @@ class UserConverter(IDConverter):
3. Lookup by name#discrim
4. Lookup by name
"""
- @asyncio.coroutine
- def convert(self, ctx, argument):
+ async def convert(self, ctx, argument):
match = self._get_id_match(argument) or re.match(r'<@!?([0-9]+)>$', argument)
result = None
state = ctx._state
@@ -176,8 +173,7 @@ class TextChannelConverter(IDConverter):
2. Lookup by mention.
3. Lookup by name
"""
- @asyncio.coroutine
- def convert(self, ctx, argument):
+ async def convert(self, ctx, argument):
bot = ctx.bot
match = self._get_id_match(argument) or re.match(r'<#([0-9]+)>$', argument)
@@ -216,8 +212,7 @@ class VoiceChannelConverter(IDConverter):
2. Lookup by mention.
3. Lookup by name
"""
- @asyncio.coroutine
- def convert(self, ctx, argument):
+ async def convert(self, ctx, argument):
bot = ctx.bot
match = self._get_id_match(argument) or re.match(r'<#([0-9]+)>$', argument)
result = None
@@ -255,8 +250,7 @@ class CategoryChannelConverter(IDConverter):
2. Lookup by mention.
3. Lookup by name
"""
- @asyncio.coroutine
- def convert(self, ctx, argument):
+ async def convert(self, ctx, argument):
bot = ctx.bot
match = self._get_id_match(argument) or re.match(r'<#([0-9]+)>$', argument)
@@ -295,8 +289,7 @@ class ColourConverter(Converter):
- The ``_`` in the name can be optionally replaced with spaces.
"""
- @asyncio.coroutine
- def convert(self, ctx, argument):
+ async def convert(self, ctx, argument):
arg = argument.replace('0x', '').lower()
if arg[0] == '#':
@@ -323,8 +316,7 @@ class RoleConverter(IDConverter):
2. Lookup by mention.
3. Lookup by name
"""
- @asyncio.coroutine
- def convert(self, ctx, argument):
+ async def convert(self, ctx, argument):
guild = ctx.message.guild
if not guild:
raise NoPrivateMessage()
@@ -338,8 +330,7 @@ class RoleConverter(IDConverter):
class GameConverter(Converter):
"""Converts to :class:`Game`."""
- @asyncio.coroutine
- def convert(self, ctx, argument):
+ async def convert(self, ctx, argument):
return discord.Game(name=argument)
class InviteConverter(Converter):
@@ -347,10 +338,9 @@ class InviteConverter(Converter):
This is done via an HTTP request using :meth:`.Bot.get_invite`.
"""
- @asyncio.coroutine
- def convert(self, ctx, argument):
+ async def convert(self, ctx, argument):
try:
- invite = yield from ctx.bot.get_invite(argument)
+ invite = await ctx.bot.get_invite(argument)
return invite
except Exception as e:
raise BadArgument('Invite is invalid or expired') from e
@@ -368,8 +358,7 @@ class EmojiConverter(IDConverter):
2. Lookup by extracting ID from the emoji.
3. Lookup by name
"""
- @asyncio.coroutine
- def convert(self, ctx, argument):
+ async def convert(self, ctx, argument):
match = self._get_id_match(argument) or re.match(r'<a?:[a-zA-Z0-9\_]+:([0-9]+)>$', argument)
result = None
bot = ctx.bot
@@ -403,8 +392,7 @@ class PartialEmojiConverter(Converter):
This is done by extracting the animated flag, name and ID from the emoji.
"""
- @asyncio.coroutine
- def convert(self, ctx, argument):
+ async def convert(self, ctx, argument):
match = re.match(r'<(a?):([a-zA-Z0-9\_]+):([0-9]+)>$', argument)
if match:
@@ -436,8 +424,7 @@ class clean_content(Converter):
self.use_nicknames = use_nicknames
self.escape_markdown = escape_markdown
- @asyncio.coroutine
- def convert(self, ctx, argument):
+ async def convert(self, ctx, argument):
message = ctx.message
transformations = {}
diff --git a/discord/ext/commands/core.py b/discord/ext/commands/core.py
index 927fe6fa..6c1d9a93 100644
--- a/discord/ext/commands/core.py
+++ b/discord/ext/commands/core.py
@@ -41,10 +41,9 @@ __all__ = [ 'Command', 'Group', 'GroupMixin', 'command', 'group',
def wrap_callback(coro):
@functools.wraps(coro)
- @asyncio.coroutine
- def wrapped(*args, **kwargs):
+ async def wrapped(*args, **kwargs):
try:
- ret = yield from coro(*args, **kwargs)
+ ret = await coro(*args, **kwargs)
except CommandError:
raise
except asyncio.CancelledError:
@@ -56,10 +55,9 @@ def wrap_callback(coro):
def hooked_wrapped_callback(command, ctx, coro):
@functools.wraps(coro)
- @asyncio.coroutine
- def wrapped(*args, **kwargs):
+ async def wrapped(*args, **kwargs):
try:
- ret = yield from coro(*args, **kwargs)
+ ret = await coro(*args, **kwargs)
except CommandError:
ctx.command_failed = True
raise
@@ -70,7 +68,7 @@ def hooked_wrapped_callback(command, ctx, coro):
ctx.command_failed = True
raise CommandInvokeError(e) from e
finally:
- yield from command.call_after_hooks(ctx)
+ await command.call_after_hooks(ctx)
return ret
return wrapped
@@ -182,8 +180,7 @@ class Command:
self._before_invoke = None
self._after_invoke = None
- @asyncio.coroutine
- def dispatch_error(self, ctx, error):
+ async def dispatch_error(self, ctx, error):
ctx.command_failed = True
cog = self.instance
try:
@@ -193,9 +190,9 @@ class Command:
else:
injected = wrap_callback(coro)
if cog is not None:
- yield from injected(cog, ctx, error)
+ await injected(cog, ctx, error)
else:
- yield from injected(ctx, error)
+ await injected(ctx, error)
try:
local = getattr(cog, '_{0.__class__.__name__}__error'.format(cog))
@@ -203,7 +200,7 @@ class Command:
pass
else:
wrapped = wrap_callback(local)
- yield from wrapped(ctx, error)
+ await wrapped(ctx, error)
finally:
ctx.bot.dispatch('command_error', ctx, error)
@@ -212,8 +209,7 @@ class Command:
self.instance = instance
return self
- @asyncio.coroutine
- def do_conversion(self, ctx, converter, argument):
+ async def do_conversion(self, ctx, converter, argument):
if converter is bool:
return _convert_to_bool(argument)
@@ -228,15 +224,15 @@ class Command:
if inspect.isclass(converter):
if issubclass(converter, converters.Converter):
instance = converter()
- ret = yield from instance.convert(ctx, argument)
+ ret = await instance.convert(ctx, argument)
return ret
else:
method = getattr(converter, 'convert', None)
if method is not None and inspect.ismethod(method):
- ret = yield from method(ctx, argument)
+ ret = await method(ctx, argument)
return ret
elif isinstance(converter, converters.Converter):
- ret = yield from converter.convert(ctx, argument)
+ ret = await converter.convert(ctx, argument)
return ret
return converter(argument)
@@ -250,8 +246,7 @@ class Command:
converter = str
return converter
- @asyncio.coroutine
- def transform(self, ctx, param):
+ async def transform(self, ctx, param):
required = param.default is param.empty
converter = self._get_converter(param)
consume_rest_is_special = param.kind == param.KEYWORD_ONLY and not self.rest_is_raw
@@ -271,7 +266,7 @@ class Command:
argument = quoted_word(view)
try:
- return (yield from self.do_conversion(ctx, converter, argument))
+ return (await self.do_conversion(ctx, converter, argument))
except CommandError as e:
raise e
except Exception as e:
@@ -354,8 +349,7 @@ class Command:
def __str__(self):
return self.qualified_name
- @asyncio.coroutine
- def _parse_arguments(self, ctx):
+ async def _parse_arguments(self, ctx):
ctx.args = [ctx] if self.instance is None else [self.instance, ctx]
ctx.kwargs = {}
args = ctx.args
@@ -382,21 +376,21 @@ class Command:
for name, param in iterator:
if param.kind == param.POSITIONAL_OR_KEYWORD:
- transformed = yield from self.transform(ctx, param)
+ transformed = await self.transform(ctx, param)
args.append(transformed)
elif param.kind == param.KEYWORD_ONLY:
# kwarg only param denotes "consume rest" semantics
if self.rest_is_raw:
converter = self._get_converter(param)
argument = view.read_rest()
- kwargs[name] = yield from self.do_conversion(ctx, converter, argument)
+ kwargs[name] = await self.do_conversion(ctx, converter, argument)
else:
- kwargs[name] = yield from self.transform(ctx, param)
+ kwargs[name] = await self.transform(ctx, param)
break
elif param.kind == param.VAR_POSITIONAL:
while not view.eof:
try:
- transformed = yield from self.transform(ctx, param)
+ transformed = await self.transform(ctx, param)
args.append(transformed)
except RuntimeError:
break
@@ -405,24 +399,22 @@ class Command:
if not view.eof:
raise TooManyArguments('Too many arguments passed to ' + self.qualified_name)
- @asyncio.coroutine
- def _verify_checks(self, ctx):
+ async def _verify_checks(self, ctx):
if not self.enabled:
raise DisabledCommand('{0.name} command is disabled'.format(self))
- if not (yield from self.can_run(ctx)):
+ if not (await self.can_run(ctx)):
raise CheckFailure('The check functions for command {0.qualified_name} failed.'.format(self))
- @asyncio.coroutine
- def call_before_hooks(self, ctx):
+ async def call_before_hooks(self, ctx):
# now that we're done preparing we can call the pre-command hooks
# first, call the command local hook:
cog = self.instance
if self._before_invoke is not None:
if cog is None:
- yield from self._before_invoke(ctx)
+ await self._before_invoke(ctx)
else:
- yield from self._before_invoke(cog, ctx)
+ await self._before_invoke(cog, ctx)
# call the cog local hook if applicable:
try:
@@ -430,37 +422,35 @@ class Command:
except AttributeError:
pass
else:
- yield from hook(ctx)
+ await hook(ctx)
# call the bot global hook if necessary
hook = ctx.bot._before_invoke
if hook is not None:
- yield from hook(ctx)
+ await hook(ctx)
- @asyncio.coroutine
- def call_after_hooks(self, ctx):
+ async def call_after_hooks(self, ctx):
cog = self.instance
if self._after_invoke is not None:
if cog is None:
- yield from self._after_invoke(ctx)
+ await self._after_invoke(ctx)
else:
- yield from self._after_invoke(cog, ctx)
+ await self._after_invoke(cog, ctx)
try:
hook = getattr(cog, '_{0.__class__.__name__}__after_invoke'.format(cog))
except AttributeError:
pass
else:
- yield from hook(ctx)
+ await hook(ctx)
hook = ctx.bot._after_invoke
if hook is not None:
- yield from hook(ctx)
+ await hook(ctx)
- @asyncio.coroutine
- def prepare(self, ctx):
+ async def prepare(self, ctx):
ctx.command = self
- yield from self._verify_checks(ctx)
+ await self._verify_checks(ctx)
if self._buckets.valid:
bucket = self._buckets.get_bucket(ctx.message)
@@ -468,8 +458,8 @@ class Command:
if retry_after:
raise CommandOnCooldown(bucket, retry_after)
- yield from self._parse_arguments(ctx)
- yield from self.call_before_hooks(ctx)
+ await self._parse_arguments(ctx)
+ await self.call_before_hooks(ctx)
def is_on_cooldown(self, ctx):
"""Checks whether the command is currently on cooldown.
@@ -502,34 +492,32 @@ class Command:
bucket = self._buckets.get_bucket(ctx.message)
bucket.reset()
- @asyncio.coroutine
- def invoke(self, ctx):
- yield from self.prepare(ctx)
+ async def invoke(self, ctx):
+ await self.prepare(ctx)
# terminate the invoked_subcommand chain.
# since we're in a regular command (and not a group) then
# the invoked subcommand is None.
ctx.invoked_subcommand = None
injected = hooked_wrapped_callback(self, ctx, self.callback)
- yield from injected(*ctx.args, **ctx.kwargs)
+ await injected(*ctx.args, **ctx.kwargs)
- @asyncio.coroutine
- def reinvoke(self, ctx, *, call_hooks=False):
+ async def reinvoke(self, ctx, *, call_hooks=False):
ctx.command = self
- yield from self._parse_arguments(ctx)
+ await self._parse_arguments(ctx)
if call_hooks:
- yield from self.call_before_hooks(ctx)
+ await self.call_before_hooks(ctx)
ctx.invoked_subcommand = None
try:
- yield from self.callback(*ctx.args, **ctx.kwargs)
+ await self.callback(*ctx.args, **ctx.kwargs)
except:
ctx.command_failed = True
raise
finally:
if call_hooks:
- yield from self.call_after_hooks(ctx)
+ await self.call_after_hooks(ctx)
def error(self, coro):
"""A decorator that registers a coroutine as a local error handler.
@@ -667,8 +655,7 @@ class Command:
return ' '.join(result)
- @asyncio.coroutine
- def can_run(self, ctx):
+ async def can_run(self, ctx):
"""|coro|
Checks if the command can be executed by checking all the predicates
@@ -695,7 +682,7 @@ class Command:
ctx.command = self
try:
- if not (yield from ctx.bot.can_run(ctx)):
+ if not (await ctx.bot.can_run(ctx)):
raise CheckFailure('The global check functions for command {0.qualified_name} failed.'.format(self))
cog = self.instance
@@ -705,7 +692,7 @@ class Command:
except AttributeError:
pass
else:
- ret = yield from discord.utils.maybe_coroutine(local_check, ctx)
+ ret = await discord.utils.maybe_coroutine(local_check, ctx)
if not ret:
return False
@@ -714,7 +701,7 @@ class Command:
# since we have no checks, then we just return True.
return True
- return (yield from discord.utils.async_all(predicate(ctx) for predicate in predicates))
+ return (await discord.utils.async_all(predicate(ctx) for predicate in predicates))
finally:
ctx.command = original
@@ -903,11 +890,10 @@ class Group(GroupMixin, Command):
self.invoke_without_command = attrs.pop('invoke_without_command', False)
super().__init__(**attrs)
- @asyncio.coroutine
- def invoke(self, ctx):
+ async def invoke(self, ctx):
early_invoke = not self.invoke_without_command
if early_invoke:
- yield from self.prepare(ctx)
+ await self.prepare(ctx)
view = ctx.view
previous = view.index
@@ -920,26 +906,25 @@ class Group(GroupMixin, Command):
if early_invoke:
injected = hooked_wrapped_callback(self, ctx, self.callback)
- yield from injected(*ctx.args, **ctx.kwargs)
+ await injected(*ctx.args, **ctx.kwargs)
if trigger and ctx.invoked_subcommand:
ctx.invoked_with = trigger
- yield from ctx.invoked_subcommand.invoke(ctx)
+ await ctx.invoked_subcommand.invoke(ctx)
elif not early_invoke:
# undo the trigger parsing
view.index = previous
view.previous = previous
- yield from super().invoke(ctx)
+ await super().invoke(ctx)
- @asyncio.coroutine
- def reinvoke(self, ctx, *, call_hooks=False):
+ async def reinvoke(self, ctx, *, call_hooks=False):
early_invoke = not self.invoke_without_command
if early_invoke:
ctx.command = self
- yield from self._parse_arguments(ctx)
+ await self._parse_arguments(ctx)
if call_hooks:
- yield from self.call_before_hooks(ctx)
+ await self.call_before_hooks(ctx)
view = ctx.view
previous = view.index
@@ -952,22 +937,22 @@ class Group(GroupMixin, Command):
if early_invoke:
try:
- yield from self.callback(*ctx.args, **ctx.kwargs)
+ await self.callback(*ctx.args, **ctx.kwargs)
except:
ctx.command_failed = True
raise
finally:
if call_hooks:
- yield from self.call_after_hooks(ctx)
+ await self.call_after_hooks(ctx)
if trigger and ctx.invoked_subcommand:
ctx.invoked_with = trigger
- yield from ctx.invoked_subcommand.reinvoke(ctx, call_hooks=call_hooks)
+ await ctx.invoked_subcommand.reinvoke(ctx, call_hooks=call_hooks)
elif not early_invoke:
# undo the trigger parsing
view.index = previous
view.previous = previous
- yield from super().reinvoke(ctx, call_hooks=call_hooks)
+ await super().reinvoke(ctx, call_hooks=call_hooks)
# Decorators
@@ -1279,9 +1264,8 @@ def is_owner():
from :exc:`.CheckFailure`.
"""
- @asyncio.coroutine
- def predicate(ctx):
- if not (yield from ctx.bot.is_owner(ctx.author)):
+ async def predicate(ctx):
+ if not (await ctx.bot.is_owner(ctx.author)):
raise NotOwner('You do not own this bot.')
return True
diff --git a/discord/ext/commands/formatter.py b/discord/ext/commands/formatter.py
index 7b9528c4..0dead85f 100644
--- a/discord/ext/commands/formatter.py
+++ b/discord/ext/commands/formatter.py
@@ -199,8 +199,7 @@ class HelpFormatter:
return "Type {0}{1} command for more info on a command.\n" \
"You can also type {0}{1} category for more info on a category.".format(self.clean_prefix, command_name)
- @asyncio.coroutine
- def filter_command_list(self):
+ async def filter_command_list(self):
"""Returns a filtered list of commands based on the two attributes
provided, :attr:`show_check_failure` and :attr:`show_hidden`.
Also filters based on if :meth:`~.HelpFormatter.is_cog` is valid.
@@ -224,14 +223,13 @@ class HelpFormatter:
return True
- @asyncio.coroutine
- def predicate(tup):
+ async def predicate(tup):
if sane_no_suspension_point_predicate(tup) is False:
return False
cmd = tup[1]
try:
- return (yield from cmd.can_run(self.context))
+ return (await cmd.can_run(self.context))
except CommandError:
return False
@@ -242,7 +240,7 @@ class HelpFormatter:
# Gotta run every check and verify it
ret = []
for elem in iterator:
- valid = yield from predicate(elem)
+ valid = await predicate(elem)
if valid:
ret.append(elem)
@@ -258,8 +256,7 @@ class HelpFormatter:
shortened = self.shorten(entry)
self._paginator.add_line(shortened)
- @asyncio.coroutine
- def format_help_for(self, context, command_or_bot):
+ async def format_help_for(self, context, command_or_bot):
"""Formats the help page and handles the actual heavy lifting of how
the help command looks like. To change the behaviour, override the
:meth:`~.HelpFormatter.format` method.
@@ -278,10 +275,9 @@ class HelpFormatter:
"""
self.context = context
self.command = command_or_bot
- return (yield from self.format())
+ return (await self.format())
- @asyncio.coroutine
- def format(self):
+ async def format(self):
"""Handles the actual behaviour involved with formatting.
To change the behaviour, this method should be overridden.
@@ -323,7 +319,7 @@ class HelpFormatter:
# last place sorting position.
return cog + ':' if cog is not None else '\u200bNo Category:'
- filtered = yield from self.filter_command_list()
+ filtered = await self.filter_command_list()
if self.is_bot():
data = sorted(filtered, key=category)
for category, commands in itertools.groupby(data, key=category):
diff --git a/discord/gateway.py b/discord/gateway.py
index 4e100753..d463ff73 100644
--- a/discord/gateway.py
+++ b/discord/gateway.py
@@ -71,7 +71,7 @@ class KeepAliveHandler(threading.Thread):
if self._last_ack + self.heartbeat_timeout < time.monotonic():
log.warn("Shard ID %s has stopped responding to the gateway. Closing and restarting." % self.shard_id)
coro = self.ws.close(4000)
- f = compat.run_coroutine_threadsafe(coro, loop=self.ws.loop)
+ f = asyncio.run_coroutine_threadsafe(coro, loop=self.ws.loop)
try:
f.result()
@@ -84,7 +84,7 @@ class KeepAliveHandler(threading.Thread):
data = self.get_payload()
log.debug(self.msg, data['d'])
coro = self.ws.send_as_json(data)
- f = compat.run_coroutine_threadsafe(coro, loop=self.ws.loop)
+ f = asyncio.run_coroutine_threadsafe(coro, loop=self.ws.loop)
try:
# block until sending is complete
f.result()
@@ -190,14 +190,13 @@ class DiscordWebSocket(websockets.client.WebSocketClientProtocol):
self._buffer = bytearray()
@classmethod
- @asyncio.coroutine
- def from_client(cls, client, *, shard_id=None, session=None, sequence=None, resume=False):
+ async def from_client(cls, client, *, shard_id=None, session=None, sequence=None, resume=False):
"""Creates a main websocket for Discord from a :class:`Client`.
This is for internal use only.
"""
- gateway = yield from client.http.get_gateway()
- ws = yield from websockets.connect(gateway, loop=client.loop, klass=cls)
+ gateway = await client.http.get_gateway()
+ ws = await websockets.connect(gateway, loop=client.loop, klass=cls)
# dynamically add attributes needed
ws.token = client.http.token
@@ -215,19 +214,19 @@ class DiscordWebSocket(websockets.client.WebSocketClientProtocol):
log.info('Created websocket connected to %s', gateway)
# poll event for OP Hello
- yield from ws.poll_event()
+ await ws.poll_event()
if not resume:
- yield from ws.identify()
+ await ws.identify()
return ws
- yield from ws.resume()
+ await ws.resume()
try:
- yield from ws.ensure_open()
+ await ws.ensure_open()
except websockets.exceptions.ConnectionClosed:
# ws got closed so let's just do a regular IDENTIFY connect.
log.info('RESUME failed (the websocket decided to close) for Shard ID %s. Retrying.', shard_id)
- return (yield from cls.from_client(client, shard_id=shard_id))
+ return (await cls.from_client(client, shard_id=shard_id))
else:
return ws
@@ -251,13 +250,12 @@ class DiscordWebSocket(websockets.client.WebSocketClientProtocol):
A future to wait for.
"""
- future = compat.create_future(self.loop)
+ future = self.loop.create_future()
entry = EventListener(event=event, predicate=predicate, result=result, future=future)
self._dispatch_listeners.append(entry)
return future
- @asyncio.coroutine
- def identify(self):
+ async def identify(self):
"""Sends the IDENTIFY packet."""
payload = {
'op': self.IDENTIFY,
@@ -291,11 +289,10 @@ class DiscordWebSocket(websockets.client.WebSocketClientProtocol):
'afk': False
}
- yield from self.send_as_json(payload)
+ await self.send_as_json(payload)
log.info('Shard ID %s has sent the IDENTIFY payload.', self.shard_id)
- @asyncio.coroutine
- def resume(self):
+ async def resume(self):
"""Sends the RESUME packet."""
payload = {
'op': self.RESUME,
@@ -306,11 +303,10 @@ class DiscordWebSocket(websockets.client.WebSocketClientProtocol):
}
}
- yield from self.send_as_json(payload)
+ await self.send_as_json(payload)
log.info('Shard ID %s has sent the RESUME payload.', self.shard_id)
- @asyncio.coroutine
- def received_message(self, msg):
+ async def received_message(self, msg):
self._dispatch('socket_raw_receive', msg)
if isinstance(msg, bytes):
@@ -342,7 +338,7 @@ class DiscordWebSocket(websockets.client.WebSocketClientProtocol):
# so we terminate our connection and raise an
# internal exception signalling to reconnect.
log.info('Received RECONNECT opcode.')
- yield from self.close()
+ await self.close()
raise ResumeWebSocket(self.shard_id)
if op == self.HEARTBEAT_ACK:
@@ -351,27 +347,27 @@ class DiscordWebSocket(websockets.client.WebSocketClientProtocol):
if op == self.HEARTBEAT:
beat = self._keep_alive.get_payload()
- yield from self.send_as_json(beat)
+ await self.send_as_json(beat)
return
if op == self.HELLO:
interval = data['heartbeat_interval'] / 1000.0
self._keep_alive = KeepAliveHandler(ws=self, interval=interval, shard_id=self.shard_id)
# send a heartbeat immediately
- yield from self.send_as_json(self._keep_alive.get_payload())
+ await self.send_as_json(self._keep_alive.get_payload())
self._keep_alive.start()
return
if op == self.INVALIDATE_SESSION:
if data == True:
- yield from asyncio.sleep(5.0, loop=self.loop)
- yield from self.close()
+ await asyncio.sleep(5.0, loop=self.loop)
+ await self.close()
raise ResumeWebSocket(self.shard_id)
self.sequence = None
self.session_id = None
log.info('Shard ID %s session has been invalidated.' % self.shard_id)
- yield from self.identify()
+ await self.identify()
return
if op != self.DISPATCH:
@@ -435,8 +431,7 @@ class DiscordWebSocket(websockets.client.WebSocketClientProtocol):
def _can_handle_close(self, code):
return code not in (1000, 4004, 4010, 4011)
- @asyncio.coroutine
- def poll_event(self):
+ async def poll_event(self):
"""Polls for a DISPATCH event and handles the general gateway loop.
Raises
@@ -445,8 +440,8 @@ class DiscordWebSocket(websockets.client.WebSocketClientProtocol):
The websocket connection was terminated for unhandled reasons.
"""
try:
- msg = yield from self.recv()
- yield from self.received_message(msg)
+ msg = await self.recv()
+ await self.received_message(msg)
except websockets.exceptions.ConnectionClosed as e:
if self._can_handle_close(e.code):
log.info('Websocket closed with %s (%s), attempting a reconnect.', e.code, e.reason)
@@ -455,21 +450,18 @@ class DiscordWebSocket(websockets.client.WebSocketClientProtocol):
log.info('Websocket closed with %s (%s), cannot reconnect.', e.code, e.reason)
raise ConnectionClosed(e, shard_id=self.shard_id) from e
- @asyncio.coroutine
- def send(self, data):
+ async def send(self, data):
self._dispatch('socket_raw_send', data)
- yield from super().send(data)
+ await super().send(data)
- @asyncio.coroutine
- def send_as_json(self, data):
+ async def send_as_json(self, data):
try:
- yield from super().send(utils.to_json(data))
+ await super().send(utils.to_json(data))
except websockets.exceptions.ConnectionClosed as e:
if not self._can_handle_close(e.code):
raise ConnectionClosed(e, shard_id=self.shard_id) from e
- @asyncio.coroutine
- def change_presence(self, *, activity=None, status=None, afk=False, since=0.0):
+ async def change_presence(self, *, activity=None, status=None, afk=False, since=0.0):
if activity is not None:
if not isinstance(activity, _ActivityTag):
raise InvalidArgument('activity must be one of Game, Streaming, or Activity.')
@@ -490,18 +482,16 @@ class DiscordWebSocket(websockets.client.WebSocketClientProtocol):
sent = utils.to_json(payload)
log.debug('Sending "%s" to change status', sent)
- yield from self.send(sent)
+ await self.send(sent)
- @asyncio.coroutine
- def request_sync(self, guild_ids):
+ async def request_sync(self, guild_ids):
payload = {
'op': self.GUILD_SYNC,
'd': list(guild_ids)
}
- yield from self.send_as_json(payload)
+ await self.send_as_json(payload)
- @asyncio.coroutine
- def voice_state(self, guild_id, channel_id, self_mute=False, self_deaf=False):
+ async def voice_state(self, guild_id, channel_id, self_mute=False, self_deaf=False):
payload = {
'op': self.VOICE_STATE,
'd': {
@@ -513,14 +503,13 @@ class DiscordWebSocket(websockets.client.WebSocketClientProtocol):
}
log.debug('Updating our voice state to %s.', payload)
- yield from self.send_as_json(payload)
+ await self.send_as_json(payload)
- @asyncio.coroutine
- def close_connection(self, *args, **kwargs):
+ async def close_connection(self, *args, **kwargs):
if self._keep_alive:
self._keep_alive.stop()
- yield from super().close_connection(*args, **kwargs)
+ await super().close_connection(*args, **kwargs)
class DiscordVoiceWebSocket(websockets.client.WebSocketClientProtocol):
"""Implements the websocket protocol for handling voice connections.
@@ -565,13 +554,11 @@ class DiscordVoiceWebSocket(websockets.client.WebSocketClientProtocol):
self.max_size = None
self._keep_alive = None
- @asyncio.coroutine
- def send_as_json(self, data):
+ async def send_as_json(self, data):
log.debug('Sending voice websocket frame: %s.', data)
- yield from self.send(utils.to_json(data))
+ await self.send(utils.to_json(data))
- @asyncio.coroutine
- def resume(self):
+ async def resume(self):
state = self._connection
payload = {
'op': self.RESUME,
@@ -581,10 +568,9 @@ class DiscordVoiceWebSocket(websockets.client.WebSocketClientProtocol):
'session_id': state.session_id
}
}
- yield from self.send_as_json(payload)
+ await self.send_as_json(payload)
- @asyncio.coroutine
- def identify(self):
+ async def identify(self):
state = self._connection
payload = {
'op': self.IDENTIFY,
@@ -595,27 +581,25 @@ class DiscordVoiceWebSocket(websockets.client.WebSocketClientProtocol):
'token': state.token
}
}
- yield from self.send_as_json(payload)
+ await self.send_as_json(payload)
@classmethod
- @asyncio.coroutine
- def from_client(cls, client, *, resume=False):
+ async def from_client(cls, client, *, resume=False):
"""Creates a voice websocket for the :class:`VoiceClient`."""
gateway = 'wss://' + client.endpoint + '/?v=3'
- ws = yield from websockets.connect(gateway, loop=client.loop, klass=cls)
+ ws = await websockets.connect(gateway, loop=client.loop, klass=cls)
ws.gateway = gateway
ws._connection = client
ws._max_heartbeat_timeout = 60.0
if resume:
- yield from ws.resume()
+ await ws.resume()
else:
- yield from ws.identify()
+ await ws.identify()
return ws
- @asyncio.coroutine
- def select_protocol(self, ip, port):
+ async def select_protocol(self, ip, port):
payload = {
'op': self.SELECT_PROTOCOL,
'd': {
@@ -628,10 +612,9 @@ class DiscordVoiceWebSocket(websockets.client.WebSocketClientProtocol):
}
}
- yield from self.send_as_json(payload)
+ await self.send_as_json(payload)
- @asyncio.coroutine
- def speak(self, is_speaking=True):
+ async def speak(self, is_speaking=True):
payload = {
'op': self.SPEAKING,
'd': {
@@ -640,10 +623,9 @@ class DiscordVoiceWebSocket(websockets.client.WebSocketClientProtocol):
}
}
- yield from self.send_as_json(payload)
+ await self.send_as_json(payload)
- @asyncio.coroutine
- def received_message(self, msg):
+ async def received_message(self, msg):
log.debug('Voice websocket frame received: %s', msg)
op = msg['op']
data = msg.get('d')
@@ -652,17 +634,16 @@ class DiscordVoiceWebSocket(websockets.client.WebSocketClientProtocol):
interval = data['heartbeat_interval'] / 1000.0
self._keep_alive = VoiceKeepAliveHandler(ws=self, interval=interval)
self._keep_alive.start()
- yield from self.initial_connection(data)
+ await self.initial_connection(data)
elif op == self.HEARTBEAT_ACK:
self._keep_alive.ack()
elif op == self.INVALIDATE_SESSION:
log.info('Voice RESUME failed.')
- yield from self.identify()
+ await self.identify()
elif op == self.SESSION_DESCRIPTION:
- yield from self.load_secret_key(data)
+ await self.load_secret_key(data)
- @asyncio.coroutine
- def initial_connection(self, data):
+ async def initial_connection(self, data):
state = self._connection
state.ssrc = data['ssrc']
state.voice_port = data['port']
@@ -670,7 +651,7 @@ class DiscordVoiceWebSocket(websockets.client.WebSocketClientProtocol):
packet = bytearray(70)
struct.pack_into('>I', packet, 0, state.ssrc)
state.socket.sendto(packet, (state.endpoint_ip, state.voice_port))
- recv = yield from self.loop.sock_recv(state.socket, 70)
+ recv = await self.loop.sock_recv(state.socket, 70)
log.debug('received packet in initial_connection: %s', recv)
# the ip is ascii starting at the 4th byte and ending at the first null
@@ -683,28 +664,25 @@ class DiscordVoiceWebSocket(websockets.client.WebSocketClientProtocol):
state.port = struct.unpack_from('<H', recv, len(recv) - 2)[0]
log.debug('detected ip: %s port: %s', state.ip, state.port)
- yield from self.select_protocol(state.ip, state.port)
+ await self.select_protocol(state.ip, state.port)
log.info('selected the voice protocol for use')
- @asyncio.coroutine
- def load_secret_key(self, data):
+ async def load_secret_key(self, data):
log.info('received secret key for voice connection')
self._connection.secret_key = data.get('secret_key')
- yield from self.speak()
+ await self.speak()
- @asyncio.coroutine
- def poll_event(self):
+ async def poll_event(self):
try:
- msg = yield from asyncio.wait_for(self.recv(), timeout=30.0, loop=self.loop)
- yield from self.received_message(json.loads(msg))
+ msg = await asyncio.wait_for(self.recv(), timeout=30.0, loop=self.loop)
+ await self.received_message(json.loads(msg))
except websockets.exceptions.ConnectionClosed as e:
raise ConnectionClosed(e, shard_id=None) from e
- @asyncio.coroutine
- def close_connection(self, *args, **kwargs):
+ async def close_connection(self, *args, **kwargs):
if self._keep_alive:
self._keep_alive.stop()
- yield from super().close_connection(*args, **kwargs)
+ await super().close_connection(*args, **kwargs)
diff --git a/discord/guild.py b/discord/guild.py
index d49f7780..4c2fc1fa 100644
--- a/discord/guild.py
+++ b/discord/guild.py
@@ -543,8 +543,7 @@ class Guild(Hashable):
return self._state.http.create_channel(self.id, name, channel_type.value, parent_id=parent_id,
permission_overwrites=perms, reason=reason)
- @asyncio.coroutine
- def create_text_channel(self, name, *, overwrites=None, category=None, reason=None):
+ async def create_text_channel(self, name, *, overwrites=None, category=None, reason=None):
"""|coro|
Creates a :class:`TextChannel` for the guild.
@@ -606,28 +605,26 @@ class Guild(Hashable):
:class:`TextChannel`
The channel that was just created.
"""
- data = yield from self._create_channel(name, overwrites, ChannelType.text, category, reason=reason)
+ data = await self._create_channel(name, overwrites, ChannelType.text, category, reason=reason)
channel = TextChannel(state=self._state, guild=self, data=data)
# temporarily add to the cache
self._channels[channel.id] = channel
return channel
- @asyncio.coroutine
- def create_voice_channel(self, name, *, overwrites=None, category=None, reason=None):
+ async def create_voice_channel(self, name, *, overwrites=None, category=None, reason=None):
"""|coro|
Same as :meth:`create_text_channel` except makes a :class:`VoiceChannel` instead.
"""
- data = yield from self._create_channel(name, overwrites, ChannelType.voice, category, reason=reason)
+ data = await self._create_channel(name, overwrites, ChannelType.voice, category, reason=reason)
channel = VoiceChannel(state=self._state, guild=self, data=data)
# temporarily add to the cache
self._channels[channel.id] = channel
return channel
- @asyncio.coroutine
- def create_category(self, name, *, overwrites=None, reason=None):
+ async def create_category(self, name, *, overwrites=None, reason=None):
"""|coro|
Same as :meth:`create_text_channel` except makes a :class:`CategoryChannel` instead.
@@ -637,7 +634,7 @@ class Guild(Hashable):
The ``category`` parameter is not supported in this function since categories
cannot have categories.
"""
- data = yield from self._create_channel(name, overwrites, ChannelType.category, reason=reason)
+ data = await self._create_channel(name, overwrites, ChannelType.category, reason=reason)
channel = CategoryChannel(state=self._state, guild=self, data=data)
# temporarily add to the cache
@@ -646,8 +643,7 @@ class Guild(Hashable):
create_category_channel = create_category
- @asyncio.coroutine
- def leave(self):
+ async def leave(self):
"""|coro|
Leaves the guild.
@@ -662,10 +658,9 @@ class Guild(Hashable):
HTTPException
Leaving the guild failed.
"""
- yield from self._state.http.leave_guild(self.id)
+ await self._state.http.leave_guild(self.id)
- @asyncio.coroutine
- def delete(self):
+ async def delete(self):
"""|coro|
Deletes the guild. You must be the guild owner to delete the
@@ -679,10 +674,9 @@ class Guild(Hashable):
You do not have permissions to delete the guild.
"""
- yield from self._state.http.delete_guild(self.id)
+ await self._state.http.delete_guild(self.id)
- @asyncio.coroutine
- def edit(self, *, reason=None, **fields):
+ async def edit(self, *, reason=None, **fields):
"""|coro|
Edits the guild.
@@ -748,7 +742,7 @@ class Guild(Hashable):
except KeyError:
pass
else:
- yield from http.change_vanity_code(self.id, vanity_code, reason=reason)
+ await http.change_vanity_code(self.id, vanity_code, reason=reason)
try:
splash_bytes = fields['splash']
@@ -798,7 +792,7 @@ class Guild(Hashable):
fields['verification_level'] = level.value
- yield from http.edit_guild(self.id, reason=reason, **fields)
+ await http.edit_guild(self.id, reason=reason, **fields)
@asyncio.coroutine
def get_ban(self, user):
@@ -836,8 +830,7 @@ class Guild(Hashable):
reason=data['reason']
)
- @asyncio.coroutine
- def bans(self):
+ async def bans(self):
"""|coro|
Retrieves all the users that are banned from the guild.
@@ -863,13 +856,12 @@ class Guild(Hashable):
A list of BanEntry objects.
"""
- data = yield from self._state.http.get_bans(self.id)
+ data = await self._state.http.get_bans(self.id)
return [BanEntry(user=User(state=self._state, data=e['user']),
reason=e['reason'])
for e in data]
- @asyncio.coroutine
- def prune_members(self, *, days, reason=None):
+ async def prune_members(self, *, days, reason=None):
"""|coro|
Prunes the guild from its inactive members.
@@ -908,11 +900,10 @@ class Guild(Hashable):
if not isinstance(days, int):
raise InvalidArgument('Expected int for ``days``, received {0.__class__.__name__} instead.'.format(days))
- data = yield from self._state.http.prune_members(self.id, days, reason=reason)
+ data = await self._state.http.prune_members(self.id, days, reason=reason)
return data['pruned']
- @asyncio.coroutine
- def webhooks(self):
+ async def webhooks(self):
"""|coro|
Gets the list of webhooks from this guild.
@@ -930,11 +921,10 @@ class Guild(Hashable):
The webhooks for this guild.
"""
- data = yield from self._state.http.guild_webhooks(self.id)
+ data = await self._state.http.guild_webhooks(self.id)
return [Webhook.from_state(d, state=self._state) for d in data]
- @asyncio.coroutine
- def estimate_pruned_members(self, *, days):
+ async def estimate_pruned_members(self, *, days):
"""|coro|
Similar to :meth:`prune_members` except instead of actually
@@ -964,11 +954,10 @@ class Guild(Hashable):
if not isinstance(days, int):
raise InvalidArgument('Expected int for ``days``, received {0.__class__.__name__} instead.'.format(days))
- data = yield from self._state.http.estimate_pruned_members(self.id, days)
+ data = await self._state.http.estimate_pruned_members(self.id, days)
return data['pruned']
- @asyncio.coroutine
- def invites(self):
+ async def invites(self):
"""|coro|
Returns a list of all active instant invites from the guild.
@@ -989,7 +978,7 @@ class Guild(Hashable):
The list of invites that are currently active.
"""
- data = yield from self._state.http.invites_from(self.id)
+ data = await self._state.http.invites_from(self.id)
result = []
for invite in data:
channel = self.get_channel(int(invite['channel']['id']))
@@ -999,8 +988,7 @@ class Guild(Hashable):
return result
- @asyncio.coroutine
- def create_custom_emoji(self, *, name, image, reason=None):
+ async def create_custom_emoji(self, *, name, image, reason=None):
"""|coro|
Creates a custom :class:`Emoji` for the guild.
@@ -1036,11 +1024,10 @@ class Guild(Hashable):
"""
img = utils._bytes_to_base64_data(image)
- data = yield from self._state.http.create_custom_emoji(self.id, name, img, reason=reason)
+ data = await self._state.http.create_custom_emoji(self.id, name, img, reason=reason)
return self._state.store_emoji(self, data)
- @asyncio.coroutine
- def create_role(self, *, reason=None, **fields):
+ async def create_role(self, *, reason=None, **fields):
"""|coro|
Creates a :class:`Role` for the guild.
@@ -1102,14 +1089,13 @@ class Guild(Hashable):
if key not in valid_keys:
raise InvalidArgument('%r is not a valid field.' % key)
- data = yield from self._state.http.create_role(self.id, reason=reason, **fields)
+ data = await self._state.http.create_role(self.id, reason=reason, **fields)
role = Role(guild=self, data=data, state=self._state)
# TODO: add to cache
return role
- @asyncio.coroutine
- def kick(self, user, *, reason=None):
+ async def kick(self, user, *, reason=None):
"""|coro|
Kicks a user from the guild.
@@ -1133,10 +1119,9 @@ class Guild(Hashable):
HTTPException
Kicking failed.
"""
- yield from self._state.http.kick(user.id, self.id, reason=reason)
+ await self._state.http.kick(user.id, self.id, reason=reason)
- @asyncio.coroutine
- def ban(self, user, *, reason=None, delete_message_days=1):
+ async def ban(self, user, *, reason=None, delete_message_days=1):
"""|coro|
Bans a user from the guild.
@@ -1163,10 +1148,9 @@ class Guild(Hashable):
HTTPException
Banning failed.
"""
- yield from self._state.http.ban(user.id, self.id, delete_message_days, reason=reason)
+ await self._state.http.ban(user.id, self.id, delete_message_days, reason=reason)
- @asyncio.coroutine
- def unban(self, user, *, reason=None):
+ async def unban(self, user, *, reason=None):
"""|coro|
Unbans a user from the guild.
@@ -1190,10 +1174,9 @@ class Guild(Hashable):
HTTPException
Unbanning failed.
"""
- yield from self._state.http.unban(user.id, self.id, reason=reason)
+ await self._state.http.unban(user.id, self.id, reason=reason)
- @asyncio.coroutine
- def vanity_invite(self):
+ async def vanity_invite(self):
"""|coro|
Returns the guild's special vanity invite.
@@ -1218,11 +1201,11 @@ class Guild(Hashable):
"""
# we start with { code: abc }
- payload = yield from self._state.http.get_vanity_code(self.id)
+ payload = await self._state.http.get_vanity_code(self.id)
# get the vanity URL channel since default channels aren't
# reliable or a thing anymore
- data = yield from self._state.http.get_invite(payload['code'])
+ data = await self._state.http.get_invite(payload['code'])
payload['guild'] = self
payload['channel'] = self.get_channel(int(data['channel']['id']))
diff --git a/discord/http.py b/discord/http.py
index 39c24fdb..de8b6b0d 100644
--- a/discord/http.py
+++ b/discord/http.py
@@ -38,9 +38,8 @@ log = logging.getLogger(__name__)
from .errors import HTTPException, Forbidden, NotFound, LoginFailure, GatewayNotFound
from . import __version__, utils
-def json_or_text(response):
- text = yield from response.text(encoding='utf-8')
+async def json_or_text(response):
+ text = await response.text(encoding='utf-8')
if response.headers['content-type'] == 'application/json':
return json.loads(text)
return text
@@ -106,8 +105,7 @@ class HTTPClient:
if self._session.closed:
self._session = aiohttp.ClientSession(connector=self.connector, loop=self.loop)
- @asyncio.coroutine
- def request(self, route, *, header_bypass_delay=None, **kwargs):
+ async def request(self, route, *, header_bypass_delay=None, **kwargs):
bucket = route.bucket
method = route.method
url = route.url
@@ -148,16 +146,16 @@ class HTTPClient:
if not self._global_over.is_set():
# wait until the global lock is complete
- yield from self._global_over.wait()
+ await self._global_over.wait()
- yield from lock
+ await lock
with MaybeUnlock(lock) as maybe_lock:
for tries in range(5):
- r = yield from self._session.request(method, url, **kwargs)
- log.debug('%s %s with %s has returned %s', method, url, kwargs.get('data'), r.status)
- try:
+ async with self._session.request(method, url, **kwargs) as r:
+ log.debug('%s %s with %s has returned %s', method, url, kwargs.get('data'), r.status)
+
# even errors have text involved in them so this is safe to call
- data = yield from json_or_text(r)
+ data = await json_or_text(r)
# check if we have rate limit header information
remaining = r.headers.get('X-Ratelimit-Remaining')
@@ -191,7 +189,7 @@ class HTTPClient:
log.info('Global rate limit has been hit. Retrying in %.2f seconds.', retry_after)
self._global_over.clear()
- yield from asyncio.sleep(retry_after, loop=self.loop)
+ await asyncio.sleep(retry_after, loop=self.loop)
log.debug('Done sleeping for the rate limit. Retrying...')
# release the global lock now that the
@@ -204,7 +202,7 @@ class HTTPClient:
# we've received a 500 or 502, unconditional retry
if r.status in {500, 502}:
- yield from asyncio.sleep(1 + tries * 2, loop=self.loop)
+ await asyncio.sleep(1 + tries * 2, loop=self.loop)
continue
# the usual error cases
@@ -214,32 +212,25 @@ class HTTPClient:
raise NotFound(r, data)
else:
raise HTTPException(r, data)
- finally:
- # clean-up just in case
- yield from r.release()
+
# We've run out of retries, raise.
raise HTTPException(r, data)
- @asyncio.coroutine
- def get_attachment(self, url):
- resp = yield from self._session.get(url)
- try:
+ async def get_attachment(self, url):
+ async with self._session.get(url) as resp:
if resp.status == 200:
- return (yield from resp.read())
+ return (await resp.read())
elif resp.status == 404:
raise NotFound(resp, 'attachment not found')
elif resp.status == 403:
raise Forbidden(resp, 'cannot retrieve attachment')
else:
raise HTTPException(resp, 'failed to get attachment')
- finally:
- yield from resp.release()
# state management
- @asyncio.coroutine
- def close(self):
- yield from self._session.close()
+ async def close(self):
+ await self._session.close()
def _token(self, token, *, bot=True):
self.token = token
@@ -248,13 +239,12 @@ class HTTPClient:
# login management
- @asyncio.coroutine
- def static_login(self, token, *, bot):
+ async def static_login(self, token, *, bot):
old_token, old_bot = self.token, self.bot_token
self._token(token, bot=bot)
try:
- data = yield from self.request(Route('GET', '/users/@me'))
+ data = await self.request(Route('GET', '/users/@me'))
except HTTPException as e:
self._token(old_token, bot=old_bot)
if e.response.status == 401:
@@ -349,11 +339,10 @@ class HTTPClient:
return self.request(r, data=form)
- @asyncio.coroutine
- def ack_message(self, channel_id, message_id):
+ async def ack_message(self, channel_id, message_id):
r = Route('POST', '/channels/{channel_id}/messages/{message_id}/ack', channel_id=channel_id,
message_id=message_id)
- data = yield from self.request(r, json={'token': self._ack_token})
+ data = await self.request(r, json={'token': self._ack_token})
self._ack_token = data['token']
def ack_guild(self, guild_id):
@@ -751,10 +740,9 @@ class HTTPClient:
def application_info(self):
return self.request(Route('GET', '/oauth2/applications/@me'))
- @asyncio.coroutine
- def get_gateway(self, *, encoding='json', v=6, zlib=True):
+ async def get_gateway(self, *, encoding='json', v=6, zlib=True):
try:
- data = yield from self.request(Route('GET', '/gateway'))
+ data = await self.request(Route('GET', '/gateway'))
except HTTPException as e:
raise GatewayNotFound() from e
if zlib:
@@ -763,10 +751,9 @@ class HTTPClient:
value = '{0}?encoding={1}&v={2}'
return value.format(data['url'], encoding, v)
- @asyncio.coroutine
- def get_bot_gateway(self, *, encoding='json', v=6, zlib=True):
+ async def get_bot_gateway(self, *, encoding='json', v=6, zlib=True):
try:
- data = yield from self.request(Route('GET', '/gateway/bot'))
+ data = await self.request(Route('GET', '/gateway/bot'))
except HTTPException as e:
raise GatewayNotFound() from e
diff --git a/discord/invite.py b/discord/invite.py
index 4acbf279..1cdabfdf 100644
--- a/discord/invite.py
+++ b/discord/invite.py
@@ -134,8 +134,7 @@ class Invite(Hashable):
"""A property that retrieves the invite URL."""
return 'http://discord.gg/' + self.code
- @asyncio.coroutine
- def delete(self, *, reason=None):
+ async def delete(self, *, reason=None):
"""|coro|
Revokes the instant invite.
@@ -157,4 +156,4 @@ class Invite(Hashable):
Revoking the invite failed.
"""
- yield from self._state.http.delete_invite(self.code, reason=reason)
+ await self._state.http.delete_invite(self.code, reason=reason)
diff --git a/discord/iterators.py b/discord/iterators.py
index e6f5234b..4e57faa7 100644
--- a/discord/iterators.py
+++ b/discord/iterators.py
@@ -33,8 +33,6 @@ from .utils import time_snowflake, maybe_coroutine
from .object import Object
from .audit_logs import AuditLogEntry
-PY35 = sys.version_info >= (3, 5)
-
class _AsyncIterator:
__slots__ = ()
@@ -52,15 +50,14 @@ class _AsyncIterator:
return self.find(predicate)
- @asyncio.coroutine
- def find(self, predicate):
+ async def find(self, predicate):
while True:
try:
- elem = yield from self.next()
+ elem = await self.next()
except NoMoreItems:
return None
- ret = yield from maybe_coroutine(predicate, elem)
+ ret = await maybe_coroutine(predicate, elem)
if ret:
return elem
@@ -70,30 +67,26 @@ class _AsyncIterator:
def filter(self, predicate):
return _FilteredAsyncIterator(self, predicate)
- @asyncio.coroutine
- def flatten(self):
+ async def flatten(self):
ret = []
while True:
try:
- item = yield from self.next()
+ item = await self.next()
except NoMoreItems:
return ret
else:
ret.append(item)
- if PY35:
- @asyncio.coroutine
- def __aiter__(self):
- return self
+ async def __aiter__(self):
+ return self
- @asyncio.coroutine
- def __anext__(self):
- try:
- msg = yield from self.next()
- except NoMoreItems:
- raise StopAsyncIteration()
- else:
- return msg
+ async def __anext__(self):
+ try:
+ msg = await self.next()
+ except NoMoreItems:
+ raise StopAsyncIteration()
+ else:
+ return msg
def _identity(x):
return x
@@ -103,11 +96,10 @@ class _MappedAsyncIterator(_AsyncIterator):
self.iterator = iterator
self.func = func
- @asyncio.coroutine
- def next(self):
+ async def next(self):
# this raises NoMoreItems and will propagate appropriately
- item = yield from self.iterator.next()
- return (yield from maybe_coroutine(self.func, item))
+ item = await self.iterator.next()
+ return (await maybe_coroutine(self.func, item))
class _FilteredAsyncIterator(_AsyncIterator):
def __init__(self, iterator, predicate):
@@ -118,14 +110,13 @@ class _FilteredAsyncIterator(_AsyncIterator):
self.predicate = predicate
- @asyncio.coroutine
- def next(self):
+ async def next(self):
getter = self.iterator.next
pred = self.predicate
while True:
# propagate NoMoreItems similar to _MappedAsyncIterator
- item = yield from getter()
- ret = yield from maybe_coroutine(pred, item)
+ item = await getter()
+ ret = await maybe_coroutine(pred, item)
if ret:
return item
@@ -142,18 +133,16 @@ class ReactionIterator(_AsyncIterator):
self.channel_id = message.channel.id
self.users = asyncio.Queue(loop=state.loop)
- @asyncio.coroutine
- def next(self):
+ async def next(self):
if self.users.empty():
- yield from self.fill_users()
+ await self.fill_users()
try:
return self.users.get_nowait()
except asyncio.QueueEmpty:
raise NoMoreItems()
- @asyncio.coroutine
- def fill_users(self):
+ async def fill_users(self):
# this is a hack because >circular imports<
from .user import User
@@ -161,7 +150,7 @@ class ReactionIterator(_AsyncIterator):
retrieve = self.limit if self.limit <= 100 else 100
after = self.after.id if self.after else None
- data = yield from self.getter(self.message.id, self.channel_id, self.emoji, retrieve, after=after)
+ data = await self.getter(self.message.id, self.channel_id, self.emoji, retrieve, after=after)
if data:
self.limit -= retrieve
@@ -169,15 +158,15 @@ class ReactionIterator(_AsyncIterator):
if self.guild is None:
for element in reversed(data):
- yield from self.users.put(User(state=self.state, data=element))
+ await self.users.put(User(state=self.state, data=element))
else:
for element in reversed(data):
member_id = int(element['id'])
member = self.guild.get_member(member_id)
if member is not None:
- yield from self.users.put(member)
+ await self.users.put(member)
else:
- yield from self.users.put(User(state=self.state, data=element))
+ await self.users.put(User(state=self.state, data=element))
class HistoryIterator(_AsyncIterator):
"""Iterator for receiving a channel's message history.
@@ -270,10 +259,9 @@ class HistoryIterator(_AsyncIterator):
else:
self._retrieve_messages = self._retrieve_messages_before_strategy
- @asyncio.coroutine
- def next(self):
+ async def next(self):
if self.messages.empty():
- yield from self.fill_messages()
+ await self.fill_messages()
try:
return self.messages.get_nowait()
@@ -292,15 +280,14 @@ class HistoryIterator(_AsyncIterator):
self.retrieve = r
return r > 0
- @asyncio.coroutine
- def flatten(self):
+ async def flatten(self):
# this is similar to fill_messages except it uses a list instead
# of a queue to place the messages in.
result = []
- channel = yield from self.messageable._get_channel()
+ channel = await self.messageable._get_channel()
self.channel = channel
while self._get_retrieve():
- data = yield from self._retrieve_messages(self.retrieve)
+ data = await self._retrieve_messages(self.retrieve)
if len(data) < 100:
self.limit = 0 # terminate the infinite loop
@@ -313,15 +300,14 @@ class HistoryIterator(_AsyncIterator):
result.append(self.state.create_message(channel=channel, data=element))
return result
- @asyncio.coroutine
- def fill_messages(self):
+ async def fill_messages(self):
if not hasattr(self, 'channel'):
# do the required set up
- channel = yield from self.messageable._get_channel()
+ channel = await self.messageable._get_channel()
self.channel = channel
if self._get_retrieve():
- data = yield from self._retrieve_messages(self.retrieve)
+ data = await self._retrieve_messages(self.retrieve)
if self.limit is None and len(data) < 100:
self.limit = 0 # terminate the infinite loop
@@ -332,41 +318,37 @@ class HistoryIterator(_AsyncIterator):
channel = self.channel
for element in data:
- yield from self.messages.put(self.state.create_message(channel=channel, data=element))
+ await self.messages.put(self.state.create_message(channel=channel, data=element))
- @asyncio.coroutine
- def _retrieve_messages(self, retrieve):
+ async def _retrieve_messages(self, retrieve):
"""Retrieve messages and update next parameters."""
pass
- @asyncio.coroutine
- def _retrieve_messages_before_strategy(self, retrieve):
+ async def _retrieve_messages_before_strategy(self, retrieve):
"""Retrieve messages using before parameter."""
before = self.before.id if self.before else None
- data = yield from self.logs_from(self.channel.id, retrieve, before=before)
+ data = await self.logs_from(self.channel.id, retrieve, before=before)
if len(data):
if self.limit is not None:
self.limit -= retrieve
self.before = Object(id=int(data[-1]['id']))
return data
- @asyncio.coroutine
- def _retrieve_messages_after_strategy(self, retrieve):
+ async def _retrieve_messages_after_strategy(self, retrieve):
"""Retrieve messages using after parameter."""
after = self.after.id if self.after else None
- data = yield from self.logs_from(self.channel.id, retrieve, after=after)
+ data = await self.logs_from(self.channel.id, retrieve, after=after)
if len(data):
if self.limit is not None:
self.limit -= retrieve
self.after = Object(id=int(data[0]['id']))
return data
- @asyncio.coroutine
- def _retrieve_messages_around_strategy(self, retrieve):
+ async def _retrieve_messages_around_strategy(self, retrieve):
"""Retrieve messages using around parameter."""
if self.around:
around = self.around.id if self.around else None
- data = yield from self.logs_from(self.channel.id, retrieve, around=around)
+ data = await self.logs_from(self.channel.id, retrieve, around=around)
self.around = None
return data
return []
@@ -411,10 +393,9 @@ class AuditLogIterator(_AsyncIterator):
else:
self._strategy = self._before_strategy
- @asyncio.coroutine
- def _before_strategy(self, retrieve):
+ async def _before_strategy(self, retrieve):
before = self.before.id if self.before else None
- data = yield from self.request(self.guild.id, limit=retrieve, user_id=self.user_id,
+ data = await self.request(self.guild.id, limit=retrieve, user_id=self.user_id,
action_type=self.action_type, before=before)
entries = data.get('audit_log_entries', [])
@@ -424,10 +405,9 @@ class AuditLogIterator(_AsyncIterator):
self.before = Object(id=int(entries[-1]['id']))
return data.get('users', []), entries
- @asyncio.coroutine
- def _after_strategy(self, retrieve):
+ async def _after_strategy(self, retrieve):
after = self.after.id if self.after else None
- data = yield from self.request(self.guild.id, limit=retrieve, user_id=self.user_id,
+ data = await self.request(self.guild.id, limit=retrieve, user_id=self.user_id,
action_type=self.action_type, after=after)
entries = data.get('audit_log_entries', [])
if len(data) and entries:
@@ -436,10 +416,9 @@ class AuditLogIterator(_AsyncIterator):
self.after = Object(id=int(entries[0]['id']))
return data.get('users', []), entries
- @asyncio.coroutine
- def next(self):
+ async def next(self):
if self.entries.empty():
- yield from self._fill()
+ await self._fill()
try:
return self.entries.get_nowait()
@@ -458,12 +437,11 @@ class AuditLogIterator(_AsyncIterator):
self.retrieve = r
return r > 0
- @asyncio.coroutine
- def _fill(self):
+ async def _fill(self):
from .user import User
if self._get_retrieve():
- users, data = yield from self._strategy(self.retrieve)
+ users, data = await self._strategy(self.retrieve)
if self.limit is None and len(data) < 100:
self.limit = 0 # terminate the infinite loop
@@ -481,4 +459,4 @@ class AuditLogIterator(_AsyncIterator):
if element['action_type'] is None:
continue
- yield from self.entries.put(AuditLogEntry(data=element, users=self._users, guild=self.guild))
+ await self.entries.put(AuditLogEntry(data=element, users=self._users, guild=self.guild))
diff --git a/discord/member.py b/discord/member.py
index 11bbfcf4..792313d3 100644
--- a/discord/member.py
+++ b/discord/member.py
@@ -183,9 +183,8 @@ class Member(discord.abc.Messageable, _BaseUser):
def __hash__(self):
return hash(self._user.id)
- @asyncio.coroutine
- def _get_channel(self):
- ch = yield from self.create_dm()
+ async def _get_channel(self):
+ ch = await self.create_dm()
return ch
def _update_roles(self, data):
@@ -341,32 +340,28 @@ class Member(discord.abc.Messageable, _BaseUser):
"""Optional[:class:`VoiceState`]: Returns the member's current voice state."""
return self.guild._voice_state_for(self._user.id)
- @asyncio.coroutine
- def ban(self, **kwargs):
+ async def ban(self, **kwargs):
"""|coro|
Bans this member. Equivalent to :meth:`Guild.ban`
"""
- yield from self.guild.ban(self, **kwargs)
+ await self.guild.ban(self, **kwargs)
- @asyncio.coroutine
- def unban(self, *, reason=None):
+ async def unban(self, *, reason=None):
"""|coro|
Unbans this member. Equivalent to :meth:`Guild.unban`
"""
- yield from self.guild.unban(self, reason=reason)
+ await self.guild.unban(self, reason=reason)
- @asyncio.coroutine
- def kick(self, *, reason=None):
+ async def kick(self, *, reason=None):
"""|coro|
Kicks this member. Equivalent to :meth:`Guild.kick`
"""
- yield from self.guild.kick(self, reason=reason)
+ await self.guild.kick(self, reason=reason)
- @asyncio.coroutine
- def edit(self, *, reason=None, **fields):
+ async def edit(self, *, reason=None, **fields):
"""|coro|
Edits the member's data.
@@ -423,7 +418,7 @@ class Member(discord.abc.Messageable, _BaseUser):
else:
nick = nick if nick else ''
if self._state.self_id == self.id:
- yield from http.change_my_nickname(guild_id, nick, reason=reason)
+ await http.change_my_nickname(guild_id, nick, reason=reason)
else:
payload['nick'] = nick
@@ -449,12 +444,11 @@ class Member(discord.abc.Messageable, _BaseUser):
else:
payload['roles'] = tuple(r.id for r in roles)
- yield from http.edit_member(guild_id, self.id, reason=reason, **payload)
+ await http.edit_member(guild_id, self.id, reason=reason, **payload)
# TODO: wait for WS event for modify-in-place behaviour
- @asyncio.coroutine
- def move_to(self, channel, *, reason=None):
+ async def move_to(self, channel, *, reason=None):
"""|coro|
Moves a member to a new voice channel (they must be connected first).
@@ -471,10 +465,9 @@ class Member(discord.abc.Messageable, _BaseUser):
reason: Optional[str]
The reason for doing this action. Shows up on the audit log.
"""
- yield from self.edit(voice_channel=channel, reason=reason)
+ await self.edit(voice_channel=channel, reason=reason)
- @asyncio.coroutine
- def add_roles(self, *roles, reason=None, atomic=True):
+ async def add_roles(self, *roles, reason=None, atomic=True):
"""|coro|
Gives the member a number of :class:`Role`\s.
@@ -504,16 +497,15 @@ class Member(discord.abc.Messageable, _BaseUser):
if not atomic:
new_roles = utils._unique(Object(id=r.id) for s in (self.roles[1:], roles) for r in s)
- yield from self.edit(roles=new_roles, reason=reason)
+ await self.edit(roles=new_roles, reason=reason)
else:
req = self._state.http.add_role
guild_id = self.guild.id
user_id = self.id
for role in roles:
- yield from req(guild_id, user_id, role.id, reason=reason)
+ await req(guild_id, user_id, role.id, reason=reason)
- @asyncio.coroutine
- def remove_roles(self, *roles, reason=None, atomic=True):
+ async def remove_roles(self, *roles, reason=None, atomic=True):
"""|coro|
Removes :class:`Role`\s from this member.
@@ -549,10 +541,10 @@ class Member(discord.abc.Messageable, _BaseUser):
except ValueError:
pass
- yield from self.edit(roles=new_roles, reason=reason)
+ await self.edit(roles=new_roles, reason=reason)
else:
req = self._state.http.remove_role
guild_id = self.guild.id
user_id = self.id
for role in roles:
- yield from req(guild_id, user_id, role.id, reason=reason)
+ await req(guild_id, user_id, role.id, reason=reason)
diff --git a/discord/message.py b/discord/message.py
index e9ebf5f2..5e78be48 100644
--- a/discord/message.py
+++ b/discord/message.py
@@ -71,8 +71,7 @@ class Attachment:
self.proxy_url = data.get('proxy_url')
self._http = state.http
- @asyncio.coroutine
- def save(self, fp, *, seek_begin=True):
+ async def save(self, fp, *, seek_begin=True):
"""|coro|
Saves this attachment into a file-like object.
@@ -100,7 +99,7 @@ class Attachment:
The number of bytes written.
"""
- data = yield from self._http.get_attachment(self.url)
+ data = await self._http.get_attachment(self.url)
if isinstance(fp, str):
with open(fp, 'wb') as f:
return f.write(data)
@@ -527,8 +526,7 @@ class Message:
else:
return '{0.author.name} started a call \N{EM DASH} Join the call.'.format(self)
- @asyncio.coroutine
- def delete(self):
+ async def delete(self):
"""|coro|
Deletes the message.
@@ -544,10 +542,9 @@ class Message:
HTTPException
Deleting the message failed.
"""
- yield from self._state.http.delete_message(self.channel.id, self.id)
+ await self._state.http.delete_message(self.channel.id, self.id)
- @asyncio.coroutine
- def edit(self, **fields):
+ async def edit(self, **fields):
"""|coro|
Edits the message.
@@ -589,7 +586,7 @@ class Message:
if embed is not None:
fields['embed'] = embed.to_dict()
- data = yield from self._state.http.edit_message(self.id, self.channel.id, **fields)
+ data = await self._state.http.edit_message(self.id, self.channel.id, **fields)
self._update(channel=self.channel, data=data)
try:
@@ -598,18 +595,16 @@ class Message:
pass
else:
if delete_after is not None:
- @asyncio.coroutine
- def delete():
- yield from asyncio.sleep(delete_after, loop=self._state.loop)
+ async def delete():
+ await asyncio.sleep(delete_after, loop=self._state.loop)
try:
- yield from self._state.http.delete_message(self.channel.id, self.id)
+ await self._state.http.delete_message(self.channel.id, self.id)
except:
pass
- compat.create_task(delete(), loop=self._state.loop)
+ asyncio.ensure_future(delete(), loop=self._state.loop)
- @asyncio.coroutine
- def pin(self):
+ async def pin(self):
"""|coro|
Pins the message.
@@ -628,11 +623,10 @@ class Message:
having more than 50 pinned messages.
"""
- yield from self._state.http.pin_message(self.channel.id, self.id)
+ await self._state.http.pin_message(self.channel.id, self.id)
self.pinned = True
- @asyncio.coroutine
- def unpin(self):
+ async def unpin(self):
"""|coro|
Unpins the message.
@@ -650,11 +644,10 @@ class Message:
Unpinning the message failed.
"""
- yield from self._state.http.unpin_message(self.channel.id, self.id)
+ await self._state.http.unpin_message(self.channel.id, self.id)
self.pinned = False
- @asyncio.coroutine
- def add_reaction(self, emoji):
+ async def add_reaction(self, emoji):
"""|coro|
Add a reaction to the message.
@@ -694,10 +687,9 @@ class Message:
else:
raise InvalidArgument('emoji argument must be str, Emoji, or Reaction not {.__class__.__name__}.'.format(emoji))
- yield from self._state.http.add_reaction(self.id, self.channel.id, emoji)
+ await self._state.http.add_reaction(self.id, self.channel.id, emoji)
- @asyncio.coroutine
- def remove_reaction(self, emoji, member):
+ async def remove_reaction(self, emoji, member):
"""|coro|
Remove a reaction by the member from the message.
@@ -742,12 +734,11 @@ class Message:
raise InvalidArgument('emoji argument must be str, Emoji, or Reaction not {.__class__.__name__}.'.format(emoji))
if member.id == self._state.self_id:
- yield from self._state.http.remove_own_reaction(self.id, self.channel.id, emoji)
+ await self._state.http.remove_own_reaction(self.id, self.channel.id, emoji)
else:
- yield from self._state.http.remove_reaction(self.id, self.channel.id, emoji, member.id)
+ await self._state.http.remove_reaction(self.id, self.channel.id, emoji, member.id)
- @asyncio.coroutine
- def clear_reactions(self):
+ async def clear_reactions(self):
"""|coro|
Removes all the reactions from the message.
@@ -761,7 +752,7 @@ class Message:
Forbidden
You do not have the proper permissions to remove all the reactions.
"""
- yield from self._state.http.clear_reactions(self.id, self.channel.id)
+ await self._state.http.clear_reactions(self.id, self.channel.id)
def ack(self):
"""|coro|
diff --git a/discord/reaction.py b/discord/reaction.py
index 2af74789..9fdc4b74 100644
--- a/discord/reaction.py
+++ b/discord/reaction.py
@@ -137,7 +137,7 @@ class Reaction:
iterator = reaction.users()
while True:
try:
- user = yield from iterator.next()
+ user = await iterator.next()
except discord.NoMoreItems:
break
else:
diff --git a/discord/relationship.py b/discord/relationship.py
index 575627e9..b301d420 100644
--- a/discord/relationship.py
+++ b/discord/relationship.py
@@ -52,8 +52,7 @@ class Relationship:
def __repr__(self):
return '<Relationship user={0.user!r} type={0.type!r}>'.format(self)
- @asyncio.coroutine
- def delete(self):
+ async def delete(self):
"""|coro|
Deletes the relationship.
@@ -64,10 +63,9 @@ class Relationship:
Deleting the relationship failed.
"""
- yield from self._state.http.remove_relationship(self.user.id)
+ await self._state.http.remove_relationship(self.user.id)
- @asyncio.coroutine
- def accept(self):
+ async def accept(self):
"""|coro|
Accepts the relationship request. e.g. accepting a
@@ -79,4 +77,4 @@ class Relationship:
Accepting the relationship failed.
"""
- yield from self._state.http.add_relationship(self.user.id)
+ await self._state.http.add_relationship(self.user.id)
diff --git a/discord/role.py b/discord/role.py
index 3e35d8af..5cee019b 100644
--- a/discord/role.py
+++ b/discord/role.py
@@ -171,8 +171,7 @@ class Role(Hashable):
return [member for member in all_members if self in member.roles]
- @asyncio.coroutine
- def _move(self, position, reason):
+ async def _move(self, position, reason):
if position <= 0:
raise InvalidArgument("Cannot move role to position 0 or below")
@@ -196,10 +195,9 @@ class Role(Hashable):
roles.append(self.id)
payload = [{"id": z[0], "position": z[1]} for z in zip(roles, change_range)]
- yield from http.move_role_position(self.guild.id, payload, reason=reason)
+ await http.move_role_position(self.guild.id, payload, reason=reason)
- @asyncio.coroutine
- def edit(self, *, reason=None, **fields):
+ async def edit(self, *, reason=None, **fields):
"""|coro|
Edits the role.
@@ -240,7 +238,7 @@ class Role(Hashable):
position = fields.get('position')
if position is not None:
- yield from self._move(position, reason=reason)
+ await self._move(position, reason=reason)
self.position = position
try:
@@ -256,11 +254,10 @@ class Role(Hashable):
'mentionable': fields.get('mentionable', self.mentionable)
}
- data = yield from self._state.http.edit_role(self.guild.id, self.id, reason=reason, **payload)
+ data = await self._state.http.edit_role(self.guild.id, self.id, reason=reason, **payload)
self._update(data)
- @asyncio.coroutine
- def delete(self, *, reason=None):
+ async def delete(self, *, reason=None):
"""|coro|
Deletes the role.
@@ -281,4 +278,4 @@ class Role(Hashable):
Deleting the role failed.
"""
- yield from self._state.http.delete_role(self.guild.id, self.id, reason=reason)
+ await self._state.http.delete_role(self.guild.id, self.id, reason=reason)
diff --git a/discord/shard.py b/discord/shard.py
index 284f5cdc..a7fec0c4 100644
--- a/discord/shard.py
+++ b/discord/shard.py
@@ -43,7 +43,7 @@ class Shard:
self.ws = ws
self._client = client
self.loop = self._client.loop
- self._current = compat.create_future(self.loop)
+ self._current = self.loop.create_future()
self._current.set_result(None) # we just need an already done future
self._pending = asyncio.Event(loop=self.loop)
self._pending_task = None
@@ -58,47 +58,36 @@ class Shard:
def complete_pending_reads(self):
self._pending.set()
- def _pending_reads(self):
+ async def _pending_reads(self):
try:
while self.is_pending():
- yield from self.poll()
+ await self.poll()
except asyncio.CancelledError:
pass
def launch_pending_reads(self):
- self._pending_task = compat.create_task(self._pending_reads(), loop=self.loop)
+ self._pending_task = asyncio.ensure_future(self._pending_reads(), loop=self.loop)
def wait(self):
return self._pending_task
- @asyncio.coroutine
- def poll(self):
+ async def poll(self):
try:
- yield from self.ws.poll_event()
+ await self.ws.poll_event()
except ResumeWebSocket as e:
log.info('Got a request to RESUME the websocket at Shard ID %s.', self.id)
coro = DiscordWebSocket.from_client(self._client, resume=True,
shard_id=self.id,
session=self.ws.session_id,
sequence=self.ws.sequence)
- self.ws = yield from asyncio.wait_for(coro, timeout=180.0, loop=self.loop)
+ self.ws = await asyncio.wait_for(coro, timeout=180.0, loop=self.loop)
def get_future(self):
if self._current.done():
- self._current = compat.create_task(self.poll(), loop=self.loop)
+ self._current = asyncio.ensure_future(self.poll(), loop=self.loop)
return self._current
-def _ensure_coroutine_connect(gateway, loop):
- # In 3.5+ websockets.connect does not return a coroutine, but an awaitable.
- # The problem is that in 3.5.0 and in some cases 3.5.1, asyncio.ensure_future and
- # by proxy, asyncio.wait_for, do not accept awaitables, but rather futures or coroutines.
- # By wrapping it up into this function we ensure that it's in a coroutine and not an awaitable
- # even for 3.5.0 users.
- ws = yield from websockets.connect(gateway, loop=loop, klass=DiscordWebSocket)
- return ws
-
class AutoShardedClient(Client):
"""A client similar to :class:`Client` except it handles the complications
of sharding for the user into a more manageable and transparent single
@@ -149,8 +138,7 @@ class AutoShardedClient(Client):
self._connection._get_websocket = _get_websocket
- @asyncio.coroutine
- def _chunker(self, guild, *, shard_id=None):
+ async def _chunker(self, guild, *, shard_id=None):
try:
guild_id = guild.id
shard_id = shard_id or guild.shard_id
@@ -167,7 +155,7 @@ class AutoShardedClient(Client):
}
ws = self.shards[shard_id].ws
- yield from ws.send_as_json(payload)
+ await ws.send_as_json(payload)
@property
def latency(self):
@@ -189,8 +177,7 @@ class AutoShardedClient(Client):
"""
return [(shard_id, shard.ws.latency) for shard_id, shard in self.shards.items()]
- @asyncio.coroutine
- def request_offline_members(self, *guilds):
+ async def request_offline_members(self, *guilds):
"""|coro|
Requests previously offline members from the guild to be filled up
@@ -219,16 +206,16 @@ class AutoShardedClient(Client):
_guilds = sorted(guilds, key=lambda g: g.shard_id)
for shard_id, sub_guilds in itertools.groupby(_guilds, key=lambda g: g.shard_id):
sub_guilds = list(sub_guilds)
- yield from self._connection.request_offline_members(sub_guilds, shard_id=shard_id)
+ await self._connection.request_offline_members(sub_guilds, shard_id=shard_id)
- @asyncio.coroutine
- def launch_shard(self, gateway, shard_id):
+ async def launch_shard(self, gateway, shard_id):
try:
- ws = yield from asyncio.wait_for(_ensure_coroutine_connect(gateway, self.loop), loop=self.loop, timeout=180.0)
+ coro = websockets.connect(gateway, loop=self.loop, klass=DiscordWebSocket)
+ ws = await asyncio.wait_for(coro, loop=self.loop, timeout=180.0)
except Exception as e:
log.info('Failed to connect for shard_id: %s. Retrying...', shard_id)
- yield from asyncio.sleep(5.0, loop=self.loop)
- return (yield from self.launch_shard(gateway, shard_id))
+ await asyncio.sleep(5.0, loop=self.loop)
+ return (await self.launch_shard(gateway, shard_id))
ws.token = self.http.token
ws._connection = self._connection
@@ -240,31 +227,30 @@ class AutoShardedClient(Client):
try:
# OP HELLO
- yield from asyncio.wait_for(ws.poll_event(), loop=self.loop, timeout=180.0)
- yield from asyncio.wait_for(ws.identify(), loop=self.loop, timeout=180.0)
+ await asyncio.wait_for(ws.poll_event(), loop=self.loop, timeout=180.0)
+ await asyncio.wait_for(ws.identify(), loop=self.loop, timeout=180.0)
except asyncio.TimeoutError:
log.info('Timed out when connecting for shard_id: %s. Retrying...', shard_id)
- yield from asyncio.sleep(5.0, loop=self.loop)
- return (yield from self.launch_shard(gateway, shard_id))
+ await asyncio.sleep(5.0, loop=self.loop)
+ return (await self.launch_shard(gateway, shard_id))
# keep reading the shard while others connect
self.shards[shard_id] = ret = Shard(ws, self)
ret.launch_pending_reads()
- yield from asyncio.sleep(5.0, loop=self.loop)
+ await asyncio.sleep(5.0, loop=self.loop)
- @asyncio.coroutine
- def launch_shards(self):
+ async def launch_shards(self):
if self.shard_count is None:
- self.shard_count, gateway = yield from self.http.get_bot_gateway()
+ self.shard_count, gateway = await self.http.get_bot_gateway()
else:
- gateway = yield from self.http.get_gateway()
+ gateway = await self.http.get_gateway()
self._connection.shard_count = self.shard_count
shard_ids = self.shard_ids if self.shard_ids else range(self.shard_count)
for shard_id in shard_ids:
- yield from self.launch_shard(gateway, shard_id)
+ await self.launch_shard(gateway, shard_id)
shards_to_wait_for = []
for shard in self.shards.values():
@@ -272,21 +258,19 @@ class AutoShardedClient(Client):
shards_to_wait_for.append(shard.wait())
# wait for all pending tasks to finish
- yield from utils.sane_wait_for(shards_to_wait_for, timeout=300.0, loop=self.loop)
+ await utils.sane_wait_for(shards_to_wait_for, timeout=300.0, loop=self.loop)
- @asyncio.coroutine
- def _connect(self):
- yield from self.launch_shards()
+ async def _connect(self):
+ await self.launch_shards()
while True:
pollers = [shard.get_future() for shard in self.shards.values()]
- done, pending = yield from asyncio.wait(pollers, loop=self.loop, return_when=asyncio.FIRST_COMPLETED)
+ done, pending = await asyncio.wait(pollers, loop=self.loop, return_when=asyncio.FIRST_COMPLETED)
for f in done:
# we wanna re-raise to the main Client.connect handler if applicable
f.result()
- @asyncio.coroutine
- def close(self):
+ async def close(self):
"""|coro|
Closes the connection to discord.
@@ -298,16 +282,15 @@ class AutoShardedClient(Client):
for vc in self.voice_clients:
try:
- yield from vc.disconnect()
+ await vc.disconnect()
except:
pass
to_close = [shard.ws.close() for shard in self.shards.values()]
- yield from asyncio.wait(to_close, loop=self.loop)
- yield from self.http.close()
+ await asyncio.wait(to_close, loop=self.loop)
+ await self.http.close()
- @asyncio.coroutine
- def change_presence(self, *, activity=None, status=None, afk=False, shard_id=None):
+ async def change_presence(self, *, activity=None, status=None, afk=False, shard_id=None):
"""|coro|
Changes the client's presence.
@@ -355,12 +338,12 @@ class AutoShardedClient(Client):
if shard_id is None:
for shard in self.shards.values():
- yield from shard.ws.change_presence(activity=activity, status=status, afk=afk)
+ await shard.ws.change_presence(activity=activity, status=status, afk=afk)
guilds = self._connection.guilds
else:
shard = self.shards[shard_id]
- yield from shard.ws.change_presence(activity=activity, status=status, afk=afk)
+ await shard.ws.change_presence(activity=activity, status=status, afk=afk)
guilds = [g for g in self._connection.guilds if g.shard_id == shard_id]
for guild in guilds:
diff --git a/discord/state.py b/discord/state.py
index 899ce029..0e398583 100644
--- a/discord/state.py
+++ b/discord/state.py
@@ -255,8 +255,7 @@ class ConnectionState:
return channel, guild
- @asyncio.coroutine
- def request_offline_members(self, guilds):
+ async def request_offline_members(self, guilds):
# get all the chunks
chunks = []
for guild in guilds:
@@ -265,17 +264,16 @@ class ConnectionState:
# we only want to request ~75 guilds per chunk request.
splits = [guilds[i:i + 75] for i in range(0, len(guilds), 75)]
for split in splits:
- yield from self.chunker(split)
+ await self.chunker(split)
# wait for the chunks
if chunks:
try:
- yield from utils.sane_wait_for(chunks, timeout=len(chunks) * 30.0, loop=self.loop)
+ await utils.sane_wait_for(chunks, timeout=len(chunks) * 30.0, loop=self.loop)
except asyncio.TimeoutError:
log.info('Somehow timed out waiting for chunks.')
- @asyncio.coroutine
- def _delay_ready(self):
+ async def _delay_ready(self):
try:
launch = self._ready_state.launch
@@ -285,11 +283,11 @@ class ConnectionState:
# this snippet of code is basically waiting 2 seconds
# until the last GUILD_CREATE was sent
launch.set()
- yield from asyncio.sleep(2, loop=self.loop)
+ await asyncio.sleep(2, loop=self.loop)
guilds = self._ready_state.guilds
if self._fetch_offline:
- yield from self.request_offline_members(guilds)
+ await self.request_offline_members(guilds)
# remove the state
try:
@@ -300,7 +298,7 @@ class ConnectionState:
# call GUILD_SYNC after we're done chunking
if not self.is_bot:
log.info('Requesting GUILD_SYNC for %s guilds', len(self.guilds))
- yield from self.syncer([s.id for s in self.guilds])
+ await self.syncer([s.id for s in self.guilds])
except asyncio.CancelledError:
pass
else:
@@ -336,7 +334,7 @@ class ConnectionState:
self._add_private_channel(factory(me=self.user, data=pm, state=self))
self.dispatch('connect')
- self._ready_task = compat.create_task(self._delay_ready(), loop=self.loop)
+ self._ready_task = asyncio.ensure_future(self._delay_ready(), loop=self.loop)
def parse_resumed(self, data):
self.dispatch('resumed')
@@ -617,13 +615,12 @@ class ConnectionState:
return self._add_guild_from_data(data)
- @asyncio.coroutine
- def _chunk_and_dispatch(self, guild, unavailable):
+ async def _chunk_and_dispatch(self, guild, unavailable):
chunks = list(self.chunks_needed(guild))
- yield from self.chunker(guild)
+ await self.chunker(guild)
if chunks:
try:
- yield from utils.sane_wait_for(chunks, timeout=len(chunks), loop=self.loop)
+ await utils.sane_wait_for(chunks, timeout=len(chunks), loop=self.loop)
except asyncio.TimeoutError:
log.info('Somehow timed out waiting for chunks.')
@@ -662,7 +659,7 @@ class ConnectionState:
# since we're not waiting for 'useful' READY we'll just
# do the chunk request here if wanted
if self._fetch_offline:
- compat.create_task(self._chunk_and_dispatch(guild, unavailable), loop=self.loop)
+ asyncio.ensure_future(self._chunk_and_dispatch(guild, unavailable), loop=self.loop)
return
# Dispatch available if newly available
@@ -807,7 +804,7 @@ class ConnectionState:
vc = self._get_voice_client(key_id)
if vc is not None:
- compat.create_task(vc._create_socket(key_id, data))
+ asyncio.ensure_future(vc._create_socket(key_id, data))
def parse_typing_start(self, data):
channel, guild = self._get_guild_channel(data)
@@ -886,7 +883,7 @@ class ConnectionState:
return Message(state=self, channel=channel, data=data)
def receive_chunk(self, guild_id):
- future = compat.create_future(self.loop)
+ future = self.loop.create_future()
listener = Listener(ListenerType.chunk, future, lambda s: s.id == guild_id)
self._listeners.append(listener)
return future
@@ -896,8 +893,7 @@ class AutoShardedConnectionState(ConnectionState):
super().__init__(*args, **kwargs)
self._ready_task = None
- @asyncio.coroutine
- def request_offline_members(self, guilds, *, shard_id):
+ async def request_offline_members(self, guilds, *, shard_id):
# get all the chunks
chunks = []
for guild in guilds:
@@ -906,30 +902,29 @@ class AutoShardedConnectionState(ConnectionState):
# we only want to request ~75 guilds per chunk request.
splits = [guilds[i:i + 75] for i in range(0, len(guilds), 75)]
for split in splits:
- yield from self.chunker(split, shard_id=shard_id)
+ await self.chunker(split, shard_id=shard_id)
# wait for the chunks
if chunks:
try:
- yield from utils.sane_wait_for(chunks, timeout=len(chunks) * 30.0, loop=self.loop)
+ await utils.sane_wait_for(chunks, timeout=len(chunks) * 30.0, loop=self.loop)
except asyncio.TimeoutError:
log.info('Somehow timed out waiting for chunks.')
- @asyncio.coroutine
- def _delay_ready(self):
+ async def _delay_ready(self):
launch = self._ready_state.launch
while not launch.is_set():
# this snippet of code is basically waiting 2 seconds
# until the last GUILD_CREATE was sent
launch.set()
- yield from asyncio.sleep(2.0 * self.shard_count, loop=self.loop)
+ await asyncio.sleep(2.0 * self.shard_count, loop=self.loop)
if self._fetch_offline:
guilds = sorted(self._ready_state.guilds, key=lambda g: g.shard_id)
for shard_id, sub_guilds in itertools.groupby(guilds, key=lambda g: g.shard_id):
sub_guilds = list(sub_guilds)
- yield from self.request_offline_members(sub_guilds, shard_id=shard_id)
+ await self.request_offline_members(sub_guilds, shard_id=shard_id)
self.dispatch('shard_ready', shard_id)
# remove the state
@@ -964,4 +959,4 @@ class AutoShardedConnectionState(ConnectionState):
self.dispatch('connect')
if self._ready_task is None:
- self._ready_task = compat.create_task(self._delay_ready(), loop=self.loop)
+ self._ready_task = asyncio.ensure_future(self._delay_ready(), loop=self.loop)
diff --git a/discord/user.py b/discord/user.py
index c43bae91..a1f3704d 100644
--- a/discord/user.py
+++ b/discord/user.py
@@ -313,8 +313,7 @@ class ClientUser(BaseUser):
"""Returns a :class:`list` of :class:`User`\s that the user has blocked."""
return [r.user for r in self._relationships.values() if r.type is RelationshipType.blocked]
- @asyncio.coroutine
- def edit(self, **fields):
+ async def edit(self, **fields):
"""|coro|
Edits the current profile of the client.
@@ -387,7 +386,7 @@ class ClientUser(BaseUser):
http = self._state.http
- data = yield from http.edit_profile(**args)
+ data = await http.edit_profile(**args)
if not_bot_account:
self.email = data['email']
try:
@@ -398,8 +397,7 @@ class ClientUser(BaseUser):
# manually update data by calling __init__ explicitly.
self.__init__(state=self._state, data=data)
- @asyncio.coroutine
- def create_group(self, *recipients):
+ async def create_group(self, *recipients):
"""|coro|
Creates a group direct message with the recipients
@@ -434,7 +432,7 @@ class ClientUser(BaseUser):
raise ClientException('You must have two or more recipients to create a group.')
users = [str(u.id) for u in recipients]
- data = yield from self._state.http.create_group(self.id, users)
+ data = await self._state.http.create_group(self.id, users)
return GroupChannel(me=self, data=data, state=self._state)
class User(BaseUser, discord.abc.Messageable):
@@ -477,9 +475,8 @@ class User(BaseUser, discord.abc.Messageable):
def __repr__(self):
return '<User id={0.id} name={0.name!r} discriminator={0.discriminator!r} bot={0.bot}>'.format(self)
- @asyncio.coroutine
- def _get_channel(self):
- ch = yield from self.create_dm()
+ async def _get_channel(self):
+ ch = await self.create_dm()
return ch
@property
@@ -491,8 +488,7 @@ class User(BaseUser, discord.abc.Messageable):
"""
return self._state._get_private_channel_by_user(self.id)
- @asyncio.coroutine
- def create_dm(self):
+ async def create_dm(self):
"""Creates a :class:`DMChannel` with this user.
This should be rarely called, as this is done transparently for most
@@ -503,7 +499,7 @@ class User(BaseUser, discord.abc.Messageable):
return found
state = self._state
- data = yield from state.http.start_private_message(self.id)
+ data = await state.http.start_private_message(self.id)
return state.add_dm_channel(data)
@property
@@ -525,8 +521,7 @@ class User(BaseUser, discord.abc.Messageable):
return False
return r.type is RelationshipType.blocked
- @asyncio.coroutine
- def block(self):
+ async def block(self):
"""|coro|
Blocks the user.
@@ -539,10 +534,9 @@ class User(BaseUser, discord.abc.Messageable):
Blocking the user failed.
"""
- yield from self._state.http.add_relationship(self.id, type=RelationshipType.blocked.value)
+ await self._state.http.add_relationship(self.id, type=RelationshipType.blocked.value)
- @asyncio.coroutine
- def unblock(self):
+ async def unblock(self):
"""|coro|
Unblocks the user.
@@ -554,10 +548,9 @@ class User(BaseUser, discord.abc.Messageable):
HTTPException
Unblocking the user failed.
"""
- yield from self._state.http.remove_relationship(self.id)
+ await self._state.http.remove_relationship(self.id)
- @asyncio.coroutine
- def remove_friend(self):
+ async def remove_friend(self):
"""|coro|
Removes the user as a friend.
@@ -569,10 +562,9 @@ class User(BaseUser, discord.abc.Messageable):
HTTPException
Removing the user as a friend failed.
"""
- yield from self._state.http.remove_relationship(self.id)
+ await self._state.http.remove_relationship(self.id)
- @asyncio.coroutine
- def send_friend_request(self):
+ async def send_friend_request(self):
"""|coro|
Sends the user a friend request.
@@ -584,10 +576,9 @@ class User(BaseUser, discord.abc.Messageable):
HTTPException
Sending the friend request failed.
"""
- yield from self._state.http.send_friend_request(username=self.name, discriminator=self.discriminator)
+ await self._state.http.send_friend_request(username=self.name, discriminator=self.discriminator)
- @asyncio.coroutine
- def profile(self):
+ async def profile(self):
"""|coro|
Gets the user's profile. This can only be used by non-bot accounts.
@@ -606,7 +597,7 @@ class User(BaseUser, discord.abc.Messageable):
"""
state = self._state
- data = yield from state.http.get_user_profile(self.id)
+ data = await state.http.get_user_profile(self.id)
def transform(d):
return state._get_guild(int(d['id']))
diff --git a/discord/utils.py b/discord/utils.py
index ee612ecc..5c939922 100644
--- a/discord/utils.py
+++ b/discord/utils.py
@@ -29,6 +29,7 @@ from .errors import InvalidArgument
import datetime
from base64 import b64encode
from email.utils import parsedate_to_datetime
+from inspect import isawaitable as _isawaitable
import asyncio
import json
import warnings, functools
@@ -264,27 +265,23 @@ def _parse_ratelimit_header(request):
reset = datetime.datetime.fromtimestamp(int(request.headers['X-Ratelimit-Reset']), datetime.timezone.utc)
return (reset - now).total_seconds()
-def maybe_coroutine(f, *args, **kwargs):
+async def maybe_coroutine(f, *args, **kwargs):
value = f(*args, **kwargs)
- if asyncio.iscoroutine(value):
- return (yield from value)
+ if _isawaitable(value):
+ return (await value)
else:
return value
-def async_all(gen):
- check = asyncio.iscoroutine
+async def async_all(gen, *, check=_isawaitable):
for elem in gen:
if check(elem):
- elem = yield from elem
+ elem = await elem
if not elem:
return False
return True
-def sane_wait_for(futures, *, timeout, loop):
- done, pending = yield from asyncio.wait(futures, timeout=timeout, loop=loop)
+async def sane_wait_for(futures, *, timeout, loop):
+ done, pending = await asyncio.wait(futures, timeout=timeout, loop=loop)
if len(pending) != 0:
raise asyncio.TimeoutError()
diff --git a/discord/voice_client.py b/discord/voice_client.py
index f892bdb9..6f88856e 100644
--- a/discord/voice_client.py
+++ b/discord/voice_client.py
@@ -129,8 +129,7 @@ class VoiceClient:
# connection related
- @asyncio.coroutine
- def start_handshake(self):
+ async def start_handshake(self):
log.info('Starting voice handshake...')
key_id, key_name = self.channel._get_voice_client_key()
@@ -140,21 +139,20 @@ class VoiceClient:
self._connections += 1
# request joining
- yield from ws.voice_state(guild_id, channel_id)
+ await ws.voice_state(guild_id, channel_id)
try:
- yield from asyncio.wait_for(self._handshake_complete.wait(), timeout=self.timeout, loop=self.loop)
+ await asyncio.wait_for(self._handshake_complete.wait(), timeout=self.timeout, loop=self.loop)
except asyncio.TimeoutError as e:
- yield from self.terminate_handshake(remove=True)
+ await self.terminate_handshake(remove=True)
raise e
log.info('Voice handshake complete. Endpoint found %s (IP: %s)', self.endpoint, self.endpoint_ip)
- @asyncio.coroutine
- def terminate_handshake(self, *, remove=False):
+ async def terminate_handshake(self, *, remove=False):
guild_id, channel_id = self.channel._get_voice_state_pair()
self._handshake_complete.clear()
- yield from self.main_ws.voice_state(guild_id, None, self_mute=True)
+ await self.main_ws.voice_state(guild_id, None, self_mute=True)
log.info('The voice handshake is being terminated for Channel ID %s (Guild ID %s)', channel_id, guild_id)
if remove:
@@ -162,8 +160,7 @@ class VoiceClient:
key_id, _ = self.channel._get_voice_client_key()
self._state._remove_voice_client(key_id)
- @asyncio.coroutine
- def _create_socket(self, server_id, data):
+ async def _create_socket(self, server_id, data):
self._connected.clear()
self.session_id = self.main_ws.session_id
self.server_id = server_id
@@ -190,13 +187,12 @@ class VoiceClient:
if self._handshake_complete.is_set():
# terminate the websocket and handle the reconnect loop if necessary.
self._handshake_complete.clear()
- yield from self.ws.close(4000)
+ await self.ws.close(4000)
return
self._handshake_complete.set()
- @asyncio.coroutine
- def connect(self, *, reconnect=True, _tries=0, do_handshake=True):
+ async def connect(self, *, reconnect=True, _tries=0, do_handshake=True):
log.info('Connecting to voice...')
try:
del self.secret_key
@@ -204,56 +200,54 @@ class VoiceClient:
pass
if do_handshake:
- yield from self.start_handshake()
+ await self.start_handshake()
try:
- self.ws = yield from DiscordVoiceWebSocket.from_client(self)
+ self.ws = await DiscordVoiceWebSocket.from_client(self)
self._connected.clear()
while not hasattr(self, 'secret_key'):
- yield from self.ws.poll_event()
+ await self.ws.poll_event()
self._connected.set()
except (ConnectionClosed, asyncio.TimeoutError):
if reconnect and _tries < 5:
log.exception('Failed to connect to voice... Retrying...')
- yield from asyncio.sleep(1 + _tries * 2.0, loop=self.loop)
- yield from self.terminate_handshake()
- yield from self.connect(reconnect=reconnect, _tries=_tries + 1)
+ await asyncio.sleep(1 + _tries * 2.0, loop=self.loop)
+ await self.terminate_handshake()
+ await self.connect(reconnect=reconnect, _tries=_tries + 1)
else:
raise
if self._runner is None:
self._runner = self.loop.create_task(self.poll_voice_ws(reconnect))
- @asyncio.coroutine
- def poll_voice_ws(self, reconnect):
+ async def poll_voice_ws(self, reconnect):
backoff = ExponentialBackoff()
while True:
try:
- yield from self.ws.poll_event()
+ await self.ws.poll_event()
except (ConnectionClosed, asyncio.TimeoutError) as e:
if isinstance(e, ConnectionClosed):
if e.code == 1000:
- yield from self.disconnect()
+ await self.disconnect()
break
if not reconnect:
- yield from self.disconnect()
+ await self.disconnect()
raise e
retry = backoff.delay()
log.exception('Disconnected from voice... Reconnecting in %.2fs.', retry)
self._connected.clear()
- yield from asyncio.sleep(retry, loop=self.loop)
- yield from self.terminate_handshake()
+ await asyncio.sleep(retry, loop=self.loop)
+ await self.terminate_handshake()
try:
- yield from self.connect(reconnect=True)
+ await self.connect(reconnect=True)
except asyncio.TimeoutError:
# at this point we've retried 5 times... let's continue the loop.
log.warning('Could not connect to voice... Retrying...')
continue
- @asyncio.coroutine
- def disconnect(self, *, force=False):
+ async def disconnect(self, *, force=False):
"""|coro|
Disconnects this voice client from voice.
@@ -266,15 +260,14 @@ class VoiceClient:
try:
if self.ws:
- yield from self.ws.close()
+ await self.ws.close()
- yield from self.terminate_handshake(remove=True)
+ await self.terminate_handshake(remove=True)
finally:
if self.socket:
self.socket.close()
- @asyncio.coroutine
- def move_to(self, channel):
+ async def move_to(self, channel):
"""|coro|
Moves you to a different voice channel.
@@ -285,7 +278,7 @@ class VoiceClient:
The channel to move to. Must be a voice channel.
"""
guild_id, _ = self.channel._get_voice_state_pair()
- yield from self.main_ws.voice_state(guild_id, channel.id)
+ await self.main_ws.voice_state(guild_id, channel.id)
def is_connected(self):
""":class:`bool`: Indicates if the voice client is connected to voice."""
diff --git a/discord/webhook.py b/discord/webhook.py
index d9d380a1..9d336aee 100644
--- a/discord/webhook.py
+++ b/discord/webhook.py
@@ -135,8 +135,7 @@ class AsyncWebhookAdapter(WebhookAdapter):
self.session = session
self.loop = session.loop
- @asyncio.coroutine
- def request(self, verb, url, payload=None, multipart=None):
+ async def request(self, verb, url, payload=None, multipart=None):
headers = {}
data = None
if payload:
@@ -152,9 +151,8 @@ class AsyncWebhookAdapter(WebhookAdapter):
data.add_field(key, value)
for tries in range(5):
- r = yield from self.session.request(verb, url, headers=headers, data=data)
- try:
- data = yield from r.text(encoding='utf-8')
+ async with self.session.request(verb, url, headers=headers, data=data) as r:
+ data = await r.text(encoding='utf-8')
if r.headers['Content-Type'] == 'application/json':
data = json.loads(data)
@@ -162,7 +160,7 @@ class AsyncWebhookAdapter(WebhookAdapter):
remaining = r.headers.get('X-Ratelimit-Remaining')
if remaining == '0' and r.status != 429:
delta = utils._parse_ratelimit_header(r)
- yield from asyncio.sleep(delta, loop=self.loop)
+ await asyncio.sleep(delta, loop=self.loop)
if 300 > r.status >= 200:
return data
@@ -170,11 +168,11 @@ class AsyncWebhookAdapter(WebhookAdapter):
# we are being rate limited
if r.status == 429:
retry_after = data['retry_after'] / 1000.0
- yield from asyncio.sleep(retry_after, loop=self.loop)
+ await asyncio.sleep(retry_after, loop=self.loop)
continue
if r.status in (500, 502):
- yield from asyncio.sleep(1 + tries * 2, loop=self.loop)
+ await asyncio.sleep(1 + tries * 2, loop=self.loop)
continue
if r.status == 403:
@@ -183,12 +181,9 @@ class AsyncWebhookAdapter(WebhookAdapter):
raise NotFound(r, data)
else:
raise HTTPException(r, data)
- finally:
- yield from r.release()
- @asyncio.coroutine
- def handle_execution_response(self, response, *, wait):
- data = yield from response
+ async def handle_execution_response(self, response, *, wait):
+ data = await response
if not wait:
return data
diff --git a/docs/api.rst b/docs/api.rst
index 8be772ac..109f78f9 100644
--- a/docs/api.rst
+++ b/docs/api.rst
@@ -93,17 +93,8 @@ to handle it, which defaults to print a traceback and ignoring the exception.
.. warning::
All the events must be a |corourl|_. If they aren't, then you might get unexpected
- errors. In order to turn a function into a coroutine they must either be ``async def``
- functions or in 3.4 decorated with :func:`asyncio.coroutine`.
-
- The following two functions are examples of coroutine functions: ::
-
- async def on_ready():
- pass
-
- @asyncio.coroutine
- def on_ready():
- pass
+ errors. In order to turn a function into a coroutine they must be ``async def``
+ functions.
.. function:: on_connect()
@@ -1306,22 +1297,11 @@ Some API functions return an "async iterator". An async iterator is something th
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: ::
+These async iterators can be used as follows: ::
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.next()
- except discord.NoMoreItems:
- break
-
- # do stuff with item here
-
Certain utilities make working with async iterators easier, detailed below.
.. class:: AsyncIterator
diff --git a/docs/faq.rst b/docs/faq.rst
index 57daa471..506a4d43 100644
--- a/docs/faq.rst
+++ b/docs/faq.rst
@@ -15,27 +15,6 @@ Coroutines
Questions regarding coroutines and asyncio belong here.
-I get a SyntaxError around the word ``async``\! What should I do?
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-This :exc:`SyntaxError` happens because you're using a Python version lower than 3.5. Python 3.4 uses ``@asyncio.coroutine`` and
-``yield from`` instead of ``async def`` and ``await``.
-
-Thus you must do the following instead: ::
-
- async def foo():
- await bar()
-
- # into
-
- @asyncio.coroutine
- def foo():
- yield from bar()
-
-Don't forget to ``import asyncio`` on the top of your files.
-
-**It is heavily recommended that you update to Python 3.5 or higher as it simplifies asyncio massively.**
-
What is a coroutine?
~~~~~~~~~~~~~~~~~~~~~~
@@ -195,11 +174,6 @@ technically in another thread, we must take caution in calling thread-safe opera
us, :mod:`asyncio` comes with a :func:`asyncio.run_coroutine_threadsafe` function that allows us to call
a coroutine from another thread.
-.. warning::
-
- This function is only part of 3.5.1+ and 3.4.4+. If you are not using these Python versions then use
- ``discord.compat.run_coroutine_threadsafe``.
-
However, this function returns a :class:`concurrent.Future` and to actually call it we have to fetch its result. Putting all of
this together we can do the following: ::
diff --git a/docs/intro.rst b/docs/intro.rst
index 6a8a4d71..29d9f58a 100644
--- a/docs/intro.rst
+++ b/docs/intro.rst
@@ -11,9 +11,9 @@ in creating applications that utilise the Discord API.
Prerequisites
---------------
-discord.py works with Python 3.4.2 or higher. Support for earlier versions of Python
-is not provided. Python 2.7 or lower is not supported. Python 3.3 is not supported
-due to one of the dependencies (``aiohttp``) not supporting Python 3.3.
+discord.py works with Python 3.5.2 or higher. Support for earlier versions of Python
+is not provided. Python 2.7 or lower is not supported. Python 3.4 or lower is not supported
+due to one of the dependencies (``aiohttp``) not supporting Python 3.4.
.. _installing:
diff --git a/docs/migrating.rst b/docs/migrating.rst
index e9fbf000..6d196d87 100644
--- a/docs/migrating.rst
+++ b/docs/migrating.rst
@@ -14,6 +14,13 @@ new library.
Part of the redesign involves making things more easy to use and natural. Things are done on the
:ref:`models <discord_api_models>` instead of requiring a :class:`Client` instance to do any work.
+Python Version Change
+-----------------------
+
+In order to make development easier and also to allow for our dependencies to upgrade to allow usage of 3.7 or higher,
+the library had to remove support for Python versions lower than 3.5.2, which essentially means that **support for Python 3.4
+is dropped**.
+
Major Model Changes
---------------------
@@ -441,14 +448,14 @@ Prior to v1.0, certain functions like ``Client.logs_from`` would return a differ
In v1.0, this change has been reverted and will now return a singular type meeting an abstract concept called
:class:`AsyncIterator`.
-This allows you to iterate over it like normal in Python 3.5+: ::
+This allows you to iterate over it like normal: ::
async for message in channel.history():
print(message)
-Or turn it into a list for either Python 3.4 or 3.5+: ::
+Or turn it into a list: ::
- messages = await channel.history().flatten() # use yield from for 3.4!
+ messages = await channel.history().flatten()
for message in messages:
print(message)
diff --git a/requirements.txt b/requirements.txt
index 867a4063..607d4503 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,2 +1,2 @@
-aiohttp>=2.0.0,<2.3.0
-websockets>=3.0,<4.0
+aiohttp>=3.3.0,<3.4.0
+websockets>=5.0,<6.0
diff --git a/setup.py b/setup.py
index b2c74039..bfc110c1 100644
--- a/setup.py
+++ b/setup.py
@@ -63,7 +63,6 @@ setup(name='discord.py',
'Intended Audience :: Developers',
'Natural Language :: English',
'Operating System :: OS Independent',
- 'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Topic :: Internet',