From 553e60c8380bf6dc9dee97d7f868a76a2d4eaaf8 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 26 Sep 2015 07:38:31 -0400 Subject: [PATCH 1/6] Initial draft of an asyncio-backed implementation. --- irc/client.py | 82 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/irc/client.py b/irc/client.py index e01ca56..b5323e2 100644 --- a/irc/client.py +++ b/irc/client.py @@ -405,6 +405,7 @@ def _remove_connection(self, connection): self.connections.remove(connection) self._on_disconnect(connection.socket) + _cmd_pat = "^(@(?P[^ ]*) )?(:(?P[^ ]+) +)?(?P[^ ]+)( *(?P .+))?" _rfc_1459_command_regexp = re.compile(_cmd_pat) @@ -1376,3 +1377,84 @@ def from_group(cls, group): def _ping_ponger(connection, event): "A global handler for the 'ping' event" connection.pong(event.target) + + +class AsyncIOReactor(Reactor, asyncio.BaseEventLoop): + def schedule_command(self, command): + self.call_at(command.timestamp(), command.function) + + +class Protocol(asyncio.Protocol, ServerConnection): + password = None + real_server_name = "" + + def __init__(self, nickname, **kwargs): + self.nickname = nickname + vars(self).update(kwargs) + self.features = FeatureSet() + + @NonDataProperty + def user_name(self): + return self.nickname + + @NonDataProperty + def real_name(self): + return self.nickname + + @NonDataProperty + def real_nickname(self): + return self.nickname + + def connection_made(self, transport): + self.transport = transport + self.buffer = self.buffer_class() + # Log on... + if self.password: + self.pass_(self.password) + self.nick(self.nickname) + self.user(self.user_name, self.real_name) + + def data_received(self, data): + self.buffer.feed(new_data) + + # process each non-empty line after logging all lines + for line in self.buffer: + log.debug("FROM SERVER: %s", line) + if not line: continue + self._process_line(line) + + def _handle_event(self, event): + """ + Dispatch events to on_ method, if present. + """ + log.debug("handling: %s", event.type) + + do_nothing = lambda c, e: None + method = getattr(self, "on_" + event.type, do_nothing) + method(connection, event) + + def send_raw(self, string): + """ + Send raw string to the server. + + The string will be padded with appropriate CR LF. + """ + self.transport.write(self._prep_message(string)) + log.debug("TO SERVER: %s", string) + + +class AsyncIRCClient: + + def __init__(self): + self.reactor = AsyncIOReactor() + + def connect(self, protocol, *args, **kwargs): + proto_factory = lambda: protocol + self.connection = self.reactor.create_connection(proto_factory, *args, **kwargs) + transport, protocol = self.reactor.run_until_complete(self.connection) + self.protocol = protocol + self.transport = transport + + def start(self): + """Start the IRC client.""" + self.reactor.process_forever() From badc23722f6fe6df587790bf73282bebf3c8353f Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 26 Sep 2015 21:55:41 -0400 Subject: [PATCH 2/6] Create new irccat.py to demonstrate asyncio-based implementation. --- irc/client.py | 17 +++++------- scripts/irccat-aio.py | 61 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 10 deletions(-) create mode 100644 scripts/irccat-aio.py diff --git a/irc/client.py b/irc/client.py index b5323e2..57c0a60 100644 --- a/irc/client.py +++ b/irc/client.py @@ -62,10 +62,12 @@ import functools import itertools import contextlib +import asyncio import six from jaraco.itertools import always_iterable from jaraco.functools import Throttler +from jaraco.classes.properties import NonDataProperty try: import pkg_resources @@ -1379,11 +1381,6 @@ def _ping_ponger(connection, event): connection.pong(event.target) -class AsyncIOReactor(Reactor, asyncio.BaseEventLoop): - def schedule_command(self, command): - self.call_at(command.timestamp(), command.function) - - class Protocol(asyncio.Protocol, ServerConnection): password = None real_server_name = "" @@ -1391,7 +1388,7 @@ class Protocol(asyncio.Protocol, ServerConnection): def __init__(self, nickname, **kwargs): self.nickname = nickname vars(self).update(kwargs) - self.features = FeatureSet() + self.features = features.FeatureSet() @NonDataProperty def user_name(self): @@ -1415,7 +1412,7 @@ def connection_made(self, transport): self.user(self.user_name, self.real_name) def data_received(self, data): - self.buffer.feed(new_data) + self.buffer.feed(data) # process each non-empty line after logging all lines for line in self.buffer: @@ -1429,9 +1426,9 @@ def _handle_event(self, event): """ log.debug("handling: %s", event.type) - do_nothing = lambda c, e: None + do_nothing = lambda event: None method = getattr(self, "on_" + event.type, do_nothing) - method(connection, event) + method(event) def send_raw(self, string): """ @@ -1457,4 +1454,4 @@ def connect(self, protocol, *args, **kwargs): def start(self): """Start the IRC client.""" - self.reactor.process_forever() + self.reactor.run_forever() diff --git a/scripts/irccat-aio.py b/scripts/irccat-aio.py new file mode 100644 index 0000000..ff60b8b --- /dev/null +++ b/scripts/irccat-aio.py @@ -0,0 +1,61 @@ +#! /usr/bin/env python +# +# Example program using irc.client. +# +# This program is free without restrictions; do anything you like with +# it. +# +# Joel Rosdahl + +import sys +import argparse +import itertools +import functools + +import irc.client +import jaraco.logging + +def get_lines(): + while True: + yield sys.stdin.readline().strip() + +def get_args(): + parser = argparse.ArgumentParser() + parser.add_argument('server') + parser.add_argument('nickname') + parser.add_argument('target', help="a nickname or channel") + parser.add_argument('-p', '--port', default=6667, type=int) + jaraco.logging.add_arguments(parser) + return parser.parse_args() + +class Protocol(irc.client.Protocol): + def on_welcome(self, event): + assert irc.client.is_channel(self.target) + self.join(self.target) + + def on_join(self, event): + for line in itertools.takewhile(bool, get_lines()): + print(line) + self.privmsg(self.target, line) + self.quit("Using irc.client.py") + +def main(): + global target + + args = get_args() + jaraco.logging.setup(args) + + proto_factory = functools.partial(Protocol, + nickname=args.nickname, + target=args.target, + ) + import asyncio + reactor = asyncio.SelectorEventLoop() + connection = reactor.create_connection(proto_factory, args.server, args.port) + + transport, protocol = reactor.run_until_complete(connection) + + reactor.run_forever() + +if __name__ == '__main__': + main() From 7cf8c2d01db72cf56bd2db8e6625003276072b9d Mon Sep 17 00:00:00 2001 From: Matt Broach Date: Mon, 16 Apr 2018 12:46:32 -0400 Subject: [PATCH 3/6] added docstrings, test --- .gitignore | 1 + README.rst | 25 +++++- irc/aio_client.py | 166 +++++++++++++++++++++++++++++++++-- irc/client.py | 2 - irc/tests/test_aio_client.py | 28 ++++++ pytest.ini | 2 +- scripts/irccat-aio.py | 15 ++-- scripts/irccat2-aio.py | 16 ++-- 8 files changed, 230 insertions(+), 25 deletions(-) create mode 100644 irc/tests/test_aio_client.py diff --git a/.gitignore b/.gitignore index 7029c28..cce76a5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ __pycache__ .cache .eggs +.pytest_cache/ diff --git a/README.rst b/README.rst index 65c6572..b787d75 100644 --- a/README.rst +++ b/README.rst @@ -58,9 +58,9 @@ The main features of the IRC client framework are: connection object. * Messages from an IRC server triggers events, which can be caught by event handlers. -* Reading from and writing to IRC server sockets is normally done - by an internal ``select()`` loop, but the ``select()`` may be done - by an external main loop. +* Multiple opations for reading from and writing to an IRC server: + you can use sockets in an internal ``select()`` loop OR use + python3's asyncio event loop * Functions can be registered to execute at specified times by the event-loop. * Decodes CTCP tagging correctly (hopefully); I haven't seen any @@ -76,6 +76,7 @@ Current limitations: * Data is not written asynchronously to the server (and DCC peers), i.e. the ``write()`` may block if the TCP buffers are stuffed. * Like most projects, documentation is lacking ... +* DCC is not currently implemented in the asyncio-based versin Unfortunately, this library isn't as well-documented as I would like it to be. I think the best way to get started is to read and @@ -90,6 +91,13 @@ The following modules might be of interest: docstrings to get a grip of what it does. Use it at your own risk and read the source, Luke! +* ``irc.aio_client`` + + All the functionality of the above library, but utilizing + Python 3's native asyncio library for the core event loop. + Interface/API is otherwise functionally identical to the classes + in ``irc.client`` + * ``irc.bot`` An IRC bot implementation. @@ -116,6 +124,17 @@ Example scripts in the scripts directory: The same as above, but using the ``SimpleIRCClient`` class. +* ``aio_irccat`` + + Same as above, but uses the asyncio-based event loop in + ``AioReactor`` instead of the ``select()`` based ``Reactor``. + + +* ``aio_irccat2`` + + Same as above, but using the ``AioSimpleIRCClient`` class + + * ``servermap`` Another simple example. ``servermap`` connects to an IRC server, diff --git a/irc/aio_client.py b/irc/aio_client.py index 4a37d18..d6b6b4e 100644 --- a/irc/aio_client.py +++ b/irc/aio_client.py @@ -1,16 +1,69 @@ +# -*- coding: utf-8 -*- + +""" +Internet Relay Chat (IRC) asyncio-based protocol client library. + +This an extension of the IRC client framework in irc.client that replaces +original select-based event loop with one that uses Python 3's native +asyncio event loop. + +This implementation shares many of the features of its select-based +cousin, including: + + * Abstraction of the IRC protocol. + * Handles multiple simultaneous IRC server connections. + * Handles server PONGing transparently. + * Messages to the IRC server are done by calling methods on an IRC + connection object. + * Messages from an IRC server triggers events, which can be caught + by event handlers. + * Reading from and writing to IRC server sockets are normally done + by an internal select() loop, but the select()ing may be done by + an external main loop. + * Functions can be registered to execute at specified times by the + event-loop. + * Decodes CTCP tagging correctly (hopefully); I haven't seen any + other IRC client implementation that handles the CTCP + specification subtleties. + * A kind of simple, single-server, object-oriented IRC client class + that dispatches events to instance methods is included. + +Current limitations: + * DCC chat has not yet been implemented + * DCC file transfers are not suppored + * RFCs 2810, 2811, 2812, and 2813 have not been considered. + +Notes: + * connection.quit() only sends QUIT to the server. + * ERROR from the server triggers the error event and the disconnect event. + * dropping of the connection triggers the disconnect event. +""" + import asyncio import threading import logging -from .client import ServerConnection, ServerConnectionError, ServerNotConnectedError, Reactor,\ - SimpleIRCClient, Event, _ping_ponger +from .client import ServerConnection, ServerNotConnectedError, Reactor,\ + SimpleIRCClient, Event, _ping_ponger from . import functools as irc_functools log = logging.getLogger(__name__) class IrcProtocol(asyncio.Protocol): + """ + simple asyncio-based Protocol for handling connections to + the IRC Server. + + Note: In order to maintain a consistent interface with + `irc.ServerConnection`, handling of incoming and outgoing data + is mostly handling by the `AioConnection` object, using the same + callback methods as on an `irc.ServerConnection` instance. + """ def __init__(self, connection, loop): + """ + Constructor for IrcProtocol objects. + """ self.connection = connection self.loop = loop @@ -19,12 +72,51 @@ def data_received(self, data): class AioConnection(ServerConnection): + """ + An IRC server connection. + + AioConnection objects are instantiated by calling the server + method on a AioReactor object. + + Note: because AioConnection inherits from + irc.client.ServerConnection, it has all the convenience + methods on ServerConnection for handling outgoing data, + including (but not limited to): + + * join(channel, key="") + * part(channel, message="") + * privmsg(target, text) + * privmsg_many(targets, text) + * quit(message="") + + And many more. See the documentation on + irc.client.ServerConnection for a full list of convience + functions available. + """ protocol_class = IrcProtocol @irc_functools.save_method_args def connect( - self, server, port, nickname, password=None, username=None, ircname=None + self, server, port, nickname, + password=None, username=None, ircname=None ): + """Connect/reconnect to a server. + + Arguments: + + * server - Server name + * port - Port number + * nickname - The nickname + * password - Password (if any) + * username - The username + * ircname - The IRC name ("realname") + + This function can be called to reconnect a closed connection. + + Returns the AioProtocol instance (used for handling incoming + IRC data) and the transport instance (used for handling + outgoing data). + """ if self.connected: self.disconnect("Changing servers") @@ -41,8 +133,12 @@ def connect( self.password = password connection = self.reactor.loop.create_connection( - lambda: IrcProtocol(self, self.reactor.loop), self.server, self.port, + lambda: self.protocol_class(self, self.reactor.loop), + self.server, + self.port, ) + print('HELLO') + print(connection) transport, protocol = self.reactor.loop.run_until_complete(connection) self.transport = transport @@ -60,19 +156,22 @@ def connect( def process_data(self, new_data): """ - handles incoming data from the Protocol + handles incoming data from the `IrcProtocol` connection. + Main data processing/routing is handled by the _process_line + method, inherited from `ServerConnection` """ self.buffer.feed(new_data) # process each non-empty line after logging all lines for line in self.buffer: + print(line) log.debug("FROM SERVER: %s", line) if not line: continue self._process_line(line) def send_raw(self, string): - """Send raw string to the server. + """Send raw string to the server, via the asyncio transport. The string will be padded with appropriate CR LF. """ @@ -101,12 +200,47 @@ def disconnect(self, message=""): class AioReactor(Reactor): - connection_class = AioConnection + """ + Processes message from on or more asyncio-based IRC server connections. + + This class inherits most of its functionality from irc.client.Reactor, + and mainly replaces the select-based loop from that reactor with + an asyncio event loop. + + Note: if not event loop is passed into AioReactor, it will try to get + the current loop. However, if there is a specific event loop + you'd like to use, you can pass that loop by using the `loop` kwarg + in the AioReactor's constructor: + + async def my_repeating_message(connection): + while True: + connection.privmsg('#my-irc-channel', 'hello!') + await asyncio.sleep(60) + + my_loop = asyncio.get_event_loop() + + client = AioReactor(loop=my_loop) + server = client.server() + + # use `my_loop` to initialize the repeating message on the same + # loop as the AioRector + asyncio.ensure_future(my_repeating_message(server), loop=my_loop) + + # connect to the server, start the loop + server.connect('my.irc.server', 6667, 'my_irc_nick') + client.process_forever() + + The above code will connect to the IRC server my.irc.server + and echo the 'hello!' message to 'my-irc-channel' every 60 seconds. + """ + connection_class = AioConnection def __do_nothing(*args, **kwargs): pass - def __init__(self, on_connect=__do_nothing, on_disconnect=__do_nothing, loop=None): + def __init__( + self, on_connect=__do_nothing, on_disconnect=__do_nothing, loop=None + ): self._on_connect = on_connect self._on_disconnect = on_disconnect @@ -120,8 +254,24 @@ def __init__(self, on_connect=__do_nothing, on_disconnect=__do_nothing, loop=Non self.add_global_handler("ping", _ping_ponger, -42) def process_forever(self): + """Run an infinite loop, processing data from connections. + + Rather than call `process_once` repeatedly, like + irc.client.reactor, incoming data is handled via the + asycio.Protocol class -- by default, the IrcProtocol + class definied above. + """ self.loop.run_forever() class AioSimpleIRCClient(SimpleIRCClient): + """A simple single-server IRC client class. + + This class is functionally equivalent irc.client.SimpleIRCClient, + the only difference being the using of the AioReactor for + asyncio-based loops. + + For more information on AioSimpleIRCClient, see the documentation + on irc.client.SimpleIRCClient + """ reactor_class = AioReactor diff --git a/irc/client.py b/irc/client.py index dbc6359..086f96c 100644 --- a/irc/client.py +++ b/irc/client.py @@ -62,12 +62,10 @@ import functools import itertools import contextlib -import asyncio import six from jaraco.itertools import always_iterable, infinite_call from jaraco.functools import Throttler -from jaraco.classes.properties import NonDataProperty from jaraco.stream import buffer from more_itertools.recipes import consume diff --git a/irc/tests/test_aio_client.py b/irc/tests/test_aio_client.py new file mode 100644 index 0000000..c5aef51 --- /dev/null +++ b/irc/tests/test_aio_client.py @@ -0,0 +1,28 @@ +from unittest.mock import patch, MagicMock +import asyncio + +from irc import aio_client + + +@patch('asyncio.base_events.BaseEventLoop.create_connection') +def test_privmsg_sends_msg(create_connection_mock): + # create dummy transport, protocol + fake_connection = asyncio.Future() + + mock_transport = MagicMock() + mock_protocol = MagicMock() + + fake_connection.set_result((mock_transport, mock_protocol)) + create_connection_mock.return_value = fake_connection + + # connect to dummy server + loop = asyncio.get_event_loop() + server = aio_client.AioReactor(loop=loop).server() + server.connect('foo', 6667, 'my_irc_nick') + server.privmsg('#best-channel', 'You are great') + + mock_transport.write.assert_called_with( + b'PRIVMSG #best-channel :You are great\r\n' + ) + + loop.close() diff --git a/pytest.ini b/pytest.ini index 0ba22c3..25da7cf 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,4 +1,4 @@ [pytest] -norecursedirs=dist build .tox .eggs +norecursedirs=dist build .tox .eggs env addopts=--doctest-modules --flake8 doctest_optionflags=ALLOW_UNICODE ELLIPSIS diff --git a/scripts/irccat-aio.py b/scripts/irccat-aio.py index 4e8dbf1..5d61219 100644 --- a/scripts/irccat-aio.py +++ b/scripts/irccat-aio.py @@ -4,13 +4,10 @@ # # This program is free without restrictions; do anything you like with # it. -# -# Joel Rosdahl import sys import argparse import itertools -import functools import asyncio import irc.aio_client @@ -27,7 +24,9 @@ def on_connect(connection, event): def on_join(connection, event): - connection.read_loop = asyncio.ensure_future(main_loop(connection), loop=connection.reactor.loop) + connection.read_loop = asyncio.ensure_future( + main_loop(connection), loop=connection.reactor.loop + ) def get_lines(): @@ -39,6 +38,8 @@ async def main_loop(connection): for line in itertools.takewhile(bool, get_lines()): print(line) connection.privmsg(target, line) + + # Allow pause in the stdin loop to not block the asyncio event loop asyncio.sleep(0) connection.quit("Using irc.client.py") @@ -69,7 +70,9 @@ def main(): reactor = irc.aio_client.AioReactor(loop=loop) try: - c = reactor.server().connect(args.server, args.port, args.nickname, password=args.password) + c = reactor.server().connect( + args.server, args.port, args.nickname, password=args.password + ) except irc.client.ServerConnectionError: print(sys.exc_info()[1]) raise SystemExit(1) @@ -84,7 +87,7 @@ def main(): try: reactor.process_forever() - finally: + finally: loop.close() diff --git a/scripts/irccat2-aio.py b/scripts/irccat2-aio.py index aaeaa57..2853407 100644 --- a/scripts/irccat2-aio.py +++ b/scripts/irccat2-aio.py @@ -4,8 +4,6 @@ # # This program is free without restrictions; do anything you like with # it. -# -# Joel Rosdahl import sys import asyncio import argparse @@ -24,10 +22,14 @@ def on_welcome(self, connection, event): if irc.client.is_channel(self.target): connection.join(self.target) else: - self.future = asyncio.ensure_future(self.send_it(), loop=connection.reactor.loop) + self.future = asyncio.ensure_future( + self.send_it(), loop=connection.reactor.loop + ) def on_join(self, connection, event): - self.future = asyncio.ensure_future(self.send_it(), loop=connection.reactor.loop) + self.future = asyncio.ensure_future( + self.send_it(), loop=connection.reactor.loop + ) def on_disconnect(self, connection, event): self.future.cancel() @@ -39,6 +41,8 @@ async def send_it(self): if not line: break self.connection.privmsg(self.target, line) + + # Allow pause in the stdin loop to not block asyncio loop asyncio.sleep(0) self.connection.quit("Using irc.client.py") @@ -62,7 +66,9 @@ def main(): c = AioIRCCat(target) try: - c.connect(args.server, args.port, args.nickname, password=args.password) + c.connect( + args.server, args.port, args.nickname, password=args.password + ) except irc.client.ServerConnectionError as x: print(x) sys.exit(1) From 1368f4eac49dbc9b4ba03f74909818ddfd3f922f Mon Sep 17 00:00:00 2001 From: Matt Broach Date: Mon, 16 Apr 2018 13:21:14 -0400 Subject: [PATCH 4/6] removing virtualenv from pytest.ini --- pytest.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytest.ini b/pytest.ini index 25da7cf..0ba22c3 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,4 +1,4 @@ [pytest] -norecursedirs=dist build .tox .eggs env +norecursedirs=dist build .tox .eggs addopts=--doctest-modules --flake8 doctest_optionflags=ALLOW_UNICODE ELLIPSIS From b585f5fccd79017c9c62c09bce6f49bf8b0cf0b9 Mon Sep 17 00:00:00 2001 From: Matt Broach Date: Mon, 16 Apr 2018 15:53:04 -0400 Subject: [PATCH 5/6] update tests to ignore files in python < 3.5 --- README.rst | 2 +- conftest.py | 10 ++++++++++ irc/{aio_client.py => client_aio.py} | 0 irc/tests/{test_aio_client.py => test_client_aio.py} | 4 ++-- scripts/irccat-aio.py | 6 +++--- scripts/irccat2-aio.py | 4 ++-- 6 files changed, 18 insertions(+), 8 deletions(-) create mode 100644 conftest.py rename irc/{aio_client.py => client_aio.py} (100%) rename irc/tests/{test_aio_client.py => test_client_aio.py} (89%) diff --git a/README.rst b/README.rst index b787d75..8c9b86a 100644 --- a/README.rst +++ b/README.rst @@ -91,7 +91,7 @@ The following modules might be of interest: docstrings to get a grip of what it does. Use it at your own risk and read the source, Luke! -* ``irc.aio_client`` +* ``irc.client_aio`` All the functionality of the above library, but utilizing Python 3's native asyncio library for the core event loop. diff --git a/conftest.py b/conftest.py new file mode 100644 index 0000000..56677ef --- /dev/null +++ b/conftest.py @@ -0,0 +1,10 @@ +import sys +import fnmatch +import os + +collect_ignore = ["setup.py"] + +if sys.version_info < (3, 5): + for root, dirnames, filenames in os.walk('.'): + for filename in fnmatch.filter(filenames, '*aio.py'): + collect_ignore.append(os.path.join(root, filename)) diff --git a/irc/aio_client.py b/irc/client_aio.py similarity index 100% rename from irc/aio_client.py rename to irc/client_aio.py diff --git a/irc/tests/test_aio_client.py b/irc/tests/test_client_aio.py similarity index 89% rename from irc/tests/test_aio_client.py rename to irc/tests/test_client_aio.py index c5aef51..3ef08df 100644 --- a/irc/tests/test_aio_client.py +++ b/irc/tests/test_client_aio.py @@ -1,7 +1,7 @@ from unittest.mock import patch, MagicMock import asyncio -from irc import aio_client +from irc import client_aio @patch('asyncio.base_events.BaseEventLoop.create_connection') @@ -17,7 +17,7 @@ def test_privmsg_sends_msg(create_connection_mock): # connect to dummy server loop = asyncio.get_event_loop() - server = aio_client.AioReactor(loop=loop).server() + server = client_aio.AioReactor(loop=loop).server() server.connect('foo', 6667, 'my_irc_nick') server.privmsg('#best-channel', 'You are great') diff --git a/scripts/irccat-aio.py b/scripts/irccat-aio.py index 5d61219..0268bf1 100644 --- a/scripts/irccat-aio.py +++ b/scripts/irccat-aio.py @@ -10,7 +10,7 @@ import itertools import asyncio -import irc.aio_client +import irc.client_aio import irc.client import jaraco.logging @@ -41,7 +41,7 @@ async def main_loop(connection): # Allow pause in the stdin loop to not block the asyncio event loop asyncio.sleep(0) - connection.quit("Using irc.client.py") + connection.quit("Using irc.client_aio.py") def on_disconnect(connection, event): @@ -67,7 +67,7 @@ def main(): target = args.target loop = asyncio.get_event_loop() - reactor = irc.aio_client.AioReactor(loop=loop) + reactor = irc.client_aio.AioReactor(loop=loop) try: c = reactor.server().connect( diff --git a/scripts/irccat2-aio.py b/scripts/irccat2-aio.py index 2853407..a63e234 100644 --- a/scripts/irccat2-aio.py +++ b/scripts/irccat2-aio.py @@ -9,11 +9,11 @@ import argparse import irc.client -import irc.aio_client +import irc.client_aio import jaraco.logging -class AioIRCCat(irc.aio_client.AioSimpleIRCClient): +class AioIRCCat(irc.client_aio.AioSimpleIRCClient): def __init__(self, target): irc.client.SimpleIRCClient.__init__(self) self.target = target From 48b35d66597b80b30c11c0f033520c4268f9e0d5 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 20 May 2018 20:02:48 -0400 Subject: [PATCH 6/6] Update changelog. Fixes #135. --- CHANGES.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.rst b/CHANGES.rst index 4f5203b..9826bba 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -2,6 +2,7 @@ ==== * #140: Methods now use 'connection' and 'event' for parameter names. +# #135 via #144: Added AsyncIO implementation. 16.2.1 ======