Last active
October 28, 2015 17:28
-
-
Save Ceasar/061ddbcb1bfd59fbb612 to your computer and use it in GitHub Desktop.
A representation object for consuming RESTful APIs.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
REST client over requests. | |
""" | |
import logging | |
import requests | |
logging.basicConfig(level=logging.INFO) | |
logger = logging.getLogger(__name__) | |
class Resource(object): | |
"""A representation of a Resource. | |
Naively, using code with requests looks like this:: | |
>>> launchpad = requests.get('https://api.launchpad.net/1.0/').json() | |
>>> people = requests.get(launchpad['people_collection_link']).json() | |
>>> people['entries'][0]['display_name'] | |
u'Martin Pitt' | |
This class should be extended by another in order to add support for | |
hypermedia controls. To do this, subclass ``_is_link()``. For example, to | |
use this class with the Launchpad API, which denotes links to other objects | |
by suffixing keys with '_collection_link' or '_link', you might extend | |
Resource like so: | |
>>> class LaunchpadResource(Resource): | |
... def _is_link(self, key): | |
... return ( | |
... key.endswith('_collection_link') or key.endswith('_link') | |
... ) | |
Then you can do things like: | |
>>> launchpad = LaunchpadResource('https://api.launchpad.net/1.0/') | |
>>> people = launchpad['people_collection_link'] | |
>>> people['entries'][0]['display_name'] | |
u'Martin Pitt' | |
""" | |
def __init__(self, url, representation=None, connection=None): | |
self._url = url | |
self._representation = representation | |
self._connection = requests if connection is None else connection | |
# Would be nice to use getattr() here for brevity, but key names are more | |
# flexible than attribute names. We don't offer both so that there's one | |
# way to do it. This also let's us distinguish data from class attributes. | |
def __getitem__(self, k): | |
# Try to load the resource. This helps with brevity. | |
if self._representation is None: | |
self.load() | |
try: | |
attr = self._representation[k] | |
except KeyError: | |
raise KeyError("object has no attribute '%s'" % k) | |
else: | |
return attr | |
def _is_link(self, key): | |
return False | |
def _convert_links(self, obj): | |
if isinstance(obj, dict): | |
return { | |
k: type(self)(v, connection=self._connection) if self._is_link(k) else self._convert_links(v) | |
for k, v in obj.items() | |
} | |
elif isinstance(obj, list): | |
return [self._convert_links(e) for e in obj] | |
else: | |
return obj | |
def load(self, **kwargs): | |
"""Fetch the resource at *url*.""" | |
data = self._connection.get(self.url, **kwargs).json() | |
self._representation = self._convert_links(data) | |
@property | |
def url(self): | |
return self._url |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment