Languages

Python API Checklist

Useful checklist for building good Python library APIs, based on "How to make a good library API" PyCon 2017 talk.
Checks are saved to your local storage

1. Simplicity

      • E.g. JavaScript history.pushState has bad default argument order: state, title, URL. Most API clients just want to add a URL to the history and they're forced to specify state and title.
      • E.g. In Scrapy 1.2, the send method accepts a "to" parameter as a list of strings. If the client passes a single string, the method will iterate over the string, trying to send emails to each character of it. Scrapy 1.3 fixed this by accepting both a single string and a list of strings.
      • E.g. a function that only accepts file-like objects will force clients to use StringIO if they want to pass strings.
    • "If a feature has a high astonishment factor, it may be necessary to redesign it".
      • E.g. to make a simple Celery task, devs don't need to worry much about task queues, workers, message brokers, serialization, etc. They just need to use the @app.task decorator. The API is focused on the task definition, not on the tasks inner-workings.
      • E.g. RPC is almost naturally bad because it abstracts remote resources as local ones, but those should really be handled differently from local ones.

2. Consistency

    • Follow PEP8, the official style guide for Python code. To guarantee adherence to Naming Conventions and other style constraints, validate the API code with flake8.
    • E.g. datetime.datetime(year, month, day, minute, second, microsecond) vs datetime.timedelta(days, seconds, microseconds, milliseconds, minutes, hours, weeks).
    • E.g. Good: numbers.sort() in-place vs sorted(numbers) not-in-place.

3. Flexibility:

      • E.g. a class to get/set items from/to a cache should separate the behavior to connect to the cache server into another class.
      • E.g. print_formatted function should be broken into two: print and formatted.
      • E.g. on Django REST Framework, the CursorPagination class supported only a fixed page_size, because it didn't have the get_page_size method. This was changed by introducing the method.
      • E.g. the API is calling another lower-level one, but it isn't exposing some useful parameters the lower-level API supports.
      • E.g. Python's built-in sched.scheduler accepts timefunc and delayfunc, so the tests don't need to mock time.monotonic nor time.sleep and the clients can change those behaviors.
      • E.g. Celery supports both the @app.task decorator and a custom task class that inherits from celery.Task.
      • E.g. Django querysets support .extra to combine custom SQL with ORM-generated one. It also supports .raw, which allow completely raw SQL queries.
      • E.g. a database connection error should be the same if the API supports multiple database engines. This helps API clients to change engines without changing much code.
      • E.g. list.sort accepts key as the ranker function to calculate the order.

4. Safety

    • E.g. Don't show all if something isn't set. fields = None shouldn't mean all fields.
    • E.g. django-admin registry, which supports both register by function or by decorator.

5. Conclusion