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/__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 c245c2f..322a8eb 100644 --- a/nyplcollections/nyplcollections.py +++ b/nyplcollections/nyplcollections.py @@ -1,70 +1,107 @@ #!/usr/bin/env python - import requests -import xmltodict class NYPLsearch(object): - raw_results = None - results = None - error = None - def __init__(self, token, format='json', page=1, per_page=10): - self.token = token - self.page = page - self.per_page = per_page - self.format = format + def __init__(self, token, page=None, per_page=None): + self._token = token + 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" - # Return the captures for a given uuid - # optional value withTitles=yes def captures(self, uuid, withTitles=False): - return self._get('/'.join([self.base, uuid]), - {'withTitles': 'yes' if withTitles else 'no'}) + """Return the captures for a given uuid + optional value withTitles=yes""" + picker = lambda x: x.get('capture', []) + return self._get((uuid,), picker, withTitles='yes' if withTitles else 'no') - # Return the item-uuid for a identifier. def uuid(self, type, val): - return self._get('/'.join([self.base, type, val])) + """Return the item-uuid for a identifier""" + picker = lambda x: x.get('uuid', x) + return self._get((type, val), picker) + + 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)""" - # 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): - params = {'q': q} - if field: - params['field'] = field + def picker(results): + if type(results['result']) == list: + return results['result'] + else: + return [results['result']] - return self._get('/'.join([self.base, 'search']), params) + return self._get(('search',), picker, q=q, field=field, page=page, per_page=per_page) - # Return a mods record for a given uuid def mods(self, uuid): - return self._get('/'.join([self.base, 'mods', uuid])) + """Return a mods record for a given uuid""" + picker = lambda x: x.get('mods', {}) + return self._get(('mods', uuid), picker) - # Generic get which handles call to api and setting of results - # Return: results dict - def _get(self, url, params=None): - self.raw_results = self.results = 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) - headers = {"Authorization": "Token token=" + self.token} - params = params or dict() - params['page'] = self.page - params['per_page'] = self.per_page + 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 r = requests.get(".".join([url, self.format]), params=params, headers=headers) - self.raw_results = r.text - self.results = self._to_dict(r)['nyplAPI']['response'] + _next = self._nextify(components, picker, params) - if self.results['headers']['status'] == 'error': - self.error = { - 'code': self.results['headers']['code'], - 'message': self.results['headers']['message'] - } - else: - self.error = None + return Result(r, picker, _next) + + 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 - return self.results + def __init__(self, request_object, picker, _next): + self.raw = request_object.text + self.status_code = request_object.status_code - def _to_dict(self, r): - return r.json() if self.format == 'json' else xmltodict.parse(r.text) + 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 diff --git a/setup.py b/setup.py index d3f7bab..d454c08 100644 --- a/setup.py +++ b/setup.py @@ -2,12 +2,11 @@ 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", install_requires=[ - "xmltodict", "requests" ], packages=['nyplcollections'], 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