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(awaitable_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

If inspect.markcoroutinefunction (added in Python 3.12) is available, sacompose instances work correctly with inspect.iscoroutinefunction:

>>> inspect.iscoroutinefunction(regular_times_4)
False
>>> inspect.iscoroutinefunction(awaitable_times_4)
True

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.6.0.tar.gz (5.7 kB view details)

Uploaded Source

Built Distributions

compose-1.6.0-py38-none-any.whl (5.0 kB view details)

Uploaded Python 3.8

compose-1.6.0-py35-none-any.whl (5.0 kB view details)

Uploaded Python 3.5

compose-1.6.0-py2.py30-none-any.whl (4.6 kB view details)

Uploaded Python 2 Python 3.0

File details

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

File metadata

  • Download URL: compose-1.6.0.tar.gz
  • Upload date:
  • Size: 5.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/5.1.1 CPython/3.12.6

File hashes

Hashes for compose-1.6.0.tar.gz
Algorithm Hash digest
SHA256 12de9aaf42ed4d13e3d17c1b5d45bdd90723e18241ca6f5f8f28ffd305dbb468
MD5 dbddf47973e6577b2bf9329fd9b56762
BLAKE2b-256 4d62b793f9c68033c9502c06c35549f1a703aadc37743cdce4056c7f39dbc1de

See more details on using hashes here.

File details

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

File metadata

  • Download URL: compose-1.6.0-py38-none-any.whl
  • Upload date:
  • Size: 5.0 kB
  • Tags: Python 3.8
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/5.1.1 CPython/3.12.6

File hashes

Hashes for compose-1.6.0-py38-none-any.whl
Algorithm Hash digest
SHA256 99798fafb8bd6c7cfa7fbd087b161c9848c843716446def40a54e7112808f8e2
MD5 bf91f5e431ab5d9b920e9149fe363864
BLAKE2b-256 700529125ab0b994564751e36b0a327afe65852e37998f80c98ac76b48677012

See more details on using hashes here.

File details

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

File metadata

  • Download URL: compose-1.6.0-py35-none-any.whl
  • Upload date:
  • Size: 5.0 kB
  • Tags: Python 3.5
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/5.1.1 CPython/3.12.6

File hashes

Hashes for compose-1.6.0-py35-none-any.whl
Algorithm Hash digest
SHA256 36faff55a5d649c0e77deb9bf1f80bc3abe959d306b5bf98bd372ad00e4c78ab
MD5 99dbadbeb93b304f39c1d3276c7f411e
BLAKE2b-256 f8a38fafdbe9159aaf3cc213437cbae824f97ddcc7bea3e36befae0d624f4605

See more details on using hashes here.

File details

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

File metadata

  • Download URL: compose-1.6.0-py2.py30-none-any.whl
  • Upload date:
  • Size: 4.6 kB
  • Tags: Python 2, Python 3.0
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/5.1.1 CPython/3.12.6

File hashes

Hashes for compose-1.6.0-py2.py30-none-any.whl
Algorithm Hash digest
SHA256 abed7e3981269992e37b1c12aa4cd466e4b5ce8dcb4d50688a97a6efc5e2d28f
MD5 afa0f8196b3727f46444580a1b3d6665
BLAKE2b-256 eb6a29a8e3096c236cf83d16c7ddfabe4ba907592183e2b4332b0c4e09e0dbdb

See more details on using hashes here.

Supported by

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