Skip to content

Commit 50cceae

Browse files
authored
Merge pull request #7 from Kronopt/develop
Develop
2 parents 03c3d85 + 741d249 commit 50cceae

File tree

17 files changed

+456
-285
lines changed

17 files changed

+456
-285
lines changed

.pylintrc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,8 @@ disable=print-statement,
139139
deprecated-sys-function,
140140
exception-escape,
141141
comprehension-escape,
142-
wrong-import-position
142+
wrong-import-position,
143+
too-many-instance-attributes
143144

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

README.md

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,3 @@ xkcd_wrapper.Comic(100)
6868

6969
## Documentation
7070
Check the documentation for more details: https://xkcd-wrapper.readthedocs.io/en/latest
71-
72-
### Ideas for the Future
73-
* retrieve comic .jpeg

docs/client-async.md

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,14 @@ Returns: **str**
3939

4040
### async get
4141
```Python
42-
AsyncClient.get(comic_id)
42+
AsyncClient.get(comic_id, raw_comic_image=True)
4343
```
4444
Retrieves an xkcd comic by id
4545

4646
| Parameter | Type / Value | Default | Description |
4747
|:---:|:---:|:---:|---|
4848
| comic_id | int | | xkcd comic id |
49+
| raw_comic_image | bool | True | Indicates if raw comic image should be retrieved or not (implies an extra http request) |
4950

5051
Returns: **xkcd_wrapper.Comic**
5152

@@ -57,11 +58,15 @@ Raises:
5758

5859
### async get_latest
5960
```Python
60-
AsyncClient.get_latest()
61-
AsyncClient.latest()
61+
AsyncClient.get_latest(raw_comic_image=True)
62+
AsyncClient.latest(raw_comic_image=True)
6263
```
6364
Retrieves the latest xkcd comic
6465

66+
| Parameter | Type / Value | Default | Description |
67+
|:---:|:---:|:---:|---|
68+
| raw_comic_image | bool | True | Indicates if raw comic image should be retrieved or not (implies an extra http request) |
69+
6570
Returns: **xkcd_wrapper.Comic**
6671

6772
Raises:
@@ -71,12 +76,16 @@ Raises:
7176

7277
### async get_random
7378
```Python
74-
AsyncClient.get_random()
75-
AsyncClient.random()
79+
AsyncClient.get_random(raw_comic_image=True)
80+
AsyncClient.random(raw_comic_image=True)
7681
```
7782
Retrieves a random xkcd comic.
7883
Contacts the xkcd API twice: once to know how many comics there are and another to fetch a random comic
7984

85+
| Parameter | Type / Value | Default | Description |
86+
|:---:|:---:|:---:|---|
87+
| raw_comic_image | bool | True | Indicates if raw comic image should be retrieved or not (implies an extra http request) |
88+
8089
Returns: **xkcd_wrapper.Comic**
8190

8291
Raises:

docs/client.md

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,14 @@ Returns: **str**
3939

4040
### get
4141
```Python
42-
Client.get(comic_id)
42+
Client.get(comic_id, raw_comic_image=True)
4343
```
4444
Retrieves an xkcd comic by id
4545

4646
| Parameter | Type / Value | Default | Description |
4747
|:---:|:---:|:---:|---|
4848
| comic_id | int | | xkcd comic id |
49+
| raw_comic_image | bool | True | Indicates if raw comic image should be retrieved or not (implies an extra http request) |
4950

5051
Returns: **xkcd_wrapper.Comic**
5152

@@ -57,11 +58,15 @@ Raises:
5758

5859
### get_latest
5960
```Python
60-
Client.get_latest()
61-
Client.latest()
61+
Client.get_latest(raw_comic_image=True)
62+
Client.latest(raw_comic_image=True)
6263
```
6364
Retrieves the latest xkcd comic
6465

66+
| Parameter | Type / Value | Default | Description |
67+
|:---:|:---:|:---:|---|
68+
| raw_comic_image | bool | True | Indicates if raw comic image should be retrieved or not (implies an extra http request) |
69+
6570
Returns: **xkcd_wrapper.Comic**
6671

6772
Raises:
@@ -71,12 +76,16 @@ Raises:
7176

7277
### get_random
7378
```Python
74-
Client.get_random()
75-
Client.random()
79+
Client.get_random(raw_comic_image=True)
80+
Client.random(raw_comic_image=True)
7681
```
7782
Retrieves a random xkcd comic.
7883
Contacts the xkcd API twice: once to know how many comics there are and another to fetch a random comic
7984

85+
| Parameter | Type / Value | Default | Description |
86+
|:---:|:---:|:---:|---|
87+
| raw_comic_image | bool | True | Indicates if raw comic image should be retrieved or not (implies an extra http request) |
88+
8089
Returns: **xkcd_wrapper.Comic**
8190

8291
Raises:

docs/comic.md

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
11
# xkcd_wrapper.Comic
22
xkcd comic representation
33

4-
A `Comic` represents a single xkcd comic. `xkcd_wrapper.Client` generates `Comic` objects
4+
A `Comic` represents a single xkcd comic. `xkcd_wrapper.Client` and `xkcd_wrapper.AsyncClient` generate `Comic` objects
55

66
## Parameters
77
The `Comic` class can be instantiated with the following parameters:
88
```Python
9-
Comic(xkcd_dict)
9+
Comic(xkcd_dict, raw_image=None, comic_url=None, explanation_url=None)
1010
```
1111

1212
| Parameter | Type / Value | Default | Description|
1313
|:---:|:---:|:---:|---|
14-
| xkcd_dict | dict | | dictionary containing all the fields parsed by the `Client` |
14+
| xkcd_dict | dict | | dictionary containing the xkcd API response json |
15+
| raw_image | bytes / None | None | raw comic image |
16+
| comic_url | str / None | None | xkcd comic url |
17+
| explanation_url | str / None | None | explainxkcd wiki url |
1518

1619
## Attributes
1720
Instances of the `Comic` class have the following attributes. All attributes can be `None` if the value was omitted
@@ -24,9 +27,11 @@ from the xkcd API response, for some reason
2427
| title | str / None | comic title |
2528
| description | str / None | comic description |
2629
| transcript | str / None | comic transcript |
27-
| image | str / None | image url |
28-
| link | str / None | comic url |
29-
| explanation | str / None | url to explainxkcd wiki |
30+
| image | bytes / None | raw comic image |
31+
| image_extension | str / None | comic image extension (ex: .png, .jpeg) |
32+
| image_url | str / None | comic image url |
33+
| comic_url | str / None | comic url |
34+
| explanation | str / None | explainxkcd wiki url |
3035

3136
## Special Methods
3237
* \_\_repr__

docs/history.md

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

3+
### 1.0.0 (06-09-2020)
4+
* Reworked xkcd API response json decoding
5+
* Reworked `Comic`
6+
* `Client` and `AsyncClient` can now retrieve comic images
7+
38
### 0.2.2 (13-08-2020)
4-
* Fixed failing to import xkcd_wrapper if either only requests or aiohttp were installed
9+
* Fixed failing to import `xkcd_wrapper` if either only `requests` or `aiohttp` were installed
510

611
### 0.2.1 (11-08-2020)
712
* Separate dependencies
813
(you can now use the async implementation without having to install the sync dependencies and vice versa)
914

1015
### 0.2.0 (08-08-2020)
11-
* Async implementation
16+
* Async implementation (`AsyncClient`)
1217

1318
### 0.1.0 (23-04-2020)
1419
* First release on PyPI
15-
* Client and Comic classes
20+
* `Client` and `Comic` classes

docs/usage.md

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,21 +20,25 @@ Then you can use one of the available methods to retrieve xkcd comics:
2020
* `get_random()` -> retrieves a random comic (can also be called as just `random()`)
2121

2222
All methods return a `Comic` object which contains the structured xkcd comic data.
23+
Each of these methods also take a boolean keyword argument `raw_comic_image` (defaults to True), which is a boolean
24+
indicating if the raw comic image should be retrieved or not (retrieving the image implies another http request).
2325

2426
```python
25-
comic = client.get_latest()
27+
comic = client.get(comic_id, raw_comic_image=True)
2628
```
2729

28-
The data is accessible as properties. You can access them like so:
30+
You can access the xkcd comic data like so:
2931
```python
30-
comic.id # comic number
31-
comic.date # date of release
32-
comic.title # title
33-
comic.description # description
34-
comic.transcript # dialog and scene transcript
35-
comic.image # image url
36-
comic.link # comic webpage url
37-
comic.explanation # xkcd explanation wiki url
32+
comic.id # comic number
33+
comic.date # date of release
34+
comic.title # title
35+
comic.description # description
36+
comic.transcript # dialog and scene transcript
37+
comic.image # raw comic image
38+
comic.image_extension # comic image extension (ex: .png, .jpeg)
39+
comic.image_url # comic image url
40+
comic.comic_url # comic webpage url
41+
comic.explanation # xkcd explanation wiki url
3842
```
3943

4044
### Async implementation
@@ -45,7 +49,7 @@ instantiate the `AsyncClient`:
4549
async_client = xkcd_wrapper.AsyncClient()
4650
```
4751

48-
Then the same methods are available as for `Client`, but they are all async:
52+
Then the same methods with the same parameters are available as for `Client`, but they are all async:
4953

5054
* `get(<id>)` -> retieves comic by id
5155
* `get_latest()` -> retrieves the latest comic (can also be called as just `latest()`)

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@
5151
zip_safe=False,
5252
keywords='xkcd wrapper xkcd-wrapper',
5353
classifiers=[
54-
'Development Status :: 4 - Beta',
54+
'Development Status :: 5 - Production/Stable',
5555
'Intended Audience :: Developers',
5656
'License :: OSI Approved :: GNU General Public License v3 (GPLv3)',
5757
'Natural Language :: English',

tests/__init__.py

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
latest_comic_url = 'https://xkcd.com/info.0.json'
1010
comic_id_url = 'https://xkcd.com/{}/info.0.json'
1111
explanation_wiki_url = 'https://www.explainxkcd.com/wiki/index.php/'
12+
raw_comic_image = b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01\x08\x02\x00\x00\x00\x90wS\xde\x00\x00\x00\x01sRGB\x00\xae\xce\x1c\xe9\x00\x00\x00\x04gAMA\x00\x00\xb1\x8f\x0b\xfca\x05\x00\x00\x00\tpHYs\x00\x00\x16%\x00\x00\x16%\x01IR$\xf0\x00\x00\x00\x0cIDAT\x18Wc\xf8\xff\xff?\x00\x05\xfe\x02\xfe\xa75\x81\x84\x00\x00\x00\x00IEND\xaeB`\x82'
1213

1314
with open('tests/xkcd_api_example_response_628.json') as example_628, \
1415
open('tests/xkcd_api_example_response_138.json') as example_138, \
@@ -23,7 +24,7 @@
2324
xkcd_api_example_138_dict = json.loads(xkcd_api_example_138_raw)
2425

2526

26-
def check_comic(test_case, comic, example):
27+
def check_comic(test_case, comic, example, raw_image=True):
2728
"""
2829
Assert comic has the required values
2930
@@ -35,16 +36,27 @@ def check_comic(test_case, comic, example):
3536
Comic
3637
example : dict
3738
parsed xkcd API example json
39+
raw_image : bool
40+
if Comic was generated with a comic raw image or not
3841
"""
3942
date = datetime.date(int(example['year']),
4043
int(example['month']),
4144
int(example['day']))
4245

43-
test_case.assertEqual(comic.date, date)
4446
test_case.assertEqual(comic.id, example['num'])
47+
test_case.assertEqual(comic.date, date)
4548
test_case.assertEqual(comic.title, example['safe_title'])
4649
test_case.assertEqual(comic.description, example['alt'])
4750
test_case.assertEqual(comic.transcript, example['transcript'])
48-
test_case.assertEqual(comic.image, example['img'])
49-
test_case.assertEqual(comic.link, '{}{}'.format(base_url, example['num']))
50-
test_case.assertEqual(comic.explanation, '{}{}'.format(explanation_wiki_url, example['num']))
51+
52+
if raw_image:
53+
test_case.assertEqual(comic.image, raw_comic_image)
54+
test_case.assertEqual(comic.image_extension, 'png')
55+
else:
56+
test_case.assertIsNone(comic.image)
57+
test_case.assertIsNone(comic.image_extension)
58+
59+
test_case.assertEqual(comic.image_url, example['img'])
60+
test_case.assertEqual(comic.comic_url, '{}{}'.format(base_url, example['num']))
61+
test_case.assertEqual(comic.explanation_url,
62+
'{}{}'.format(explanation_wiki_url, example['num']))

tests/test_async_client.py

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
import xkcd_wrapper
1313
from aioresponses import aioresponses
1414
from nose2.tools.params import params as nose2_params
15-
from . import (base_url, latest_comic_url, comic_id_url, check_comic,
15+
from . import (base_url, latest_comic_url, comic_id_url, check_comic, raw_comic_image,
1616
xkcd_api_example_628_raw,
1717
xkcd_api_example_138_raw,
1818
xkcd_api_example_wrong_raw,
@@ -31,6 +31,13 @@ def test_client_init(self):
3131
self.assertIsInstance(c._base_url, str)
3232
self.assertIsInstance(c._api, str)
3333
self.assertIsInstance(c._explanation_wiki_url, str)
34+
self.assertIsInstance(c._explanation_wiki_url, str)
35+
36+
self.assertIsInstance(c._response_int_values, dict)
37+
self.assertEqual(c._response_int_values['num'], 'id')
38+
self.assertEqual(c._response_int_values['year'], 'date')
39+
self.assertEqual(c._response_int_values['month'], 'date')
40+
self.assertEqual(c._response_int_values['day'], 'date')
3441

3542
def test_base_url(self):
3643
c = xkcd_wrapper.AsyncClient()
@@ -50,41 +57,54 @@ def test_get(self):
5057
loop = asyncio.get_event_loop()
5158
with aioresponses() as mock:
5259
mock.get(comic_id_url.format(628), status=200, body=xkcd_api_example_628_raw)
60+
mock.get(xkcd_api_example_628_dict['img'], status=200, body=raw_comic_image)
5361
response = loop.run_until_complete(c.get(628))
5462
check_comic(self, response, xkcd_api_example_628_dict)
5563
with self.assertRaises(TypeError):
5664
loop.run_until_complete(c.get(''))
5765
with self.assertRaises(TypeError):
5866
loop.run_until_complete(c.get([1, 2, 3]))
5967

68+
def test_get_without_raw_image(self):
69+
c = xkcd_wrapper.AsyncClient()
70+
loop = asyncio.get_event_loop()
71+
with aioresponses() as mock:
72+
mock.get(comic_id_url.format(628), status=200, body=xkcd_api_example_628_raw)
73+
response = loop.run_until_complete(c.get(628, raw_comic_image=False))
74+
check_comic(self, response, xkcd_api_example_628_dict, raw_image=False)
75+
with self.assertRaises(TypeError):
76+
loop.run_until_complete(c.get(''))
77+
with self.assertRaises(TypeError):
78+
loop.run_until_complete(c.get([1, 2, 3]))
79+
6080
def test_get_http_error(self):
6181
c = xkcd_wrapper.AsyncClient()
6282
loop = asyncio.get_event_loop()
6383
with aioresponses() as mock:
6484
mock.get(comic_id_url.format(628), status=404)
6585
with self.assertRaises(xkcd_wrapper.exceptions.HttpError):
66-
loop.run_until_complete(c.get(628))
86+
loop.run_until_complete(c.get(628, raw_comic_image=False))
6787

6888
def test_get_bad_response_fields(self):
6989
c = xkcd_wrapper.AsyncClient()
7090
loop = asyncio.get_event_loop()
7191
with aioresponses() as mock:
7292
mock.get(comic_id_url.format(628), status=200, body=xkcd_api_example_wrong_raw)
7393
with self.assertRaises(xkcd_wrapper.exceptions.BadResponseField):
74-
loop.run_until_complete(c.get(628))
94+
loop.run_until_complete(c.get(628, raw_comic_image=False))
7595

7696
def test_get_latest(self): # let's assume the example_628 json is the latest comic
7797
c = xkcd_wrapper.AsyncClient()
7898
loop = asyncio.get_event_loop()
7999
with aioresponses() as mock:
80100
mock.get(latest_comic_url, status=200, body=xkcd_api_example_628_raw)
81-
response = loop.run_until_complete(c.get_latest())
82-
check_comic(self, response, xkcd_api_example_628_dict)
101+
response = loop.run_until_complete(c.get_latest(raw_comic_image=False))
102+
check_comic(self, response, xkcd_api_example_628_dict, raw_image=False)
83103

84104
# alias
85105
mock.get(latest_comic_url, status=200, body=xkcd_api_example_628_raw)
86-
response = loop.run_until_complete(c.latest())
87-
check_comic(self, response, xkcd_api_example_628_dict)
106+
response = loop.run_until_complete(c.latest(raw_comic_image=False))
107+
check_comic(self, response, xkcd_api_example_628_dict, raw_image=False)
88108

89109
def test_get_random(self): # let's assume the example_628 json is the latest comic
90110
c = xkcd_wrapper.AsyncClient()
@@ -93,17 +113,17 @@ def test_get_random(self): # let's assume the example_628 json is the latest co
93113
mock.get(latest_comic_url, status=200, body=xkcd_api_example_628_raw)
94114
mock.get(comic_id_url.format(138), status=200, body=xkcd_api_example_138_raw)
95115
random.seed(1) # with latest comic being 628, random value will be 138
96-
response = loop.run_until_complete(c.get_random())
116+
response = loop.run_until_complete(c.get_random(raw_comic_image=False))
97117
self.assertEqual(response.id, xkcd_api_example_138_dict['num'])
98-
check_comic(self, response, xkcd_api_example_138_dict)
118+
check_comic(self, response, xkcd_api_example_138_dict, raw_image=False)
99119

100120
# alias
101121
mock.get(latest_comic_url, status=200, body=xkcd_api_example_628_raw)
102122
mock.get(comic_id_url.format(138), status=200, body=xkcd_api_example_138_raw)
103123
random.seed(1)
104-
response = loop.run_until_complete(c.get_random())
124+
response = loop.run_until_complete(c.get_random(raw_comic_image=False))
105125
self.assertEqual(response.id, xkcd_api_example_138_dict['num'])
106-
check_comic(self, response, xkcd_api_example_138_dict)
126+
check_comic(self, response, xkcd_api_example_138_dict, raw_image=False)
107127

108128
def test__repr__(self):
109129
c = xkcd_wrapper.AsyncClient()

0 commit comments

Comments
 (0)