From 77c42a36f7332439a06489ec7aba7c5b3585d322 Mon Sep 17 00:00:00 2001 From: Robert Kuykendall Date: Sun, 6 Nov 2016 22:14:37 -0500 Subject: [PATCH] 100% code coverage. --- .coveragerc | 3 +++ docs/index.rst | 25 +++++++++++------------ marvelous/comic.py | 2 +- marvelous/comics_list.py | 3 +++ marvelous/series.py | 14 +++++++++---- marvelous/session.py | 10 ++------- tests/cache_test.py | 42 ++++++++++++++++++++++++++++++++++++++ tests/series_test.py | 35 +++++++++++++++++++++++++++++++ tests/testing_mock.sqlite | Bin 5120 -> 103424 bytes 9 files changed, 108 insertions(+), 26 deletions(-) create mode 100644 .coveragerc create mode 100644 tests/cache_test.py create mode 100644 tests/series_test.py diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000..5ab8eaf --- /dev/null +++ b/.coveragerc @@ -0,0 +1,3 @@ +[run] +include = + marvelous/* diff --git a/docs/index.rst b/docs/index.rst index 12fb630..dd8b4ba 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -70,25 +70,24 @@ Instantiating API public_key = "Public key from https://developer.marvel.com/account" private_key = "Private key from https://developer.marvel.com/account" - print_calls = True # Will print URL of app API calls, for debugging - def CacheClass: - def get(self, key): - # This method should return cahed value with key - return None + # Optional + class CacheClass: + def get(self, key): + # This method should return cahed value with key + return None - def store(self, key, value): - # This method should store key value pair - return + def store(self, key, value): + # This method should store key value pair + return cache = CacheClass() # m is a session object, read about it below m = marvelous.api( - public_key, - private_key, - print_calls=print_calls, - cache=cache + public_key, + private_key, + cache=cache ) @@ -187,7 +186,7 @@ Series - ``response`` - Dictionary, raw response body - ``id`` - Int - ``resource_uri`` - String, `resourceURI` from API -- ``name`` - String +- ``title`` - String - ``comics`` - Method, Returns ``ComicsList`` object for `/v1/public/series/{seriesId}/comics` diff --git a/marvelous/comic.py b/marvelous/comic.py index 9d7ec12..07d29c3 100644 --- a/marvelous/comic.py +++ b/marvelous/comic.py @@ -47,7 +47,7 @@ def process_input(self, data): # Marvel comic 1768, and maybe others, returns a modified of # "-0001-11-30T00:00:00-0500". The best way to handle this is # probably just to ignore it, since I don't know how to fix it. - if new_data['modified'][0] == '-': + if new_data.get('modified', ' ')[0] == '-': del new_data['modified'] return new_data diff --git a/marvelous/comics_list.py b/marvelous/comics_list.py index dd15f5e..562612f 100644 --- a/marvelous/comics_list.py +++ b/marvelous/comics_list.py @@ -19,6 +19,9 @@ def __init__(self, response): def __iter__(self): return iter(self.comics) + def __len__(self): + return len(self.comics) + def __getitem__(self, index): try: return next(itertools.islice(self.comics, index, index+1)) diff --git a/marvelous/series.py b/marvelous/series.py index 74edb15..176684a 100644 --- a/marvelous/series.py +++ b/marvelous/series.py @@ -1,9 +1,9 @@ from marshmallow import Schema, fields, pre_load, post_load -from . import comics_list +from . import comics_list, exceptions -class Series(): +class Series: def __init__(self, **kwargs): if 'response' not in kwargs: kwargs['response'] = None @@ -11,7 +11,10 @@ def __init__(self, **kwargs): for k, v in kwargs.items(): setattr(self, k, v) - def comics(self, params): + def comics(self, params=None): + if params is None: + params = {} + return comics_list.ComicsList( self.session.call(['series', self.id, 'comics'], params=params)) @@ -20,10 +23,13 @@ class SeriesSchema(Schema): response = fields.Raw() id = fields.Int() resourceURI = fields.Str(attribute='resource_uri') - name = fields.Str() + title = fields.Str() @pre_load def process_input(self, data): + if data.get('code', 200) != 200: + raise exceptions.ApiError(data.get('status')) + if 'status' in data: data['data']['results'][0]['response'] = data data = data['data']['results'][0] diff --git a/marvelous/session.py b/marvelous/session.py index 23c375b..601834b 100644 --- a/marvelous/session.py +++ b/marvelous/session.py @@ -8,13 +8,10 @@ class Session(): api_url = "http://gateway.marvel.com:80/v1/public/{}" - def __init__( - self, public_key, private_key, cache=None, - print_calls=False): + def __init__(self, public_key, private_key, cache=None): self.public_key = public_key self.private_key = private_key - self.print_calls = print_calls self.cache = cache def call(self, endpoint, params=None): @@ -46,13 +43,10 @@ def call(self, endpoint, params=None): response = requests.get(url, params=params) - if self.print_calls: - print(response.url) - data = response.json() if 'message' in data: - raise exceptions.ApiError(response['message']) + raise exceptions.ApiError(data['message']) if self.cache and response.status_code == 200: try: diff --git a/tests/cache_test.py b/tests/cache_test.py new file mode 100644 index 0000000..a968130 --- /dev/null +++ b/tests/cache_test.py @@ -0,0 +1,42 @@ +import os +import unittest + +import marvelous + + +class NoGet: + def store(self, key, value): + # This method should store key value pair + return + + +class NoStore: + def get(self, key): + # This method should return cahed value with key + return None + + +class TestComics(unittest.TestCase): + def setUp(self): + self.pub = os.getenv('PUBLIC_KEY', 'pub') + self.priv = os.getenv('PRIVATE_KEY', 'priv') + + def test_no_get(self): + m = marvelous.api( + public_key=self.pub, private_key=self.priv, + cache=NoGet()) + + with self.assertRaises(marvelous.exceptions.CacheError): + m.series(466) + + def test_no_store(self): + m = marvelous.api( + public_key=self.pub, private_key=self.priv, + cache=NoStore()) + + with self.assertRaises(marvelous.exceptions.CacheError): + m.series(466) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/series_test.py b/tests/series_test.py new file mode 100644 index 0000000..3a7dd7b --- /dev/null +++ b/tests/series_test.py @@ -0,0 +1,35 @@ +import os +import unittest + +import marvelous +from marvelous.comics_list import ComicsList + + +class TestComics(unittest.TestCase): + def setUp(self): + pub = os.getenv('PUBLIC_KEY', 'pub') + priv = os.getenv('PRIVATE_KEY', 'priv') + self.m = marvelous.api( + public_key=pub, private_key=priv, + cache=marvelous.SqliteCache("tests/testing_mock.sqlite")) + + def test_known_series(self): + usms = self.m.series(466) + self.assertTrue(usms.title == "Ultimate Spider-Man (2000 - 2009)") + self.assertTrue(usms.id == 466) + comics = usms.comics() + self.assertTrue(comics[0].id == 23931) + + self.assertTrue(len(comics[:5]) == 5) + self.assertTrue(len(comics) == len([x for x in comics if x.id > 3])) + + def test_bad_series(self): + with self.assertRaises(marvelous.exceptions.ApiError): + self.m.series(-1) + + def test_bad_response_data(self): + with self.assertRaises(marvelous.exceptions.ApiError): + ComicsList({'data': {'results': [{'modified': 'potato'}]}}) + +if __name__ == '__main__': + unittest.main() diff --git a/tests/testing_mock.sqlite b/tests/testing_mock.sqlite index aab3d18826d8ba491673c431ae451ba47d19de17..84afbe56d39058b024ad7a90841e369895ac2292 100644 GIT binary patch literal 103424 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 qt=Xt)hS4%BTE(c?_*Ju2Yc<+cx(3;DF`PV!Z