SkillAgentSearch skills...

Restnavigator

A python library for interacting with HAL+JSON APIs

Install / Use

/learn @deontologician/Restnavigator
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

REST Navigator

|Build Status| |Coverage Status| |Pypi Status| |Documentation Status|

Note: this project is unmaintained! Feel free to fork it!

REST Navigator is a python library for interacting with hypermedia apis (REST level 3 <http://martinfowler.com/articles/richardsonMaturityModel.html#level3>). Right now, it only supports HAL+JSON <http://tools.ietf.org/html/draft-kelly-json-hal-05> but it should be general enough to extend to other formats eventually. Its first goal is to make interacting with HAL hypermedia apis as painless as possible, while discouraging REST anti-patterns.

To install it, simply use pip:

.. code:: bash

$ pip install restnavigator

Contents

  • How to use it <#how-to-use-it>__

    • Links <#links>__
    • GET requests <#get-requests>__
    • Link relation docs <#link-relation-docs>__
    • POST requests <#post-requests>__
    • Errors <#errors>__
    • Templated links <#templated-links>__
    • Authentication <#authentication>__
  • Additional Topics <#additional-topics>__

    • Identity Map <#identity-map>__
    • Iterating over a Navigator <#iterating-over-a-navigator>__
    • Headers (Request vs. Response) <#headers-request-vs-response>__
    • Bracket mini-language <#bracket-minilanguage>__
    • Finding the right link <#finding-the-right-link>__
    • Default curie <#default-curie>__
    • Specifying an api name <#specifying-an-api-name>__
    • Embedded documents <#embedded-documents>__
  • Development <#development>__

    • Testing <#testing>__
    • Planned for the future <#planned-for-the-future>__

.. raw:: html

<!-- end toc -->

How to use it

To begin interacting with a HAL api, you've got to create a HALNavigator that points to the api root. Ideally, in a hypermedia API, the root URL is the only URL that needs to be hardcoded in your application. All other URLs are obtained from the api responses themselves (think of your api client as 'clicking on links', rather than having the urls hardcoded).

As an example, we'll connect to the haltalk api.

.. code:: python

>>> from restnavigator import Navigator
>>> N = Navigator.hal('http://haltalk.herokuapp.com/', default_curie="ht")
>>> N
HALNavigator(Haltalk)

Links


Usually, with the index (normally at the api root), you're most
interested in the links. Let's look at those:

.. code:: python

    >>> N.links()
    {u'ht:users': HALNavigator(Haltalk.users),
     u'ht:signup': HALNavigator(Haltalk.signup),
     u'ht:me': TemplatedThunk(Haltalk.users.{name}),
     u'ht:latest-posts': HALNavigator(Haltalk.posts.latest)}

(This may take a moment because asking for the links causes the
HALNavigator to actually request the resource from the server).

Here we can see that the links are organized by their relation type (the
key), and each key corresponds to a new HALNavigator that represents
some other resource. Relation types are extremely important in restful
apis: we need them to be able to determine what a link means in relation
to the current resource, in a way that is automatable.

GET requests

In addition, the root has some state associated with it which you can get in two different ways:

.. code:: python

>>> N() # cached state of resource (obtained when we looked at N.links)
{u'hint_1': u'You need an account to post stuff..',
 u'hint_2': u'Create one by POSTing via the ht:signup link..',
 u'hint_3': u'Click the orange buttons on the right to make POST requests..',
 u'hint_4': u'Click the green button to follow a link with a GET request..',
 u'hint_5': u'Click the book icon to read docs for the link relation.',
 u'welcome': u'Welcome to a haltalk server.'}
>>> N.fetch() # will refetch the resource from the server
{u'hint_1': u'You need an account to post stuff..',
 u'hint_2': u'Create one by POSTing via the ht:signup link..',
 u'hint_3': u'Click the orange buttons on the right to make POST requests..',
 u'hint_4': u'Click the green button to follow a link with a GET request..',
 u'hint_5': u'Click the book icon to read docs for the link relation.',
 u'welcome': u'Welcome to a haltalk server.'}

Calling a HALNavigator will execute a GET request against the resource and returns its value (which it will cache).

Link relation docs


Let's register a hal talk account. Unfortunately, we don't really know
how to do that, so let's look at the documentation. The ``ht:signup``
link looks promising, let's check that:

.. code:: python

    >>> N.docsfor('ht:signup')

A browser will open to http://haltalk.herokuapp.com/rels/signup.

What? Popping up a browser from a library call? Yes, that's how
rest\_navigator rolls. The way we see it: docs are for humans, and while
custom rel-types are URIs, they shouldn't automatically be dereferenced
by a program that interacts with the api. So popping up a browser serves
two purposes:

1. It allows easy access to the documentation at the time when you most
   need it: when you're mucking about in the command line trying to
   figure out how to interact with the api.
2. It reminds you not to try to automatically dereference the rel
   documentation and parse it in your application.

If you need a more robust way to browse the api and the documentation,
`HAL Browser <https://github.com/mikekelly/hal-browser>`__ is probably
your best bet.

POST requests
~~~~~~~~~~~~~

The docs for ``ht:signup`` explain the format of the POST request to
sign up. So let's actually sign up. Since we've set ``"ht"`` as our
default curie, we can skip typing the curie for convenience. (Note:
haltalk is a toy api for example purposes, don't ever send plaintext
passwords over an unencrypted connection in a real app!):

.. code:: python

    >>> fred23 = N['signup'].create(
    ... {'username': 'fred23',
    ...  'password': 'hunter2',
    ...  'real_name': 'Fred 23'}
    ... )
    >>> fred23
    HALNavigator(Haltalk.users.fred23)

Errors
~~~~~~

If the user name had already been in use, a 400 would have been returned
from the haltalk api. rest\_navigator follows the Zen of Python
guideline "Errors should never pass silently". An exception would have
been raised on a 400 or 500 status code. You can squelch this exception
and just have the post call return a ``HALNavigator`` with a 400/500
status code if you want:

.. code:: python

    >>> dup_signup = N['ht:signup'].create({
    ...    'username': 'fred23',
    ...    'password': 'hunter2',
    ...    'real_name': 'Fred Wilson'
    ... }, raise_exc=False)
    >>> dup_signup
    OrphanHALNavigator(Haltalk.signup)  # 400!
    >>> dup_signup.status
    (400, 'Bad Request')
    >>> dup_signup.state
    {u"errors": {u"username": [u"is already taken"]}}

Templated links
~~~~~~~~~~~~~~~

Now that we've signed up, lets take a look at our profile. The link for
a user's profile is a templated link, which restnavigator represents as
a ``PartialNavigator``. Similar to python's
`functools.partial <https://docs.python.org/2/library/functools.html#functools.partial>`__,
a ``PartialNavigator`` is an object that needs a few more arguments to
give you a full navigator back. Despite its name, it can't talk to the
network by itself. Its job is to to generate new navigators for you. You
can see what variables it has by looking at its ``.variables`` attribute
(its ``__repr__`` hints at this as well):

.. code:: python

    >>> N.links().keys()
    ['ht:latest-posts', 'ht:me', 'ht:users', 'ht:signup']
    >>> N['ht:me']
    PartialNavigator(Haltalk.users.{name})
    >>> N['ht:me'].variables
    set(['name'])

The documentation for the ``ht:me`` rel type should tell us how the name
parameter is supposed to work, but in this case it's fairly obvious
(plug in the username). Two provide the template parameters, just call
it with keyword args:

.. code:: python

    >>> partial_me = N['ht:me']
    >>> partial_me.template_uri
    'http://haltalk.herokuapp.com/users/{name}'
    >>> Fred = partial_me(name='fred23')
    >>> Fred
    HALNavigator('haltalk.users.fred23')

Now that we have a real navigator, we can fetch the resource:

.. code:: python

    >>> Fred()
    {u'bio': None, u'real_name': u'Fred Wilson', u'username': u'fred23'}

Authentication
~~~~~~~~~~~~~~

In order to post something to haltalk, we need to authenticate with our
newly created account. HALNavigator allows any `authentication method
that requests
supports <http://www.python-requests.org/en/latest/user/advanced/#custom-authentication>`__
(so OAuth etc). For basic auth (which haltalk uses), we can just pass a
tuple.

.. code:: python

    >>> N.authenticate(('fred23', 'hunter2'))  # All subsequent calls are authenticated

This doesn't send anything to the server, it just sets the
authentication details that we'll use on the next request. Other
authentication methods may contact the server immediately.

Now we can put it all together to create a new post:

.. code:: python

    >>> N_post = N['me'](name='fred23')['posts'].create({'content': 'My first post'})
    >>> N_post
    HALNavigator(Haltalk.posts.523670eff0e6370002000001)
    >>> N_post()
    {'content': 'My first post', 'created_at': '2015-06-13T19:38:59+00:00'}

It is also possible to specify a custom requests Session object when creating 
a new navigator.

For example, if you want to talk to a OAuth2 protected api, simply pass 
an OAuth2 Session object that will be used for all requests 
done by HALNavigator:

.. code:: python

    >>> from requests_oauthlib import OAuth2Session
    >>> oauth2_session = OAuth2Session(r'client_id', token='token')
    >>> N = Navigator.hal('https://api.example.com', session=oauth2_session)

Additional Topics
-----------------

Identity Map
~~~~~~~~~~~~

You don't need to worry about inadvertently having two different
navigators pointing to the same resource. rest\_navigator will reuse the
existing navigator instead of creating a new one
View on GitHub
GitHub Stars42
CategoryDevelopment
Updated3d ago
Forks15

Languages

Python

Security Score

90/100

Audited on Apr 7, 2026

No findings