Skip to content

Commit 03c3d85

Browse files
authored
Merge pull request #5 from Kronopt/develop
Develop
2 parents a8a43a3 + e765900 commit 03c3d85

File tree

7 files changed

+157
-112
lines changed

7 files changed

+157
-112
lines changed

.pylintrc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,8 @@ disable=print-statement,
138138
xreadlines-attribute,
139139
deprecated-sys-function,
140140
exception-escape,
141-
comprehension-escape
141+
comprehension-escape,
142+
wrong-import-position
142143

143144
# Enable the message, report, category or checker with the given id(s). You can
144145
# either give multiple identifier separated by comma (,) or put this option

docs/history.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# History
22

3+
### 0.2.2 (13-08-2020)
4+
* Fixed failing to import xkcd_wrapper if either only requests or aiohttp were installed
5+
36
### 0.2.1 (11-08-2020)
47
* Separate dependencies
58
(you can now use the async implementation without having to install the sync dependencies and vice versa)

docs/usage.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ comic.explanation # xkcd explanation wiki url
3939

4040
### Async implementation
4141

42-
The `AsyncClient`can be used in much the same way as `Client`. After importing `xkcd_wrapper`, instantiate the `AsyncClient`:
42+
The `AsyncClient`can be used in much the same way as `Client`. After importing `xkcd_wrapper`,
43+
instantiate the `AsyncClient`:
4344
```python
4445
async_client = xkcd_wrapper.AsyncClient()
4546
```

xkcd_wrapper/__init__.py

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,28 @@
2727
__author__ = 'Pedro HC David, https://github.com/Kronopt'
2828
__credits__ = ['Pedro HC David']
2929
__license__ = 'GPLv3'
30-
__version__ = '0.2.1'
30+
__version__ = '0.2.2'
3131

3232

33-
from .client import Client
34-
from .async_client import AsyncClient
33+
REQUESTS_INSTALLED = True
34+
AIOHTTP_INSTALLED = True
35+
36+
try:
37+
from .client import Client
38+
except ModuleNotFoundError:
39+
REQUESTS_INSTALLED = False
40+
41+
try:
42+
from .async_client import AsyncClient
43+
except ModuleNotFoundError:
44+
AIOHTTP_INSTALLED = False
45+
46+
if not any([REQUESTS_INSTALLED, AIOHTTP_INSTALLED]):
47+
raise ModuleNotFoundError(
48+
'Neither \'requests\' nor \'aiohttp\' are installed. '
49+
'xkcd-wrapper needs at least one of those dependencies to work. '
50+
'Use the correct pip command to install the necessary dependencies '
51+
'(please refer to the xkcd-wrapper documentation).')
52+
3553
from .comic import Comic
3654
from . import exceptions

xkcd_wrapper/async_client.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@
77

88
import random
99
import aiohttp
10-
from .client import Client
10+
from .base_client import BaseClient
1111
from .comic import Comic
1212
from . import exceptions
1313

1414

15-
class AsyncClient(Client):
15+
class AsyncClient(BaseClient):
1616
"""
1717
AsyncClient asynchronously communicates with the xkcd API, parses its response and generates
1818
Comic objects
@@ -92,6 +92,9 @@ async def get_latest(self):
9292
"""
9393
return await self.get(0) # comic_id of 0 requests latest comic
9494

95+
# get_latest alias
96+
latest = get_latest
97+
9598
async def get_random(self):
9699
"""
97100
Asynchronously retrieves a random xkcd comic
@@ -118,6 +121,9 @@ async def get_random(self):
118121
parsed_response = self._parse_response(await self._request_comic(random_id))
119122
return Comic(parsed_response)
120123

124+
# get_random alias
125+
random = get_random
126+
121127
async def _request_comic(self, comic_id):
122128
"""
123129
Handles asynchronous http requests

xkcd_wrapper/base_client.py

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
#!python
2+
# coding: utf-8
3+
4+
"""
5+
xkcd-wrapper BaseClient
6+
"""
7+
8+
import json
9+
from . import exceptions
10+
11+
12+
class BaseClient:
13+
"""
14+
BaseClient contains the methods common to both Client and AsyncClient
15+
This class alone doesn't communicate with the xkcd API
16+
"""
17+
18+
def __init__(self):
19+
"""
20+
BaseClient init
21+
"""
22+
self._base_url = 'https://xkcd.com/{}{}'
23+
self._api = 'info.0.json'
24+
self._explanation_wiki_url = 'https://www.explainxkcd.com/wiki/index.php/'
25+
26+
def base_url(self):
27+
"""
28+
xkcd base API url
29+
30+
Returns
31+
-------
32+
str
33+
xkcd base API url
34+
"""
35+
return self._base_url.format('', '')
36+
37+
def latest_comic_url(self):
38+
"""
39+
xkcd API url for latest comic
40+
41+
Returns
42+
-------
43+
str
44+
xkcd API url for latest comic
45+
"""
46+
return self._base_url.format(self._api, '')
47+
48+
def comic_id_url(self, comic_id):
49+
"""
50+
xkcd API url for comic with id = comic_id
51+
52+
Parameters
53+
----------
54+
comic_id : int
55+
xkcd comic id
56+
57+
Returns
58+
-------
59+
str
60+
xkcd API url for comic with id = comic_id
61+
"""
62+
return self._base_url.format(str(comic_id) + '/', self._api)
63+
64+
def _parse_response(self, response):
65+
"""
66+
Parses xkcd API response
67+
68+
Parameters
69+
----------
70+
response : str
71+
xkcd API json response as str
72+
73+
Returns
74+
-------
75+
dict
76+
relevant fields, extracted from xkcd API response, plus some extra ones
77+
78+
Raises
79+
------
80+
xkcd_wrapper.exceptions.BadResponseField
81+
If response contained a field that could not be converted to int (after json decode)
82+
"""
83+
# relation between xkcd-wrapper fields and xkcd API fields
84+
fields_relationship = {
85+
'id': 'num',
86+
'day': 'day',
87+
'month': 'month',
88+
'year': 'year',
89+
'title': 'safe_title',
90+
'description': 'alt',
91+
'transcript': 'transcript',
92+
'image': 'img',
93+
}
94+
95+
json_response = json.loads(response)
96+
97+
# all values default to None to avoid errors if the xkcd API returns missing data
98+
parsed = dict()
99+
100+
for wrapper_value, api_value in fields_relationship.items():
101+
# if int conversion raises ValueError, something came wrong from the API
102+
if wrapper_value in ('id', 'day', 'month', 'year'): # int
103+
try:
104+
parsed[wrapper_value] = int(
105+
json_response[api_value]) if api_value in json_response else None
106+
except ValueError as err:
107+
raise exceptions.BadResponseField(wrapper_value, api_value, err)
108+
109+
# everything converts to str, so no error here
110+
else: # str
111+
parsed[wrapper_value] = str(
112+
json_response[api_value]) if api_value in json_response else None
113+
114+
parsed['link'] = self._base_url.format(
115+
parsed['id'], '') if parsed['id'] is not None else None
116+
parsed['explanation'] = '{}{}'.format(self._explanation_wiki_url,
117+
parsed['id']) if parsed['id'] is not None else None
118+
119+
return parsed

xkcd_wrapper/client.py

Lines changed: 2 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@
55
xkcd-wrapper Client
66
"""
77

8-
import json
98
import random
109
import requests
10+
from .base_client import BaseClient
1111
from .comic import Comic
1212
from . import exceptions
1313

1414

15-
class Client:
15+
class Client(BaseClient):
1616
"""
1717
Client communicates with the xkcd API, parses its response and generates Comic objects
1818
@@ -39,52 +39,6 @@ class Client:
3939
When calling get(id), get_latest() and random() if an http error or a timeout occurs
4040
"""
4141

42-
def __init__(self):
43-
"""
44-
Client init
45-
"""
46-
self._base_url = 'https://xkcd.com/{}{}'
47-
self._api = 'info.0.json'
48-
self._explanation_wiki_url = 'https://www.explainxkcd.com/wiki/index.php/'
49-
50-
def base_url(self):
51-
"""
52-
xkcd base API url
53-
54-
Returns
55-
-------
56-
str
57-
xkcd base API url
58-
"""
59-
return self._base_url.format('', '')
60-
61-
def latest_comic_url(self):
62-
"""
63-
xkcd API url for latest comic
64-
65-
Returns
66-
-------
67-
str
68-
xkcd API url for latest comic
69-
"""
70-
return self._base_url.format(self._api, '')
71-
72-
def comic_id_url(self, comic_id):
73-
"""
74-
xkcd API url for comic with id = comic_id
75-
76-
Parameters
77-
----------
78-
comic_id : int
79-
xkcd comic id
80-
81-
Returns
82-
-------
83-
str
84-
xkcd API url for comic with id = comic_id
85-
"""
86-
return self._base_url.format(str(comic_id) + '/', self._api)
87-
8842
def get(self, comic_id):
8943
"""
9044
Retrieve an xkcd comic by id
@@ -203,62 +157,5 @@ def _request_comic(self, comic_id):
203157
raise exceptions.HttpError(xkcd_response.status_code, xkcd_response.reason)
204158
return xkcd_response.text
205159

206-
def _parse_response(self, response):
207-
"""
208-
Parses xkcd API response
209-
210-
Parameters
211-
----------
212-
response : str
213-
xkcd API json response as str
214-
215-
Returns
216-
-------
217-
dict
218-
relevant fields, extracted from xkcd API response, plus some extra ones
219-
220-
Raises
221-
------
222-
xkcd_wrapper.exceptions.BadResponseField
223-
If response contained a field that could not be converted to int (after json decode)
224-
"""
225-
# relation between xkcd-wrapper fields and xkcd API fields
226-
fields_relationship = {
227-
'id': 'num',
228-
'day': 'day',
229-
'month': 'month',
230-
'year': 'year',
231-
'title': 'safe_title',
232-
'description': 'alt',
233-
'transcript': 'transcript',
234-
'image': 'img',
235-
}
236-
237-
json_response = json.loads(response)
238-
239-
# all values default to None to avoid errors when the API returns missing data
240-
parsed = dict()
241-
242-
for wrapper_value, api_value in fields_relationship.items():
243-
# if int conversion raises ValueError, something came wrong from the API
244-
if wrapper_value in ('id', 'day', 'month', 'year'): # int
245-
try:
246-
parsed[wrapper_value] = int(
247-
json_response[api_value]) if api_value in json_response else None
248-
except ValueError as err:
249-
raise exceptions.BadResponseField(wrapper_value, api_value, err)
250-
251-
# everything converts to str, so no error here
252-
else: # str
253-
parsed[wrapper_value] = str(
254-
json_response[api_value]) if api_value in json_response else None
255-
256-
parsed['link'] = self._base_url.format(
257-
parsed['id'], '') if parsed['id'] is not None else None
258-
parsed['explanation'] = '{}{}'.format(self._explanation_wiki_url,
259-
parsed['id']) if parsed['id'] is not None else None
260-
261-
return parsed
262-
263160
def __repr__(self):
264161
return 'xkcd_wrapper.Client()'

0 commit comments

Comments
 (0)