Skip to content

Commit

Permalink
Merge pull request #3 from fitnr/develop
Browse files Browse the repository at this point in the history
Add results object
  • Loading branch information
nmohoric committed Feb 22, 2015
2 parents 20ec1de + 165efa4 commit 3bf6270
Show file tree
Hide file tree
Showing 6 changed files with 195 additions and 52 deletions.
39 changes: 38 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,41 @@
nypl-digital-collections
========================

Library to access the New York Public Library's Digital Collections API
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
# {...}
````
1 change: 1 addition & 0 deletions nyplcollections/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
__all__ = ['nyplcollections']
from .nyplcollections import NYPLsearch
127 changes: 82 additions & 45 deletions nyplcollections/nyplcollections.py
Original file line number Diff line number Diff line change
@@ -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
3 changes: 1 addition & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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="[email protected]",
install_requires=[
"xmltodict",
"requests"
],
packages=['nyplcollections'],
Expand Down
73 changes: 73 additions & 0 deletions tests/tests.py
Original file line number Diff line number Diff line change
@@ -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()
4 changes: 0 additions & 4 deletions tests/testsample.py

This file was deleted.

0 comments on commit 3bf6270

Please sign in to comment.