From 0723f5e7afc809d8aeb20920c5539bc32e40b7eb Mon Sep 17 00:00:00 2001 From: Felix Yan Date: Sat, 13 Aug 2022 12:19:19 +0300 Subject: [PATCH 1/2] Add support for Python 3.10 by dropping event loop handling Implements suggestions in https://github.com/Shizmob/pydle/issues/162#issuecomment-1079797049 to remove Pydle's internal handling of the event loop object entirely. --- README.md | 6 ++--- docs/intro.rst | 6 ++--- pydle/client.py | 42 ++++++++------------------------ pydle/connection.py | 10 +++----- pydle/features/ircv3/metadata.py | 4 ++- pydle/features/ircv3/sasl.py | 5 ++-- pydle/features/rfc1459/client.py | 9 ++++--- pydle/features/tls.py | 3 +-- pydle/utils/irccat.py | 2 +- pydle/utils/run.py | 4 +-- pyproject.toml | 2 +- tests/mocks.py | 1 - 12 files changed, 34 insertions(+), 60 deletions(-) diff --git a/README.md b/README.md index 0c4bf1f..bfee5d4 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ pydle Python IRC library. ------------------- -pydle is a compact, flexible and standards-abiding IRC library for Python 3.6 through 3.9. +pydle is a compact, flexible and standards-abiding IRC library for Python 3.7 through 3.10. Features -------- @@ -65,9 +65,7 @@ Furthermore, since pydle is simply `asyncio`-based, you can run the client in yo import asyncio client = MyOwnBot('MyBot') -loop = asyncio.get_event_loop() -asyncio.ensure_future(client.connect('irc.rizon.net', tls=True, tls_verify=False), loop=loop) -loop.run_forever() +asyncio.run(client.connect('irc.rizon.net', tls=True, tls_verify=False)) ``` diff --git a/docs/intro.rst b/docs/intro.rst index 5bf98e5..7a5f150 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -4,7 +4,7 @@ Introduction to pydle What is pydle? -------------- -pydle is an IRC library for Python 3.6 through 3.9. +pydle is an IRC library for Python 3.7 through 3.10. Although old and dated on some fronts, IRC is still used by a variety of communities as the real-time communication method of choice, and the most popular IRC networks can still count on tens of thousands of users at any point during the day. @@ -35,8 +35,8 @@ All dependencies can be installed using the standard package manager for Python, Compatibility ------------- -pydle works in any interpreter that implements Python 3.6-3.9. Although mainly tested in CPython_, the standard Python implementation, -there is no reason why pydle itself should not work in alternative implementations like PyPy_, as long as they support the Python 3.6 language requirements. +pydle works in any interpreter that implements Python 3.7-3.10. Although mainly tested in CPython_, the standard Python implementation, +there is no reason why pydle itself should not work in alternative implementations like PyPy_, as long as they support the Python 3.7 language requirements. .. _CPython: https://python.org .. _PyPy: http://pypy.org diff --git a/pydle/client.py b/pydle/client.py index 1e867e6..c74065f 100644 --- a/pydle/client.py +++ b/pydle/client.py @@ -2,7 +2,7 @@ # Basic IRC client implementation. import asyncio import logging -from asyncio import new_event_loop, gather, get_event_loop, sleep +from asyncio import gather, sleep import warnings from . import connection, protocol import inspect @@ -58,17 +58,11 @@ def PING_TIMEOUT(self, value): self.READ_TIMEOUT = value def __init__(self, nickname, fallback_nicknames=None, username=None, realname=None, - eventloop=None, **kwargs): + **kwargs): """ Create a client. """ self._nicknames = [nickname] + (fallback_nicknames or []) self.username = username or nickname.lower() self.realname = realname or nickname - if eventloop: - self.eventloop = eventloop - else: - self.eventloop = get_event_loop() - - self.own_eventloop = not eventloop self._reset_connection_attributes() self._reset_attributes() @@ -104,11 +98,7 @@ def _reset_connection_attributes(self): def run(self, *args, **kwargs): """ Connect and run bot in event loop. """ - self.eventloop.run_until_complete(self.connect(*args, **kwargs)) - try: - self.eventloop.run_forever() - finally: - self.eventloop.stop() + asyncio.run(self.connect(*args, **kwargs)) async def connect(self, hostname=None, port=None, reconnect=False, **kwargs): """ Connect to IRC server. """ @@ -128,7 +118,7 @@ async def connect(self, hostname=None, port=None, reconnect=False, **kwargs): if self.server_tag: self.logger = logging.getLogger(self.__class__.__name__ + ':' + self.server_tag) - self.eventloop.create_task(self.handle_forever()) + asyncio.create_task(self.handle_forever()) async def disconnect(self, expected=True): """ Disconnect from server. """ @@ -146,18 +136,13 @@ async def _disconnect(self, expected): # Callback. await self.on_disconnect(expected) - # Shut down event loop. - if expected and self.own_eventloop: - self.connection.stop() - async def _connect(self, hostname, port, reconnect=False, channels=None, encoding=protocol.DEFAULT_ENCODING, source_address=None): """ Connect to IRC host. """ # Create connection if we can't reuse it. if not reconnect or not self.connection: self._autojoin_channels = channels or [] - self.connection = connection.Connection(hostname, port, source_address=source_address, - eventloop=self.eventloop) + self.connection = connection.Connection(hostname, port, source_address=source_address) self.encoding = encoding # Connect. @@ -387,7 +372,7 @@ async def on_data(self, data): while self._has_message(): message = self._parse_message() - self.eventloop.create_task(self.on_raw(message)) + asyncio.create_task(self.on_raw(message)) async def on_data_error(self, exception): """ Handle error. """ @@ -467,8 +452,7 @@ def event(self, func): class ClientPool: """ A pool of clients that are ran and handled in parallel. """ - def __init__(self, clients=None, eventloop=None): - self.eventloop = eventloop if eventloop else new_event_loop() + def __init__(self, clients=None): self.clients = set(clients or []) self.connect_args = {} @@ -476,15 +460,12 @@ def connect(self, client: BasicClient, *args, **kwargs): """ Add client to pool. """ self.clients.add(client) self.connect_args[client] = (args, kwargs) - # hack the clients event loop to use the pools own event loop - client.eventloop = self.eventloop - # necessary to run multiple clients in the same thread via the pool def disconnect(self, client): """ Remove client from pool. """ self.clients.remove(client) del self.connect_args[client] - asyncio.run_coroutine_threadsafe(client.disconnect(expected=True), self.eventloop) + asyncio.run_coroutine_threadsafe(client.disconnect(expected=True)) def __contains__(self, item): return item in self.clients @@ -499,10 +480,7 @@ def handle_forever(self): args, kwargs = self.connect_args[client] connection_list.append(client.connect(*args, **kwargs)) # single future for executing the connections - connections = gather(*connection_list, loop=self.eventloop) + connections = gather(*connection_list) # run the connections - self.eventloop.run_until_complete(connections) - - # run the clients - self.eventloop.run_forever() + asyncio.run(connections) diff --git a/pydle/connection.py b/pydle/connection.py index c9a9e8e..3b87937 100644 --- a/pydle/connection.py +++ b/pydle/connection.py @@ -21,7 +21,7 @@ class Connection: def __init__(self, hostname, port, tls=False, tls_verify=True, tls_certificate_file=None, tls_certificate_keyfile=None, tls_certificate_password=None, ping_timeout=240, - source_address=None, eventloop=None): + source_address=None): self.hostname = hostname self.port = port self.source_address = source_address @@ -36,7 +36,6 @@ def __init__(self, hostname, port, tls=False, tls_verify=True, tls_certificate_f self.reader = None self.writer = None - self.eventloop = eventloop or asyncio.new_event_loop() async def connect(self): """ Connect to target. """ @@ -49,8 +48,7 @@ async def connect(self): host=self.hostname, port=self.port, local_addr=self.source_address, - ssl=self.tls_context, - loop=self.eventloop + ssl=self.tls_context ) def create_tls_context(self): @@ -103,8 +101,8 @@ def connected(self): return self.reader is not None and self.writer is not None def stop(self): - """ Stop event loop. """ - self.eventloop.call_soon(self.eventloop.stop) + """ Stop connection. """ + asyncio.run(self.disconnect()) async def send(self, data): """ Add data to send queue. """ diff --git a/pydle/features/ircv3/metadata.py b/pydle/features/ircv3/metadata.py index a194b2b..eafe4cb 100644 --- a/pydle/features/ircv3/metadata.py +++ b/pydle/features/ircv3/metadata.py @@ -1,3 +1,5 @@ +import asyncio + from . import cap VISIBLITY_ALL = '*' @@ -28,7 +30,7 @@ async def get_metadata(self, target): self._metadata_queue.append(target) self._metadata_info[target] = {} - self._pending['metadata'][target] = self.eventloop.create_future() + self._pending['metadata'][target] = asyncio.get_event_loop().create_future() return self._pending['metadata'][target] diff --git a/pydle/features/ircv3/sasl.py b/pydle/features/ircv3/sasl.py index 9afb292..678ae85 100644 --- a/pydle/features/ircv3/sasl.py +++ b/pydle/features/ircv3/sasl.py @@ -1,5 +1,6 @@ ## sasl.py # SASL authentication support. Currently we only support PLAIN authentication. +import asyncio import base64 from functools import partial @@ -47,7 +48,7 @@ async def _sasl_start(self, mechanism): await self.rawmsg('AUTHENTICATE', mechanism) # create a partial, required for our callback to get the kwarg _sasl_partial = partial(self._sasl_abort, timeout=True) - self._sasl_timer = self.eventloop.call_later(self.SASL_TIMEOUT, _sasl_partial) + self._sasl_timer = asyncio.get_event_loop().call_later(self.SASL_TIMEOUT, _sasl_partial) async def _sasl_abort(self, timeout=False): """ Abort SASL authentication. """ @@ -166,7 +167,7 @@ async def on_raw_authenticate(self, message): await self._sasl_respond() else: # Response not done yet. Restart timer. - self._sasl_timer = self.eventloop.call_later(self.SASL_TIMEOUT, self._sasl_abort(timeout=True)) + self._sasl_timer = asyncio.get_event_loop().call_later(self.SASL_TIMEOUT, self._sasl_abort(timeout=True)) on_raw_900 = cap.CapabilityNegotiationSupport._ignored # You are now logged in as... diff --git a/pydle/features/rfc1459/client.py b/pydle/features/rfc1459/client.py index f8a6ba1..e24b0a6 100644 --- a/pydle/features/rfc1459/client.py +++ b/pydle/features/rfc1459/client.py @@ -1,5 +1,6 @@ ## rfc1459.py # Basic RFC1459 stuff. +import asyncio import copy import datetime import ipaddress @@ -398,7 +399,7 @@ async def whois(self, nickname): # We just check if there's a space in the nickname -- if there is, # then we immediately set the future's result to None and don't bother checking. if protocol.ARGUMENT_SEPARATOR.search(nickname) is not None: - result = self.eventloop.create_future() + result = asyncio.get_event_loop().create_future() result.set_result(None) return result @@ -412,7 +413,7 @@ async def whois(self, nickname): } # Create a future for when the WHOIS requests succeeds. - self._pending['whois'][nickname] = self.eventloop.create_future() + self._pending['whois'][nickname] = asyncio.get_event_loop().create_future() return await self._pending['whois'][nickname] @@ -425,7 +426,7 @@ async def whowas(self, nickname): """ # Same treatment as nicknames in whois. if protocol.ARGUMENT_SEPARATOR.search(nickname) is not None: - result = self.eventloop.create_future() + result = asyncio.get_event_loop().create_future() result.set_result(None) return result @@ -434,7 +435,7 @@ async def whowas(self, nickname): self._whowas_info[nickname] = {} # Create a future for when the WHOWAS requests succeeds. - self._pending['whowas'][nickname] = self.eventloop.create_future() + self._pending['whowas'][nickname] = asyncio.get_event_loop().create_future() return await self._pending['whowas'][nickname] diff --git a/pydle/features/tls.py b/pydle/features/tls.py index dabed62..f090779 100644 --- a/pydle/features/tls.py +++ b/pydle/features/tls.py @@ -46,8 +46,7 @@ async def _connect(self, hostname, port, reconnect=False, password=None, encodin tls=tls, tls_verify=tls_verify, tls_certificate_file=self.tls_client_cert, tls_certificate_keyfile=self.tls_client_cert_key, - tls_certificate_password=self.tls_client_cert_password, - eventloop=self.eventloop) + tls_certificate_password=self.tls_client_cert_password) self.encoding = encoding # Connect. diff --git a/pydle/utils/irccat.py b/pydle/utils/irccat.py index 9226197..60d49e6 100644 --- a/pydle/utils/irccat.py +++ b/pydle/utils/irccat.py @@ -56,7 +56,7 @@ async def _main(): def main(): # Setup logging. logging.basicConfig(format='!! %(levelname)s: %(message)s') - asyncio.get_event_loop().run_until_complete(_main()) + asyncio.run(_main()) if __name__ == '__main__': diff --git a/pydle/utils/run.py b/pydle/utils/run.py index 3af6742..18b46c8 100644 --- a/pydle/utils/run.py +++ b/pydle/utils/run.py @@ -6,9 +6,7 @@ def main(): client, connect = _args.client_from_args('pydle', description='pydle IRC library.') - loop = asyncio.get_event_loop() - asyncio.ensure_future(connect(), loop=loop) - loop.run_forever() + asyncio.run(connect()) if __name__ == '__main__': diff --git a/pyproject.toml b/pyproject.toml index 207797e..4e0b9ea 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ keywords = ["irc", "library","python3","compact","flexible"] license = "BSD" [tool.poetry.dependencies] -python = ">=3.6;<3.10" +python = ">=3.7;<3.11" [tool.poetry.dependencies.pure-sasl] version = "^0.6.2" diff --git a/tests/mocks.py b/tests/mocks.py index 329e1c2..3e71090 100644 --- a/tests/mocks.py +++ b/tests/mocks.py @@ -66,7 +66,6 @@ async def _connect(self, hostname, port, *args, **kwargs): port, mock_client=self, mock_server=self._mock_server, - eventloop=self.eventloop, ) await self.connection.connect() await self.on_connect() From e09f4761181d3a5eb7e921d3eea3229802fb5b15 Mon Sep 17 00:00:00 2001 From: Felix Yan Date: Sun, 23 Oct 2022 23:44:23 +0300 Subject: [PATCH 2/2] Use create_task() instead of run() --- pydle/connection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pydle/connection.py b/pydle/connection.py index 3b87937..592514a 100644 --- a/pydle/connection.py +++ b/pydle/connection.py @@ -102,7 +102,7 @@ def connected(self): def stop(self): """ Stop connection. """ - asyncio.run(self.disconnect()) + asyncio.create_task(self.disconnect()) async def send(self, data): """ Add data to send queue. """