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, acompose and sacompose instances will be correctly detected as coroutine functions:

>>> inspect.iscoroutinefunction(async_times_16)
True
>>> 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.1.tar.gz (5.7 kB view details)

Uploaded Source

Built Distributions

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

Uploaded Python 3.8

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

Uploaded Python 3.5

compose-1.6.1-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.1.tar.gz.

File metadata

  • Download URL: compose-1.6.1.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.1.tar.gz
Algorithm Hash digest
SHA256 c7983bc37f90c1051fd2c2dfff2a508e6af03aab3a4149f88b554dedd14d24be
MD5 44cbd280f0462c55ab574af2b3a336f1
BLAKE2b-256 b54fece69d8158c42ba0e6eec23b662ed23aff1d8c445004569aeea60f88dd77

See more details on using hashes here.

File details

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

File metadata

  • Download URL: compose-1.6.1-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.1-py38-none-any.whl
Algorithm Hash digest
SHA256 6226b7fc8dbe4d38b9d1a670173dd3137f9751ebeec4d6a41f9ed176b18104c0
MD5 650d3d301a7c4d4643ff61387da06810
BLAKE2b-256 ba0c34af187639279242ae883f0520e755d57b0fb88f7db1842818f7a024df3f

See more details on using hashes here.

File details

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

File metadata

  • Download URL: compose-1.6.1-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.1-py35-none-any.whl
Algorithm Hash digest
SHA256 92ca50449c3847389ce28c292df28970eb8ee554d8235a91b8dfa8cbae45e06a
MD5 fe0554f33a525d2beba2c3f16eee26c6
BLAKE2b-256 a04c3028f70f4910859639c462486384c08e46a41a305257b5cfa01e1997660d

See more details on using hashes here.

File details

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

File metadata

  • Download URL: compose-1.6.1-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.1-py2.py30-none-any.whl
Algorithm Hash digest
SHA256 15a940acbc6af046d063781ce3ef56f9c82134f4b3ede0446f696168104c8120
MD5 53b6f56da24113163f0dd77a0013d089
BLAKE2b-256 95b1e0924bc4e812be1732b7520c3a1796ef8e7bd915f175e2b5c060b1481c42

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