Skip to main content

Define signatures to create beautiful APIs

Project description

Autosig

https://img.shields.io/pypi/v/autosig.svg https://img.shields.io/travis/piccolbo/autosig.svg https://codecov.io/gh/piccolbo/autosig/branch/master/graph/badge.svg Documentation Status Updates

Autosig helps you write good python 3 APIs. Go straight to the documentation. Install with pip install autosig. Python 3 only.

Motivation

When I look at a great API I always observe a great level of consistency: similarly named and ordered arguments at a syntactic level; similar defaults, range of allowable values etc. on the semantic side. When looking at the code, one doesn’t see these regularities represented very explicitly.

Imagine we are starting to develop a library with three entry points, map, reduce and filter:

from collections import Iterable


def map(function, iterable):
    assert callable(function)
    assert isinstance(iterable, Iterable)
    return (function(x) for x in iterable)


def reduce(function, iterable):
    total = next(iterable)
    for x in iterable:
        total = function(total, x)
    return total


def filter(iterable, fun):
    if not isinstance(iterable, Iterable):
        iterable = [iterable]
    if isinstance(fun, set):
        fun = lambda x: x in fun
    return (x for x in iterable if fun(x))

But this is hardly well crafted. The order and naming of arguments isn’t consistent. One function checks its argument right away. The next doesn’t. The third attempts certain conversions to try and work with arguments that are not iterables or functions. There are reasons to build strict or tolerant APIs, but it’s unlikely that mixing the two within the same API is a good idea, unless it’s done deliberately (for instance offering a strict and tolerant version of every function). It wouldn’t be difficult to fix these problems in this small API but we would end up with duplicated logic that we need to keep aligned for the foreseeable future. Let’s do it instead the autosig way:

from autosig import param, Signature, autosig, check
from collections import Iterable


def to_callable(x):
    return (lambda y: y in x) if isinstance(x, set) else x


def to_iterable(x):
    return x if isinstance(x, Iterable) else [x]


API_signature = Signature(
    function=param(converter=to_callable, validator=check(callable)),
    iterable=param(converter=to_iterable, validator=check(Iterable)))


@autosig(API_signature)
def map(function, iterable):
    return (function(x) for x in iterable)


@autosig(API_signature)
def reduce(function, iterable):
    total = next(iterable)
    for x in iterable:
        total = function(total, x)
    return total


@autosig(API_signature)
def filter(function, iterable):
    return (x for x in iterable if function(x))

Let’s go through it step by step. First we defined 2 simple conversion functions. This is a good first step independent of autosig. Next we create a signature object, with two parameters. These are intialized with objects that define the checking and conversion that needs to be done on those parameters, independent of which function is going to use that signature. check creates a function that uses its argument, a Callable or Iterable, to validate an argument. Finally, we repeat the definition of our three API function, attaching the signature just defined with a decorator and then skipping all the checking and conversion logic and going straight to the meat of the function!

At the cost of a little more code we have gained a lot:

  • Explicit definition of the desired API signature, in a single place — DRY principle;

  • association of that signature with API functions, checked at load time — no room for error;

  • uniform application of conversion and validation logic without repeating it;

autosig is the pro tool of the API designer! If you want to take a look at a real package that uses autosig, check out altair_recipes.

Features

  • Define reusable parameters with defaults, conversion and validation logic, documentation, preferred position in the signature and whether keyword-only.

  • Define reusable signatures as ordered maps from names to parameters.

  • Combine signatures to create complex ones on top of simple ones.

  • Decorate functions with their signatures. Enforced at load time. Conversion and validation logic executed at call time.

Credits

This package is heavily based on attrs. While that may change in the future, for now it must be said this is a thin layer over that, with a bit of reflection sprinkled over. It is, I suppose, a quite original direction to take attrs into.

This package was created with Cookiecutter and the elgertam/cookiecutter-pipenv project template, based on audreyr/cookiecutter-pypackage.

History

0.6.0 (2018-09-24)

  • Added check the quick validator generator. check(int) checks an argument is integer. check(\lambda x: x>0) checks an argument is positive. Behind the scenes it creates uses an assert statement which hopefully prints a useful message.

0.5.0 (2018-09-21)

  • All new API, many breaking changes (sorry)

  • signature decorator is gone

  • create signatures directly withe the Signature constructor (it is no longer a base class to inherit from)

  • do not use inheritance to define new signatures form old ones. It was a dead end as far as controlling the order of arguments. Use instead the + operator to combine two signatures, analogous to inheriting from one while adding new attributes.

  • the new approach gives control over order of arguments, allows to mix mandatory and default arguments in one signature yet allow to reuse it (“stick” new mandatory arguments in between the arguments of the old signature)

0.4.1 (2018-09-05)

  • Close abstraction holes revealing dependency on attr (which is gratefully acknowledged, but could be confusing).

0.3.1 (2018-08-30)

  • Improved docstring generation

0.3.0 (2018-08-30)

  • Compose docstring from param docstrings

0.2.3 (2018-08-28)

  • Better and passing tests.

0.2.2 (2018-08-27)

  • More stringent enforcement of signatures including defaults. Fixed build.

0.1.0 (2018-04-25)

  • First release on PyPI.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

autosig-0.6.0.tar.gz (21.1 kB view details)

Uploaded Source

Built Distribution

autosig-0.6.0-py2.py3-none-any.whl (6.9 kB view details)

Uploaded Python 2Python 3

File details

Details for the file autosig-0.6.0.tar.gz.

File metadata

  • Download URL: autosig-0.6.0.tar.gz
  • Upload date:
  • Size: 21.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/1.11.0 pkginfo/1.4.2 requests/2.19.1 setuptools/40.4.1 requests-toolbelt/0.8.0 tqdm/4.25.0 CPython/3.6.6

File hashes

Hashes for autosig-0.6.0.tar.gz
Algorithm Hash digest
SHA256 8568ce967ce6e225147bbe8fc387fa3d5be4f38323d5b5897ef5cee3203120d8
MD5 b24b1ec2bbd2a937a4f5a697f04e413c
BLAKE2b-256 efeb2e9e7fca1832e40985389f08a1b26a769a3fe3ead4ada49bc69deda553f0

See more details on using hashes here.

File details

Details for the file autosig-0.6.0-py2.py3-none-any.whl.

File metadata

  • Download URL: autosig-0.6.0-py2.py3-none-any.whl
  • Upload date:
  • Size: 6.9 kB
  • Tags: Python 2, Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/1.11.0 pkginfo/1.4.2 requests/2.19.1 setuptools/40.4.1 requests-toolbelt/0.8.0 tqdm/4.25.0 CPython/3.6.6

File hashes

Hashes for autosig-0.6.0-py2.py3-none-any.whl
Algorithm Hash digest
SHA256 bfbc1b2d9dee3c7cb22a6c0c2b6fc11f3338a214a950eea005d48fd5cf5d61ca
MD5 e451dabc030f08a15771ae09bab13812
BLAKE2b-256 956f2140c6c2501e5a810901c8cc7a60cfee99673f18f03283f7042ceada6629

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page