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)
>>> async_times_4 = sacompose(double, async_double)
>>>
>>> regular_times_4(1)
4
>>> asyncio.run(async_times_4(1))
4

If inspect.markcoroutinefunction is available, acompose and sacompose instances will be correctly detected as coroutine functions:

>>> inspect.iscoroutinefunction(async_times_16)
True
>>> inspect.iscoroutinefunction(awaitable_times_16)
True
>>> inspect.iscoroutinefunction(regular_times_4)
False
>>> inspect.iscoroutinefunction(async_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(async_times_4, sacompose)
True
>>> isinstance(async_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.2.tar.gz (5.7 kB view details)

Uploaded Source

Built Distributions

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

Uploaded Python 3.8

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

Uploaded Python 3.5

compose-1.6.2-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.2.tar.gz.

File metadata

  • Download URL: compose-1.6.2.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.2.tar.gz
Algorithm Hash digest
SHA256 c943fa8284c1cb3892925388862e203c9888484e4ce3fd5171506769690d8b4d
MD5 a46e982c0c887ff5cc0893697e1f3ef4
BLAKE2b-256 d832bac364dddb79b58d5df429bb4f14a217e5d679f272eb16df8f68143ec1e0

See more details on using hashes here.

File details

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

File metadata

  • Download URL: compose-1.6.2-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.2-py38-none-any.whl
Algorithm Hash digest
SHA256 5074c7c36bea8d073130398b905410e606a1f75e7f7bd8754f73980028509d83
MD5 00527e1e7b1c64d5d0b5dd7e09799fa1
BLAKE2b-256 ccb2166a868ceb985939320afd32f5b8c3911bb0f56fb190dfc97a721bfb5c4a

See more details on using hashes here.

File details

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

File metadata

  • Download URL: compose-1.6.2-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.2-py35-none-any.whl
Algorithm Hash digest
SHA256 c8b58b563594199ca40c864bd44e094392eaa1c4f4492ca8591bd54582b1f093
MD5 6d445e3f78699e7b4a3c574a4a36da94
BLAKE2b-256 2308f510f8522265766b988293ef4ddbd0e4396e51c920b08bcd3b268acac644

See more details on using hashes here.

File details

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

File metadata

  • Download URL: compose-1.6.2-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.2-py2.py30-none-any.whl
Algorithm Hash digest
SHA256 e704e66df7cb8888d6c30e46491f94da80d38b2d45511d138dafaa865386fd7d
MD5 0a2e4d3fdcc9fe6e97406309b5cebcab
BLAKE2b-256 0b9fe65ede617a93729dd35ceddfe798a6edbf8ed5036df2a552e0e46c7e78c2

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