Skip to content

Commit

Permalink
Support for synchronous use of the lib.
Browse files Browse the repository at this point in the history
  • Loading branch information
PapyrusThePlant committed Aug 20, 2016
1 parent 8a74eef commit 77178ed
Show file tree
Hide file tree
Showing 9 changed files with 98 additions and 31 deletions.
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ Installation
Usage
=====

::
.. code :: py
import asyncio
import strawpoll
Expand Down
4 changes: 3 additions & 1 deletion docs/async.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ python 3.4 or defined with ``async def`` in python 3.5+.

Coroutines are not like regular functions, if you invoke it as usual, it will
not be executed. The proper way to call a coroutine is to use ``yield from`` in
python 3.4 or ``await`` in python 3.5+, like so: ::
python 3.4 or ``await`` in python 3.5+, like so:

.. code :: py
# With python 3.4
@asyncio.coroutine
Expand Down
3 changes: 2 additions & 1 deletion examples/polls.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ async def main():
# Displaying its info
print(p1.id) # 10915632
print(p1.url) # http://www.strawpoll.me/10915632
print(p1.title) # awesome?
print(p1.results()) # [('fuck yes', 1), ('yes', 0)]
print(p1.options) # ['yes', 'fuck yes']
print(p1.votes) # [0, 1]
Expand All @@ -32,7 +33,7 @@ async def main():
print(p2.url) #

# Submitting it on strawpoll
await api.submit_poll(p2)
p2 = await api.submit_poll(p2)
print(p2.id) # 10921552
print(p2.url) # http://www.strawpoll.me/10921552

Expand Down
3 changes: 2 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
aiohttp
aiohttp
requests
6 changes: 3 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@

setup(name=about['__title__'],
version=about['__version__'],
description='An async python wrapper for the Strawpoll API.',
description=about['description'],
long_description=long_description,
author=about['__author__'],
license=about['__license__'],
Expand All @@ -48,7 +48,7 @@
include_package_data=True,
install_requires=install_requires,
keywords=about['__title__'],
zip_safe = False,
zip_safe=False,
classifiers=[
'Development Status :: 5 - Production/Stable',
'License :: OSI Approved :: MIT License',
Expand All @@ -60,4 +60,4 @@
'Topic :: Software Development :: Libraries :: Python Modules',
'Topic :: Utilities'
]
)
)
1 change: 1 addition & 0 deletions strawpoll/__about__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"""

__title__ = 'strawpoll.py'
__description__ = 'A python wrapper for the Strawpoll API.'
__version__ = '0.1.0'
__author__ = 'PapyrusThePlant'
__license__ = 'MIT'
Expand Down
3 changes: 2 additions & 1 deletion strawpoll/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@

from collections import namedtuple

from .__about__ import __author__, __license__, __title__, __url__, __version__
from .__about__ import __title__, __description__, __version__, __author__, __license__, __url__, __copyright__
from .api import API
from .errors import *
from .http import RequestsPolicy
from .poll import Poll

VersionInfo = namedtuple('version_info', ('major', 'minor', 'micro', 'releaselevel', 'serial'))
Expand Down
39 changes: 23 additions & 16 deletions strawpoll/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import strawpoll.poll
from .errors import *
from .http import HTTPClient
from .http import HTTPClient, RequestsPolicy


class API:
Expand All @@ -24,20 +24,24 @@ class API:

_url_re = re.compile('^{}\/(?P<id>[0-9]+)(/r)?$'.format(_BASE_URL.replace('/', '\/')))

def __init__(self, *, loop=None):
def __init__(self, *, loop=None, requests_policy=RequestsPolicy.asynchronous):
self.loop = loop if loop is not None else asyncio.get_event_loop()
self._http_client = HTTPClient(loop)
self._http_client = HTTPClient(loop, requests_policy=requests_policy)

def __del__(self):
del self._http_client

@asyncio.coroutine
def get_poll(self, arg):
"""|coroutine|
@property
def requests_policy(self):
return self._http_client.requests_policy

Retrieves a poll from strawpoll.
def get_poll(self, arg, *, request_policy=None):
"""Retrieves a poll from strawpoll.
:param arg: Either the ID of the poll or its strawpoll url.
:param request_policy: Overrides :attr:`API.requests_policy` for that \
request.
:type request_policy: Optional[:class:`RequestsPolicy`]
:raises HTTPException: Requesting the poll failed.
Expand All @@ -50,17 +54,18 @@ def get_poll(self, arg):
if match:
arg = match.group('id')

data = yield from self._http_client.get('{}/{}'.format(self._POLLS, arg))
return strawpoll.poll.Poll(**data)
return self._http_client.get('{}/{}'.format(self._POLLS, arg),
request_policy=request_policy,
cls=strawpoll.Poll)

@asyncio.coroutine
def submit_poll(self, poll):
"""|coroutine|
Submits a poll on strawpoll.
def submit_poll(self, poll, *, request_policy=None):
"""Submits a poll on strawpoll.
:param poll: The poll to submit.
:type poll: :class:`Poll`
:param request_policy: Overrides :attr:`API.requests_policy` for that \
request.
:type request_policy: Optional[:class:`RequestsPolicy`]
:raises ExistingPoll: This poll instance has already been submitted.
:raises HTTPException: The submission failed.
Expand All @@ -84,5 +89,7 @@ def submit_poll(self, poll):
'captcha': poll.captcha
}

data = yield from self._http_client.post(self._POLLS, data=data)
poll.id = data['id']
return self._http_client.post(self._POLLS,
data=data,
request_policy=request_policy,
cls=strawpoll.Poll)
68 changes: 61 additions & 7 deletions strawpoll/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
import asyncio
import json
import logging
import requests
from enum import Enum
from sys import version_info

from . import __title__, __version__, __url__
Expand All @@ -15,23 +17,39 @@
log = logging.getLogger(__name__)


class RequestsPolicy(Enum):
"""An enumeration representing the """
asynchronous = 0
synchronous = 1


class HTTPClient:
"""Simplistic HTTP client to send requests to an url."""
def __init__(self, loop=None):
def __init__(self, loop=None, requests_policy=RequestsPolicy.asynchronous):
self.loop = loop if loop is not None else asyncio.get_event_loop()
fmt = '{0}/{1} ({2}) Python/{3.major}.{3.minor}.{3.micro} aiohttp/{4}'
self.user_agent = fmt.format(__title__, __version__, __url__, version_info, aiohttp.__version__)
self.session = aiohttp.ClientSession(loop=loop, headers={'User-Agent': self.user_agent})
self.requests_policy = requests_policy

# Build our default headers
fmt = '{0}/{1} ({2}) Python/{3.major}.{3.minor}.{3.micro} aiohttp/{4} requests/{5}'
user_agent = fmt.format(__title__, __version__, __url__, version_info, aiohttp.__version__, requests.__version__)
self.default_headers = {'User-Agent': user_agent}

# Prepare the sessions
self.session_async = aiohttp.ClientSession(loop=loop, headers=self.default_headers)
self.session_sync = requests.Session()
self.session_sync.headers = self.default_headers

def __del__(self):
self.session.close()
self.session_async.close()
self.session_sync.close()

@asyncio.coroutine
def request(self, method, url, **kwargs):
def request_async(self, method, url, **kwargs):
cls = kwargs.pop('cls', None)
if 'data' in kwargs:
kwargs['data'] = json.dumps(kwargs['data'], separators=(',', ':'))

resp = yield from self.session.request(method, url, **kwargs)
resp = yield from self.session_async.request(method, url, **kwargs)
try:
# Strawpoll's API is supposed to always return json data, but whatever
if 'application/json' in resp.headers['content-type']:
Expand All @@ -43,11 +61,47 @@ def request(self, method, url, **kwargs):
log.debug('{} on {} with {} returned {}: {}'.format(method, url, resp.status, data, rdata))
if resp.status != 200:
raise HTTPException(resp, rdata)
elif cls is not None:
return cls(**rdata)
else:
return rdata
finally:
resp.release()

def request_sync(self, method, url, **kwargs):
cls = kwargs.pop('cls', None)
if 'data' in kwargs:
kwargs['data'] = json.dumps(kwargs['data'], separators=(',', ':'))

resp = self.session_sync.request(method, url, **kwargs)
try:
# Strawpoll's API is supposed to always return json data, but whatever
if 'application/json' in resp.headers['content-type']:
rdata = resp.json()
else:
rdata = resp.text()

data = kwargs['data'] if 'data' in kwargs else 'no data'
log.debug('{} on {} with {} returned {}: {}'.format(method, url, resp.status_code, data, rdata))
if resp.status_code != 200:
raise HTTPException(resp, rdata)
elif cls is not None:
return cls(**rdata)
else:
return rdata
finally:
resp.close()

def request(self, *args, **kwargs):
request_policy = kwargs.pop('request_policy') or self.requests_policy

if request_policy == RequestsPolicy.asynchronous:
return self.request_async(*args, **kwargs)
elif request_policy == RequestsPolicy.synchronous:
return self.request_sync(*args, **kwargs)
else:
raise ValueError('Invalid request policy.')

def delete(self, *args, **kwargs):
return self.request('DELETE', *args, **kwargs)

Expand Down

0 comments on commit 77178ed

Please sign in to comment.