From f8d7441468e4bac1fd4379824721eb5fd9598c4a Mon Sep 17 00:00:00 2001 From: fitnr Date: Wed, 18 Feb 2015 14:05:48 -0500 Subject: [PATCH 1/7] comments to docstrings --- nyplcollections/nyplcollections.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/nyplcollections/nyplcollections.py b/nyplcollections/nyplcollections.py index c245c2f..a30ae21 100644 --- a/nyplcollections/nyplcollections.py +++ b/nyplcollections/nyplcollections.py @@ -16,32 +16,32 @@ def __init__(self, token, format='json', page=1, per_page=10): self.format = format self.base = "http://api.repo.nypl.org/api/v1/items" - # Return the captures for a given uuid - # optional value withTitles=yes def captures(self, uuid, withTitles=False): + """Return the captures for a given uuid + optional value withTitles=yes""" return self._get('/'.join([self.base, uuid]), {'withTitles': 'yes' if withTitles else 'no'}) - # Return the item-uuid for a identifier. def uuid(self, type, val): + """Return the item-uuid for a identifier""" return self._get('/'.join([self.base, type, val])) - # Search across all (without field) or in specific field - # (valid fields at http://www.loc.gov/standards/mods/mods-outline.html) def search(self, q, field=None): + """Search across all (without field) or in specific field + (valid fields at http://www.loc.gov/standards/mods/mods-outline.html)""" params = {'q': q} if field: params['field'] = field return self._get('/'.join([self.base, 'search']), params) - # Return a mods record for a given uuid def mods(self, uuid): + """Return a mods record for a given uuid""" return self._get('/'.join([self.base, 'mods', uuid])) - # Generic get which handles call to api and setting of results - # Return: results dict def _get(self, url, params=None): + """Generic get which handles call to api and setting of results + Return: Results object""" self.raw_results = self.results = None headers = {"Authorization": "Token token=" + self.token} From d0ca9626f08baaddfc66c9df8406e20f3da96234 Mon Sep 17 00:00:00 2001 From: fitnr Date: Thu, 12 Feb 2015 12:25:36 -0500 Subject: [PATCH 2/7] allow page, per_page to be set on NYPLsearch.search use argument collection make actual results array the return value do url joining once --- nyplcollections/nyplcollections.py | 59 +++++++++++++++++------------- 1 file changed, 34 insertions(+), 25 deletions(-) diff --git a/nyplcollections/nyplcollections.py b/nyplcollections/nyplcollections.py index a30ae21..9144329 100644 --- a/nyplcollections/nyplcollections.py +++ b/nyplcollections/nyplcollections.py @@ -3,68 +3,77 @@ import requests import xmltodict - class NYPLsearch(object): - raw_results = None - results = None + raw_results = '' + request = dict() error = None - def __init__(self, token, format='json', page=1, per_page=10): + def __init__(self, token, format=None, page=None, per_page=None): self.token = token - self.page = page - self.per_page = per_page - self.format = format + self.format = format or 'json' + self.page = page or 1 + self.per_page = per_page or 10 self.base = "http://api.repo.nypl.org/api/v1/items" def captures(self, uuid, withTitles=False): """Return the captures for a given uuid optional value withTitles=yes""" - return self._get('/'.join([self.base, uuid]), - {'withTitles': 'yes' if withTitles else 'no'}) + picker = lambda x: x.get('capture', []) + return self._get((uuid,), picker, withTitles='yes' if withTitles else 'no') def uuid(self, type, val): """Return the item-uuid for a identifier""" - return self._get('/'.join([self.base, type, val])) + picker = lambda x: x.get('uuid', x) + return self._get((type, val), picker) - def search(self, q, field=None): + def search(self, q, field=None, page=None, per_page=None): """Search across all (without field) or in specific field (valid fields at http://www.loc.gov/standards/mods/mods-outline.html)""" - params = {'q': q} - if field: - params['field'] = field - return self._get('/'.join([self.base, 'search']), params) + def picker(results): + if type(results['result']) == list: + return results['result'] + else: + return [results['result']] + + return self._get(('search',), picker, q=q, field=field, page=page, per_page=per_page) def mods(self, uuid): """Return a mods record for a given uuid""" - return self._get('/'.join([self.base, 'mods', uuid])) + picker = lambda x: x.get('mods', {}) + return self._get(('mods', uuid), picker) - def _get(self, url, params=None): + def _get(self, components, picker, **params): """Generic get which handles call to api and setting of results Return: Results object""" + url = '/'.join((self.base,) + components) + self.raw_results = self.results = None headers = {"Authorization": "Token token=" + self.token} - params = params or dict() - params['page'] = self.page - params['per_page'] = self.per_page + + params['page'] = params.get('page') or self.page + params['per_page'] = params.get('per_page') or self.per_page r = requests.get(".".join([url, self.format]), params=params, headers=headers) self.raw_results = r.text - self.results = self._to_dict(r)['nyplAPI']['response'] + results = self._to_dict(r)['nyplAPI']['response'] + + self.headers = results['headers'] + self.request = r.json()['nyplAPI'].get('request', dict()) - if self.results['headers']['status'] == 'error': + if self.headers['status'] == 'error': self.error = { - 'code': self.results['headers']['code'], - 'message': self.results['headers']['message'] + 'code': self.headers['code'], + 'message': self.headers['message'] } else: self.error = None - return self.results + return picker(results) def _to_dict(self, r): return r.json() if self.format == 'json' else xmltodict.parse(r.text) From 5c363047646742aead1f8ce875056de1b33ddf9f Mon Sep 17 00:00:00 2001 From: fitnr Date: Wed, 18 Feb 2015 13:30:44 -0500 Subject: [PATCH 3/7] remove format param - focus on returning Python objects --- nyplcollections/nyplcollections.py | 10 +++------- setup.py | 1 - 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/nyplcollections/nyplcollections.py b/nyplcollections/nyplcollections.py index 9144329..1a7d486 100644 --- a/nyplcollections/nyplcollections.py +++ b/nyplcollections/nyplcollections.py @@ -1,16 +1,15 @@ #!/usr/bin/env python import requests -import xmltodict class NYPLsearch(object): raw_results = '' request = dict() error = None - def __init__(self, token, format=None, page=None, per_page=None): + def __init__(self, token, page=None, per_page=None): self.token = token - self.format = format or 'json' + self.format = 'json' self.page = page or 1 self.per_page = per_page or 10 self.base = "http://api.repo.nypl.org/api/v1/items" @@ -60,10 +59,10 @@ def _get(self, components, picker, **params): headers=headers) self.raw_results = r.text - results = self._to_dict(r)['nyplAPI']['response'] self.headers = results['headers'] self.request = r.json()['nyplAPI'].get('request', dict()) + results = r.json()['nyplAPI']['response'] if self.headers['status'] == 'error': self.error = { @@ -73,7 +72,4 @@ def _get(self, components, picker, **params): else: self.error = None - return picker(results) - def _to_dict(self, r): - return r.json() if self.format == 'json' else xmltodict.parse(r.text) diff --git a/setup.py b/setup.py index d3f7bab..c1957ee 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,6 @@ author="nick mohoric", author_email="nick.mohoric@gmail.com", install_requires=[ - "xmltodict", "requests" ], packages=['nyplcollections'], From c95f6f4a962a2b1a2f863a0de4a2887d69757afe Mon Sep 17 00:00:00 2001 From: fitnr Date: Wed, 18 Feb 2015 14:10:40 -0500 Subject: [PATCH 4/7] add Result object --- nyplcollections/__init__.py | 1 + nyplcollections/nyplcollections.py | 69 ++++++++++++++++++++++-------- 2 files changed, 51 insertions(+), 19 deletions(-) diff --git a/nyplcollections/__init__.py b/nyplcollections/__init__.py index e78cd29..066ae5c 100644 --- a/nyplcollections/__init__.py +++ b/nyplcollections/__init__.py @@ -1 +1,2 @@ +__all__ = ['nyplcollections'] from .nyplcollections import NYPLsearch diff --git a/nyplcollections/nyplcollections.py b/nyplcollections/nyplcollections.py index 1a7d486..4041416 100644 --- a/nyplcollections/nyplcollections.py +++ b/nyplcollections/nyplcollections.py @@ -1,14 +1,10 @@ #!/usr/bin/env python - import requests class NYPLsearch(object): - raw_results = '' - request = dict() - error = None def __init__(self, token, page=None, per_page=None): - self.token = token + self._token = token self.format = 'json' self.page = page or 1 self.per_page = per_page or 10 @@ -47,9 +43,7 @@ def _get(self, components, picker, **params): Return: Results object""" url = '/'.join((self.base,) + components) - self.raw_results = self.results = None - - headers = {"Authorization": "Token token=" + self.token} + headers = {"Authorization": "Token token=" + self._token} params['page'] = params.get('page') or self.page params['per_page'] = params.get('per_page') or self.per_page @@ -58,18 +52,55 @@ def _get(self, components, picker, **params): params=params, headers=headers) - self.raw_results = r.text + _next = self._nextify(components, picker, params) - self.headers = results['headers'] - self.request = r.json()['nyplAPI'].get('request', dict()) - results = r.json()['nyplAPI']['response'] + return Result(r, picker, _next) - if self.headers['status'] == 'error': - self.error = { - 'code': self.headers['code'], - 'message': self.headers['message'] - } - else: - self.error = None + def _nextify(self, components, picker, params): + params['page'] = 1 + params.get('page', 0) + return lambda: self._get(components, picker, **params) + + +class Result(object): + + '''Iterable wrapper for responses from NYPL API''' + error = None + + def __init__(self, request_object, picker, _next): + self.raw = request_object.text + self.status_code = request_object.status_code + + self._next = _next + response = request_object.json().get('nyplAPI', {}) + try: + self.headers = response['response'].get('headers') + + if self.headers['status'] == 'error': + self.error = { + 'code': self.headers['code'], + 'message': self.headers['message'] + } + + else: + self.request = response.get('request', {}) + + for k in ('numResults', 'totalPages', 'perPage', 'page'): + v = self.request.get(k) + if v: + self.request[k] = int(v) + + if response['response'].get('numResults'): + self.count = int(response['response']['numResults']) + + self.results = picker(response['response']) + + except IndexError: + raise IndexError("Couldn't parse response.") + + def next(self): + if self.request.get('totalPages') > self.request.get('page'): + return self._next() + else: + raise StopIteration From fd93ae544485910a897607309c87528c66def1cd Mon Sep 17 00:00:00 2001 From: fitnr Date: Wed, 18 Feb 2015 14:53:43 -0500 Subject: [PATCH 5/7] add tests --- tests/tests.py | 73 +++++++++++++++++++++++++++++++++++++++++++++ tests/testsample.py | 4 --- 2 files changed, 73 insertions(+), 4 deletions(-) create mode 100644 tests/tests.py delete mode 100644 tests/testsample.py diff --git a/tests/tests.py b/tests/tests.py new file mode 100644 index 0000000..c3dfc09 --- /dev/null +++ b/tests/tests.py @@ -0,0 +1,73 @@ +from nyplcollections import NYPLsearch +import unittest + +# todo: use mock to mock in NYPL responses + +KEY = # insert your key here + +class TestNYPLsearch(unittest.TestCase): + + def setUp(self): + self.nypl = NYPLsearch(KEY) + + def test_search(self): + cats = self.nypl.search('cats') + + assert 200 == cats.status_code + + assert cats.request['perPage'] == 10 + + assert cats.results + + assert len(cats.results) + + assert cats.results[0].get('uuid') + + + def test_next(self): + cats = self.nypl.search('cats') + mor_cats = next(cats) + assert 200 == mor_cats.status_code + + def test_search_fields(self): + maps = self.nypl.search('cartographic', field='typeOfResource') + + assert 200 == maps.status_code + + assert maps.results + + assert maps.results[0].get('uuid') + + def test_per_page(self): + cats = self.nypl.search('cats', per_page=1) + assert len(cats.results) == 1 + + def test_mods(self): + uuid = '510d47dd-ab68-a3d9-e040-e00a18064a99' + hades = '423990' + mods = self.nypl.mods(uuid) + assert 200 == mods.status_code + + identifiers = mods.results['identifier'] + u = [i for i in identifiers if i['type'] == 'local_hades'] + assert u[0]['$'] == hades + + assert mods.results['titleInfo'][1]['title']['$'] == u'Gowanus Bay - Brooklyn - 30th Street Pier.' + + def test_uuid(self): + uuid = self.nypl.uuid('local_hades', '1017240') + + assert 200 == uuid.status_code + assert 'ecaf7d80-c55f-012f-e3c7-58d385a7bc34' == uuid.results + + def test_captures(self): + captures = self.nypl.captures('5fa75050-c6c7-012f-e24b-58d385a7bc34') + + assert 200 == captures.status_code + + assert 125 == captures.count + + assert type(captures.results) == list + +if __name__ == '__main__': + unittest.main() diff --git a/tests/testsample.py b/tests/testsample.py deleted file mode 100644 index 0255648..0000000 --- a/tests/testsample.py +++ /dev/null @@ -1,4 +0,0 @@ -import NYPLsearch - -def test_numbers_3_4(): - assert 3 * 4 == 12 From c450f468291f6b4ac0523d3146624a21cc5be240 Mon Sep 17 00:00:00 2001 From: fitnr Date: Thu, 12 Feb 2015 15:11:04 -0500 Subject: [PATCH 6/7] bump version --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index c1957ee..d454c08 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setup( name="nyplcollections", - version="1", + version="1.1", description="new york public library image collections api", author="nick mohoric", author_email="nick.mohoric@gmail.com", From 165efa4ff0747bf4d1d6ee99148848789094f12b Mon Sep 17 00:00:00 2001 From: fitnr Date: Wed, 18 Feb 2015 15:53:09 -0500 Subject: [PATCH 7/7] expand readme --- README.md | 39 +++++++++++++++++++++++++++++- nyplcollections/nyplcollections.py | 1 + 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 593844b..26ea12d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,41 @@ nypl-digital-collections ======================== -Library to access the New York Public Library's Digital Collections API \ No newline at end of file +Library to access the New York Public Library's [Digital Collections API](http://api.repo.nypl.org). + +Basics: + +````python +from nyplcollections import NYPLsearch + +# Create search object +nypl = NYPLsearch(API_KEY) +```` + +Methods: +* NYPLsearch.captures +* NYPLsearch.mods +* NYPLsearch.search +* NYPLsearch.uuid + +Search: + +````python +cats = self.nypl.search('cats') + +cats.results +# [...] +```` + +MODS: + +````python +# Get a MODS record based on uuid +mods = nypl.mods('acfeeb2d-7c5e-4ce7-e040-e00a180644aa') + +mods.status_code +200 + +mods.results +# {...} +```` diff --git a/nyplcollections/nyplcollections.py b/nyplcollections/nyplcollections.py index 4041416..322a8eb 100644 --- a/nyplcollections/nyplcollections.py +++ b/nyplcollections/nyplcollections.py @@ -1,6 +1,7 @@ #!/usr/bin/env python import requests + class NYPLsearch(object): def __init__(self, token, page=None, per_page=None):