From da00dd8bbbd0c4c5fcbc5e1abfbd99a88f0edb0f Mon Sep 17 00:00:00 2001 From: orlnub123 <30984274+orlnub123@users.noreply.github.com> Date: Thu, 19 Oct 2017 21:03:15 +0300 Subject: [PATCH] 2.0.0 --- README.rst | 122 ++++++++++----------------------- cleverbot/__init__.py | 3 +- cleverbot/_async.py | 125 ---------------------------------- cleverbot/abc.py | 38 +++++++++++ cleverbot/async_/__init__.py | 1 + cleverbot/async_/cleverbot.py | 94 +++++++++++++++++++++++++ cleverbot/cleverbot.py | 103 ++++++---------------------- cleverbot/errors.py | 4 +- setup.py | 5 +- 9 files changed, 193 insertions(+), 302 deletions(-) delete mode 100644 cleverbot/_async.py create mode 100644 cleverbot/abc.py create mode 100644 cleverbot/async_/__init__.py create mode 100644 cleverbot/async_/cleverbot.py diff --git a/README.rst b/README.rst index cda24a4..3a56eae 100644 --- a/README.rst +++ b/README.rst @@ -27,41 +27,30 @@ Example Installing ---------- -Install it from PyPI with pip: +Install it normally from PyPI with pip: :: pip install cleverbot.py -Or install it from GitHub using git: - -:: - - git clone https://github.com/orlnub123/cleverbot.py - cd cleverbot.py - python setup.py install - -If you don't have pip or git you can also download the source and run ``python -setup.py install`` on it. - -To install Cleverbot with asynchronous support you'll have to use pip and be on -Python 3.4.2+. +Or install it with the asynchronous dependencies (Python 3.4.2+ only): :: pip install cleverbot.py[async] -This is not required if you already have aiohttp 1.0.0 or later. - **Requirements:** - Python 3.2+ or 2.6+ -- `A Cleverbot API key `_ +- `A Cleverbot API key `_ **Dependencies:** - requests 1.0.0+ -- aiohttp 1.0.0+ (Optional, for asynchronous support) + ++ **Asynchronous:** + + - aiohttp 1.0.0+ Usage ----- @@ -72,8 +61,6 @@ First import the package: import cleverbot --------------- - Then initialize Cleverbot with your API key and optionally a cleverbot state and or timeout: @@ -81,14 +68,20 @@ and or timeout: cb = cleverbot.Cleverbot('YOUR_API_KEY', cs='76nxdxIJ02AAA', timeout=60) -The cleverbot state is the encoded state of the conversation so far and -includes the whole conversation history up to that point. +The cleverbot state is the encoded state of the conversation that you get from +talking to Cleverbot and includes the whole conversation history. + +If you have the asynchronous dependencies and want to use Cleverbot +asynchronously import ``cleverbot.async_`` and initialize Cleverbot from +``cleverbot.async_.Cleverbot`` instead. The only differences are that ``say`` +is a coroutine and that you can pass an event loop to Cleverbot with a ``loop`` +keyword argument. -------------- You can now start talking to Cleverbot. -Get the reply from the input: +Get the reply from the request: .. code:: py @@ -101,19 +94,10 @@ Or alternatively get it later: cb.say("Hello") reply = cb.output -If you want to talk to Cleverbot asynchronously use ``asay`` instead: - -.. code:: py - - await cb.asay("Hello") - -``asay`` only works if you're on Python 3.4.2+ and have aiohttp installed. -Experience with asyncio is recommended as you'll have to run it in an event -loop. - -A big benefit of using ``asay`` is that it allows multiple requests to be sent -at once instead of waiting for the previous request to return a response which -can take significantly longer. +You can also pass in keyword arguments such as ``cs`` to change the +conversation, or ``vtext`` to change the current conversation's history. Read +the "Parameters" section of `the official Cleverbot API docs +`_ for more information. -------------- @@ -127,7 +111,8 @@ As an example: ``cleverbot.errors.APIError: Missing or invalid API key or POST request, please visit www.cleverbot.com/api`` -You can get the error message and additionally the HTTP status like so: +You can get the error message and additionally the HTTP status from the error +like so: .. code:: py @@ -136,76 +121,39 @@ You can get the error message and additionally the HTTP status like so: except cleverbot.APIError as error: print(error.error, error.status) -This is also applicable to ``Timeout`` where you can get the defined timeout +This is similar for ``Timeout`` where you can get the defined timeout value with ``error.timeout``. -Also, all Cleverbot errors subclass ``CleverbotError`` so you can use it to -catch everything Cleverbot related. +Additionally, all Cleverbot errors subclass ``CleverbotError`` so you can use +it to catch every Cleverbot related error. -------------- -Print out all of the data Cleverbot gained from the previous conversation: - -.. code:: py - - print(cb.data) - -Take note of the ``cs`` key as we'll use it to save the conversation in the -next section. - -To access the data you can either get them from an attribute or directly get -them from ``cb.data``: +To access the data gained from the conversations you can either get them from +an attribute as shown previously or directly get them from ``cb.data``: .. code:: py - cb.output == cb.data['output'] + cb.conversation_id == cb.data['conversation_id'] However modifying the data with an attribute is only applicable to the -cleverbot state. - -To get a list of all of the keys' descriptions either take a look at the -``_query`` method's docstring in cleverbot.py or go to the 'JSON Reply' section -in `the official Cleverbot API docs `_. - --------------- +cleverbot state by using the ``cs`` attribute. -Save the conversation in preparation for a reset: +For a list of all of the data's items' descriptions go to the "JSON Reply" +section in `the official Cleverbot API docs +`_. -.. code:: py - - cs = cb.cs - -Reset Cleverbot, deleting all of the data it's gained from the previous -conversations: +To reset the data you can simply do the following: .. code:: py cb.reset() -Note that if you try to get the cleverbot state now you'll get an error: - -``AttributeError: 'Cleverbot' object has no attribute 'cs'`` - -Now start right where you left off by setting the cleverbot state you saved -earlier: - -.. code:: py - - cb.cs = cs - -Or by setting it when creating a new Cleverbot instance: - -.. code:: py - - cb = cleverbot.Cleverbot('YOUR_API_KEY', cs=cs) - -------------- -When you're all done, close Cleverbot's connection to the API: +When you're done with the current instance of Cleverbot, close Cleverbot's +connection to the API: .. code:: py cb.close() - -This should only be done when you're not going to use the current instance of -Cleverbot anymore. diff --git a/cleverbot/__init__.py b/cleverbot/__init__.py index 756207f..3c65f6e 100644 --- a/cleverbot/__init__.py +++ b/cleverbot/__init__.py @@ -1,5 +1,4 @@ -__all__ = ['Cleverbot', 'CleverbotError', 'APIError', 'DecodeError', 'Timeout'] -__version__ = '1.3.0' +__version__ = '2.0.0' from .cleverbot import Cleverbot from .errors import CleverbotError, APIError, DecodeError, Timeout diff --git a/cleverbot/_async.py b/cleverbot/_async.py deleted file mode 100644 index 37e0977..0000000 --- a/cleverbot/_async.py +++ /dev/null @@ -1,125 +0,0 @@ -import asyncio - -import aiohttp -import requests - -from . import __version__ -from .errors import APIError, DecodeError, Timeout - - -def __init__(self, key, **kwargs): - """Initialize Cleverbot with the given arguments. - - Arguments: - key: The key argument is always required. It is your API key. - cs: The cs argument stands for "cleverbot state". It is the encoded - state of the conversation so far and includes the whole - conversation history up to that point. - timeout: How many seconds to wait for the API to send data before - giving up and raising an error. - loop: The event loop used for asay. - **kwargs: Keyword arguments to pass into requests.Session.get and - aiohttp.ClientSession.get - """ - self.session = requests.Session() - loop = kwargs.pop('loop', asyncio.get_event_loop()) - self.asession = aiohttp.ClientSession(loop=loop) - self.key = key - self.data = {} - if 'cs' in kwargs: - self.data['cs'] = kwargs.pop('cs') - self.timeout = kwargs.pop('timeout', None) - self.kwargs = kwargs - - -@asyncio.coroutine -def asay(self, text, **vtext): - """Talk to Cleverbot asynchronously. - - Arguments: - text: The text argument is what you want to say to Cleverbot, such as - "hello". - **vtext: If you wish to override the conversation history, you can pass - in keyword arguments like vtext2 to override the last thing the bot - said, vtext3 for the previous thing the user said, and so on. - - Returns: - Cleverbot's reply. - - Raises: - APIError: A Cleverbot API error occurred. - 401: Unauthorised due to invalid API key. - 404: API not found. - 413: Request too large if you send a request over 16KB. - 502 or 504: Unable to get reply from API server, please contact us. - 503: Too many requests from a single IP address or API key. - DecodeError: An error occurred while reading the reply. - Timeout: The request timed out. - """ - params = { - 'key': self.key, - 'input': text, - 'wrapper': 'cleverbot.py' - } - try: - params['cs'] = self.data['cs'] - except KeyError: - pass - if vtext: - params.update(vtext) - return (yield from self._aquery(params)) - - -def close(self): - """Close the connections to the API.""" - self.session.close() - self.asession.close() - - -@asyncio.coroutine -def _aquery(self, params): - """Get Cleverbot's reply and store the data in a dictionary. - - Keys: - cs: State of the conversation so far, which contains an encoded copy of - the conversation id and history. - interaction_count: How many pairs of bot/user interactions have - occurred so far. - input: The entire user input, with any spaces trimmed off both ends. - output: Cleverbot's reply. - conversation_id: Identifier for this conversation between user and bot. - errorline: Any error information from Cleverbot, this is different from - general the general errors described below. - time_taken: The number of milliseconds the bot took to respond. - time_elapsed: Approximate number of seconds since conversation started. - interaction_1 to interaction_50: Record of the previous interactions. - The interaction variables come in pairs. For example interaction_1 - contains the last thing the user said, and interaction_1_other is - the bot's reply. interaction_2 was the user's previous input and so - on. So to read the whole conversation in order, you have to display - the interaction variables in reverse order, eg interaction_5, - interaction_5_other, interaction_4, interaction_4_other, etc. Note - that if an interaction didn't occur, the interaction_other will not - be defined. - """ - headers = { - 'User-Agent': 'cleverbot.py/' + __version__ + ' ' - '(+https://github.com/orlnub123/cleverbot.py)' - } - try: - reply = yield from self.asession.get( - self.url, params=params, headers=headers, timeout=self.timeout, - **self.kwargs) - except asyncio.TimeoutError: - raise Timeout(self.timeout) - else: - try: - data = yield from reply.json() - except ValueError as error: - raise DecodeError(error) - else: - if reply.status == 200: - self.data = data - return data['output'] - else: - raise APIError(data['error'], data['status']) diff --git a/cleverbot/abc.py b/cleverbot/abc.py new file mode 100644 index 0000000..54dc8ed --- /dev/null +++ b/cleverbot/abc.py @@ -0,0 +1,38 @@ +from __future__ import absolute_import + +import abc + + +class CleverbotBase: + """Base class for Cleverbot.""" + + url = 'https://www.cleverbot.com/getreply' + + def __getattribute__(self, attr): + """Allow access to the stored data through attributes.""" + try: + return super(CleverbotBase, self).__getattribute__(attr) + except AttributeError as error: + try: + return super( + CleverbotBase, self).__getattribute__('data')[attr] + except KeyError: + raise error + + def __setattr__(self, attr, value): + """Allow modifying the cleverbot state with an attribute.""" + if attr == 'cs': + self.data['cs'] = value + else: + super(CleverbotBase, self).__setattr__(attr, value) + + @abc.abstractmethod + def say(self): + pass + + def reset(self): + """Reset all of Cleverbot's stored data.""" + self.data = {} + + +CleverbotBase = abc.ABCMeta('CleverbotBase', (), dict(CleverbotBase.__dict__)) diff --git a/cleverbot/async_/__init__.py b/cleverbot/async_/__init__.py new file mode 100644 index 0000000..c5587f0 --- /dev/null +++ b/cleverbot/async_/__init__.py @@ -0,0 +1 @@ +from .cleverbot import Cleverbot diff --git a/cleverbot/async_/cleverbot.py b/cleverbot/async_/cleverbot.py new file mode 100644 index 0000000..ccea117 --- /dev/null +++ b/cleverbot/async_/cleverbot.py @@ -0,0 +1,94 @@ +import asyncio + +import aiohttp + +from .. import __version__ +from ..abc import CleverbotBase +from ..errors import APIError, DecodeError, Timeout + + +class Cleverbot(CleverbotBase): + """An asynchronous Cleverbot API wrapper.""" + + def __init__(self, key, *, cs=None, timeout=None, loop=None): + """Initialize Cleverbot with the given arguments. + + Arguments: + key: The key argument is always required. It is your API key. + cs: The cs argument stands for "cleverbot state". It is the encoded + state of the conversation so far and includes the whole + conversation history up to that point. + timeout: How many seconds to wait for the API to send data before + giving up and raising an error. + loop: The event loop used for the asynchronous requests. + """ + self.key = key + self.data = {} + if cs is not None: + self.data['cs'] = cs + self.timeout = timeout + loop = asyncio.get_event_loop() if loop is None else loop + self.session = aiohttp.ClientSession(loop=loop) + + @asyncio.coroutine + def say(self, input=None, **kwargs): + """Talk to Cleverbot. + + Arguments: + input: The input argument is what you want to say to Cleverbot, + such as "hello". + **kwargs: Keyword arguments to update the request parameters with. + + Returns: + Cleverbot's reply. + + Raises: + APIError: A Cleverbot API error occurred. + 401: Unauthorised due to invalid API key. + 404: API not found. + 413: Request too large if you send a request over 16KB. + 502 or 504: Unable to get reply from API server, please contact + us. + 503: Too many requests from a single IP address or API key. + DecodeError: An error occurred while reading the reply. + Timeout: The request timed out. + """ + params = { + 'key': self.key, + 'wrapper': 'cleverbot.py' + } + if input is not None: + params['input'] = input + try: + params['cs'] = self.data['cs'] + except KeyError: + pass + if kwargs: + params.update(kwargs) + + headers = { + 'User-Agent': 'cleverbot.py/' + __version__ + ' ' + '(+https://github.com/orlnub123/cleverbot.py)' + } + try: + reply = yield from self.session.get( + self.url, params=params, headers=headers, timeout=self.timeout) + except asyncio.TimeoutError: + raise Timeout(self.timeout) + else: + try: + data = yield from reply.json() + except ValueError as error: + raise DecodeError(error) + else: + if reply.status == 200: + self.data = data + return data['output'] + else: + raise APIError(data['error'], data['status']) + finally: + reply.release() + + def close(self): + """Close the connection to the API.""" + self.session.close() diff --git a/cleverbot/cleverbot.py b/cleverbot/cleverbot.py index 3b72076..1f6a7e4 100644 --- a/cleverbot/cleverbot.py +++ b/cleverbot/cleverbot.py @@ -1,16 +1,13 @@ -__all__ = ['Cleverbot'] - import requests from . import __version__ -from .errors import CleverbotError, APIError, DecodeError, Timeout +from .abc import CleverbotBase +from .errors import APIError, DecodeError, Timeout -class Cleverbot(object): +class Cleverbot(CleverbotBase): """A Cleverbot API wrapper.""" - url = 'https://www.cleverbot.com/getreply' - def __init__(self, key, **kwargs): """Initialize Cleverbot with the given arguments. @@ -21,43 +18,24 @@ def __init__(self, key, **kwargs): conversation history up to that point. timeout: How many seconds to wait for the API to send data before giving up and raising an error. - **kwargs: Keyword arguments to pass into requests.Session.get """ - self.session = requests.Session() self.key = key self.data = {} if 'cs' in kwargs: self.data['cs'] = kwargs.pop('cs') self.timeout = kwargs.pop('timeout', None) - self.kwargs = kwargs - - def __getattribute__(self, attr): - """Allow access to the stored data through attributes.""" - try: - return super(Cleverbot, self).__getattribute__(attr) - except AttributeError as error: - try: - return super(Cleverbot, self).__getattribute__('data')[attr] - except KeyError: - raise error - - def __setattr__(self, attr, value): - """Allow modifying the cleverbot state with an attribute.""" - if attr == 'cs': - self.data['cs'] = value - else: - super(Cleverbot, self).__setattr__(attr, value) + self.session = requests.Session() + if kwargs: + raise TypeError("__init__() got an unexpected keyword argument " + + repr(list(kwargs.keys())[0])) - def say(self, text, **vtext): + def say(self, input=None, **kwargs): """Talk to Cleverbot. Arguments: - text: The text argument is what you want to say to Cleverbot, such - as "hello". - **vtext: If you wish to override the conversation history, you can - pass in keyword arguments like vtext2 to override the last - thing the bot said, vtext3 for the previous thing the user - said, and so on. + input: The input argument is what you want to say to Cleverbot, + such as "hello". + **kwargs: Keyword arguments to update the request parameters with. Returns: Cleverbot's reply. @@ -75,65 +53,23 @@ def say(self, text, **vtext): """ params = { 'key': self.key, - 'input': text, + 'input': input, 'wrapper': 'cleverbot.py' } try: params['cs'] = self.data['cs'] except KeyError: pass - if vtext: - params.update(vtext) - return self._query(params) + if kwargs: + params.update(kwargs) - def asay(self, *args, **kwargs): - """Look in _async.py for the actual method.""" - raise CleverbotError("asay requires aiohttp and Python 3.4.2+") - - def reset(self): - """Reset all of Cleverbot's stored data.""" - self.data = {} - - def close(self): - """Close the connection to the API.""" - self.session.close() - - def _query(self, params): - """Get Cleverbot's reply and store the data in a dictionary. - - Keys: - cs: State of the conversation so far, which contains an encoded - copy of the conversation id and history. - interaction_count: How many pairs of bot/user interactions have - occurred so far. - input: The entire user input, with any spaces trimmed off both - ends. - output: Cleverbot's reply. - conversation_id: Identifier for this conversation between user and - bot. - errorline: Any error information from Cleverbot, this is different - from general the general errors described below. - time_taken: The number of milliseconds the bot took to respond. - time_elapsed: Approximate number of seconds since conversation - started. - interaction_1 to interaction_50: Record of the previous - interactions. The interaction variables come in pairs. For - example interaction_1 contains the last thing the user said, - and interaction_1_other is the bot's reply. interaction_2 was - the user's previous input and so on. So to read the whole - conversation in order, you have to display the interaction - variables in reverse order, eg interaction_5, - interaction_5_other, interaction_4, interaction_4_other, etc. - Note that if an interaction didn't occur, the interaction_other - will not be defined. - """ headers = { 'User-Agent': 'cleverbot.py/' + __version__ + ' ' '(+https://github.com/orlnub123/cleverbot.py)' } try: - reply = self.session.get(self.url, params=params, headers=headers, - timeout=self.timeout, **self.kwargs) + reply = self.session.get( + self.url, params=params, headers=headers, timeout=self.timeout) except requests.Timeout: raise Timeout(self.timeout) else: @@ -148,7 +84,6 @@ def _query(self, params): else: raise APIError(data['error'], data['status']) - try: - from ._async import __init__, asay, close, _aquery - except (ImportError, SyntaxError): - pass + def close(self): + """Close the connection to the API.""" + self.session.close() diff --git a/cleverbot/errors.py b/cleverbot/errors.py index 2caf25d..0035390 100644 --- a/cleverbot/errors.py +++ b/cleverbot/errors.py @@ -22,7 +22,7 @@ def __init__(self, error, status): class DecodeError(CleverbotError): """Raised when a decode error occurs while reading the reply. - This shouldn't happen. + Reset Cleverbot to fix it. """ @@ -31,5 +31,5 @@ class Timeout(CleverbotError): def __init__(self, timeout): super(Timeout, self).__init__( - "Request timed out after {} seconds".format(timeout)) + "Request timed out after {0} seconds".format(timeout)) self.timeout = timeout diff --git a/setup.py b/setup.py index 149b66c..802f390 100644 --- a/setup.py +++ b/setup.py @@ -19,12 +19,12 @@ name='cleverbot.py', version=version, description='A Cleverbot API wrapper for Python with asynchronous ' - 'functionality.', + 'functionality.', long_description=readme, url='https://github.com/orlnub123/cleverbot.py', author='orlnub123', license='MIT', - packages=['cleverbot'], + packages=['cleverbot', 'cleverbot.async_'], install_requires=['requests>=1.0.0'], extras_require={'async': ['aiohttp>=1.0.0']}, classifiers=[ @@ -42,6 +42,7 @@ 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', 'Topic :: Internet', 'Topic :: Games/Entertainment', 'Topic :: Software Development :: Libraries :: Python Modules',