After we initially released Porc, we found its interface unintuitive for the ways we used it, such that we had to resort to using Requests for a few projects for want of a better library.
So today we released Porc 0.3.0, a complete rewrite of the library based on community feedback. Primarily, we flattened the interface, made asynchronous requests easier, and wrapped HTTP responses in a convenience class to make them easier to work with.
Here’s how it handles now:
from porc import Client client = Client(YOUR_API_KEY) # make sure our API key works client.ping().raise_for_status() # get and update an item item = client.get(COLLECTION, KEY) item['was_modified'] = True client.put(item.collection, item.key, item.json, item.ref).raise_for_status() # asynchronously get two items with client.async() as c: futures = [ c.get(COLLECTION, KEY_1), c.get(COLLECTION, KEY_2) ] responses = [future.result() for future in futures] [response.raise_for_status() for response in responses] # iterate through search results pages = client.search(COLLECTION, QUERY) for page in pages: # prints 200 print page.status_code # prints number of items returned by page print page['count'] # get every item in a collection items = client.list(COLLECTION).all() # prints number of items in collection print len(items)
For more details, including how to get the library, see the readme.
But, why the rewrite? Besides “I hate it, change it”, what feedback did we act on?
With patterns like SOAP and REST, we like to pretend that there’s a future in which we won’t have to write API clients like Porc. If your API conforms to the spec, we can generate clients for each language automatically, right? Certainly, when an API is RESTful, writing a client is easier. You can use an HTTP client like Python Requests as a REST client, but pretty soon you’re doing the same things over and over, so you write convenience methods that do it for you. Then you want to organize your convenience methods into a class, and bam, you have a custom client all over again.
Don’t get me wrong, the ubiquity of RESTful patterns makes my life as a web developer much less painful. But what makes REST powerful – nested resources, caching, statelessness, uniformity of interface – differs from what makes a developer experience great. Consider, a nested interface is harder to introspect and learn than a flat one.
Ultimately, REST cannot encapsulate the experience of working with an API.
That said, there are countless REST clients out there for different APIs. After you work with five or six, patterns emerge. You notice what works and what doesn’t, lessons which you work into your own clients. Even though the work you’re doing is still custom, you borrow from those who’ve come before. In rewriting Porc, we borrowed heavily from patterns seen in the CouchDB client Nano and Python’s Requests. Namely:
- The user should only have to construct one object, the client. Anything else should be constructed behind the scenes.
- Flat is better than nested, so client.get_relations is better than client.collection(...).item(...).relation(...).get
- If users should (or must) do something repeatedly, and we can automate it for them, then we should.
That reasoning led us to flatten the Porc’s interface around a client object which exposes everything you need. For operations users executed frequently, like deserializing response bodies from JSON, we made sure HTTP requests returned a class that did that for them.
Initially, inspired by Backbone’s annotated source, Porc’s documentation featured source code for methods, rather than examples of actually using the method. But, just like the advantages of REST, your implementation should be irrelevant to the user. Only the interface matters, so demonstrate it.
So, we rewrote the documentation with usage examples for every class and method in Porc. Wherever possible, we opted to show code rather than write text.
So, fry up some bacon (if that’s your thing) and take Porc for a spin. Let us know what you think of the new interface and how this meshes (or doesn’t) with your own experience writing REST clients.