A New Python API

I’m happy to share fluent-discourse, a new python package for consuming the Discourse API.

There are already a few packages for consuming the Discourse API via Python. So why build another?

The other discourse packages out there tend to approach the problem in a similar way – create a unique python function for every endpoint of the API.

This approach has a few disadvantages:

  1. It’s really hard to get full feature parity with the API. Discourse’s API is somewhat under-documented and often you need to reverse engineer endpoints. Furthermore, there are numerous plugins that expose API endpoints that aren’t even part of core. This presents the library user with the tough choice of making pull requests to add the endpoints they need to the library, or building a custom solution for the extra endpoints.
  2. You have to learn another API. On top of learning the methods and endpoints of the Discourse API, you have to figure out what the matching function call is in python.
  3. There’s a lot more code to test, so it’s harder to get 100% test coverage. Thus there’s less confidence in the quality of the software produced.

In contrast to this approach, I used a “fluent” interface, where you use method chaining to construct requests. Let’s take an example.
To get the latest topics in some category ‘foo’ with id=5, you use this endpoint:
GET /c/foo/5.json.
To make that request with this library simply call:
client.c.foo[5].json.get()

You can see that there’s semantic parity between the API endpoint and the corresponding call in Python.

This approach

  1. Out of the box has full feature parity with the Discourse API, including undocumented endpoints, endpoints from plugins, and endpoints that have yet to be defined (future proof).
  2. You only have to learn the Discourse API. It’s easy to translate any API call in this way, and thus you don’t need to search for the function that matches it.
  3. The whole library is about 150 lines of code. This makes it trivial to achieve 100% test coverage.

There’s 100% test coverage with a handful of integration tests against a live discourse server.

If that sounds appealing to you, I hope you’ll give this package a try!

21 Likes

That’s an interesting aproach…

To do something similar in php we’d have to rely on magic methods and so lose the benefit of IDE autocompletion etc. But might be a wortwhile price to pay.

What’s the advantage of this approach over simply passing the url as an argument: client.get(url) ?

1 Like

That’s a good point, IDE autocompletion will not be of much help here. It might be worth using a different library if that’s important to you.

Since each method chain returns a new client, one advantage I can think of is storing a specific method chain in a variable and re-using it for subsequent calls. For example, let’s say you had a list of data for new posts that you wanted to create.

post_data = [
  {
    "raw": "Hello World!",
    "topic_id": 123,
    ...
  },
  {
    "raw": "Tester",
    "topic_id": 501,
    ...
  },
  ...
]

You could store the “New post” method chain and re-use it to create each of the posts in the list.

new_post_endpoint = client.posts.json

for post in post_data:
  new_post_endpoint.post(data=post) 

You might be able to imagine other scenarios where this ability to re-use a client could be useful. Beyond that, there’s not a huge advantage over just passing the url like you suggested. I encourage you to read about the Universal Client library which contains some information about the rational for this type of approach.

Also, for a PHP specific implementation of this approach, check out SendGrid’s PHP API. I was inspired to build this when I read about SendGrid’s Python API.

4 Likes