Skip to main content

The classic ``compose``, with all the Pythonic features.

Project description

The classic compose, with all the Pythonic features.

This compose follows the lead of functools.partial and returns callable compose objects which:

  • have a regular and unambiguous repr,

  • retain correct signature introspection,

  • allow introspection of the composed callables,

  • can be type-checked,

  • can be weakly referenced,

  • can have attributes,

  • will merge when nested, and

  • can be pickled (if all composed callables can be pickled).

For async/await support, different variants of compose are included.

Versioning

This library’s version numbers follow the SemVer 2.0.0 specification.

Installation

pip install compose

For static type checking, also install the type hint stubs:

pip install compose-stubs

Usage

Basics

Import compose:

>>> from compose import compose

All the usual function composition you know and love:

>>> def double(x):
...     return x * 2
...
>>> def increment(x):
...     return x + 1
...
>>> double_then_increment = compose(increment, double)
>>> double_then_increment(1)
3

Of course any number of functions can be composed:

>>> def double(x):
...     return x * 2
...
>>> times_eight = compose(double, double, double)
>>> times_16 = compose(double, double, double, double)

We still get the correct signature introspection:

>>> def f(a, b, c=0, **kwargs):
...     pass
...
>>> def g(x):
...     pass
...
>>> g_of_f = compose(g, f)
>>> import inspect
>>> inspect.signature(g_of_f)
<Signature (a, b, c=0, **kwargs)>

And we can inspect all the composed callables:

>>> g_of_f.functions  # in order of execution:
(<function f at 0x...>, <function g at 0x...>)

compose instances flatten when nested:

>>> times_eight_times_two = compose(double, times_eight)
>>> times_eight_times_two.functions == times_16.functions
True

When programmatically inspecting arbitrary callables, we can check if we are looking at a compose instance:

>>> isinstance(g_of_f, compose)
True

compose raises a TypeError when called with no arguments or with any non-callable arguments:

>>> compose()
Traceback (most recent call last):
    ...
TypeError: compose() needs at least one argument
>>> compose(increment, 'oops', increment)
Traceback (most recent call last):
    ...
TypeError: compose() arguments must be callable

async/await

We can compose async code by using acompose:

>>> import asyncio
>>> from compose import acompose
>>>
>>> async def get_data():
...     # pretend this data is fetched from some async API
...     await asyncio.sleep(0)
...     return 42
...
>>> get_and_double_data = acompose(double, get_data)
>>> asyncio.run(get_and_double_data())
84

acompose can compose any number of async and regular functions, in any order:

>>> async def async_double(x):
...     await asyncio.sleep(0)
...     return x * 2
...
>>> async_times_16 = acompose(async_double, double, async_double, double)
>>> asyncio.run(async_times_16(1))
16

acompose instances always return awaitable values, even if none of the composed functions are async:

>>> awaitable_times_16 = acompose(double, double, double, double)
>>> asyncio.run(async_times_16(1))
16

sacompose is like acompose, but sacompose instances return an awaitable value only if any of the composed functions return an awaitable value:

>>> from compose import sacompose
>>>
>>> regular_times_4 = sacompose(double, double)
>>> awaitable_times_4 = sacompose(double, async_double)
>>>
>>> regular_times_4(1)
4
>>> asyncio.run(awaitable_times_4(1))
4

acompose and sacompose instances flatten when nested:

>>> acompose(f, acompose(f, f)).functions == (f, f, f)
True
>>> acompose(sacompose(f, f), f).functions == (f, f, f)
True
>>> sacompose(acompose(f, f), f).functions == (f, f, f)
True
>>> sacompose(f, sacompose(f, f)).functions == (f, f, f)
True

But compose instances don’t flatten when nested into acompose and sacompose, and vice versa:

>>> acompose(g_of_f).functions
(compose(<function g at 0x...>, <function f at 0x...>),)
>>> sacompose(g_of_f).functions
(compose(<function g at 0x...>, <function f at 0x...>),)
>>> compose(acompose(g, f)).functions
(acompose(<function g at 0x...>, <function f at 0x...>),)
>>> compose(sacompose(g, f)).functions
(sacompose(<function g at 0x...>, <function f at 0x...>),)

compose, acompose, and sacompose instances are all distinct types:

>>> isinstance(g_of_f, compose)
True
>>> isinstance(g_of_f, (acompose, sacompose))
False
>>> isinstance(async_times_16, acompose)
True
>>> isinstance(async_times_16, (compose, sacompose))
False
>>> isinstance(awaitable_times_4, sacompose)
True
>>> isinstance(awaitable_times_4, (compose, acompose))
False

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

compose-1.5.0.tar.gz (5.5 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

compose-1.5.0-py38-none-any.whl (4.8 kB view details)

Uploaded Python 3.8

compose-1.5.0-py35-none-any.whl (4.8 kB view details)

Uploaded Python 3.5

compose-1.5.0-py2.py30-none-any.whl (4.4 kB view details)

Uploaded Python 2Python 3.0

File details

Details for the file compose-1.5.0.tar.gz.

File metadata

  • Download URL: compose-1.5.0.tar.gz
  • Upload date:
  • Size: 5.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/4.0.2 CPython/3.11.4

File hashes

Hashes for compose-1.5.0.tar.gz
Algorithm Hash digest
SHA256 1b26cb59c7ab007306097dfa34f2f14cae840ed56469ff4923ba5959e2af997f
MD5 bfe813c3cc8f37963324e0637142c7e1
BLAKE2b-256 26da8d301747facc1553a4f23d3f02910794c8949bd9f11b7373f6eeab538430

See more details on using hashes here.

File details

Details for the file compose-1.5.0-py38-none-any.whl.

File metadata

  • Download URL: compose-1.5.0-py38-none-any.whl
  • Upload date:
  • Size: 4.8 kB
  • Tags: Python 3.8
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/4.0.2 CPython/3.11.4

File hashes

Hashes for compose-1.5.0-py38-none-any.whl
Algorithm Hash digest
SHA256 b8a0bdf74f348fc0214ff2cf53d612242b562e90c525cf9802b6da054b6233f0
MD5 f261418060ff78aa77d9b53672b1e8ce
BLAKE2b-256 0fe3573c6b889fe0df1f72f4e0ca1030dd597003397fd361b39bb92d68643b15

See more details on using hashes here.

File details

Details for the file compose-1.5.0-py35-none-any.whl.

File metadata

  • Download URL: compose-1.5.0-py35-none-any.whl
  • Upload date:
  • Size: 4.8 kB
  • Tags: Python 3.5
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/4.0.2 CPython/3.11.4

File hashes

Hashes for compose-1.5.0-py35-none-any.whl
Algorithm Hash digest
SHA256 a7da30cf5fde6031779af2264637a354bdb1a7b8c4522b0b23ddd36fde80a325
MD5 a6e99a80256b03d56f8cee99d617c04a
BLAKE2b-256 d9c257818ad3da31ff8aff47e9e51d84feef68b5fcb2824038bd99234e689f4e

See more details on using hashes here.

File details

Details for the file compose-1.5.0-py2.py30-none-any.whl.

File metadata

  • Download URL: compose-1.5.0-py2.py30-none-any.whl
  • Upload date:
  • Size: 4.4 kB
  • Tags: Python 2, Python 3.0
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/4.0.2 CPython/3.11.4

File hashes

Hashes for compose-1.5.0-py2.py30-none-any.whl
Algorithm Hash digest
SHA256 978597e74828db6fe62aca0aea2af7943834a5088ddd117f90a9732205e4df29
MD5 f5aeb5d594097f6d65ef548fae3eedaa
BLAKE2b-256 464f0ba00763bfff112f43ee7159a216acfebd4648b34dc7c7b8d8c335bf0e82

See more details on using hashes here.

Supported by

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