Skip to main content

A decorator that coalesces concurrent function calls

Project description

Concurrent Coalesce

A Python decorator that coalesces concurrent function calls.

Description

This package provides a decorator that helps manage concurrent function calls by coalescing them, preventing redundant executions when multiple calls occur simultaneously.

Installation

pip install concurrent-coalesce

Features

  • Prevents redundant concurrent execution of functions with identical arguments
  • Works with both synchronous threads and asynchronous code (Python 3.5+)
  • Supports custom key functions for unhashable inputs and controlling how calls are grouped
  • Compatible with Python 2.7 and Python 3.5+
  • No external dependencies

Usage

See examples

Basic Usage

from concurrent_coalesce import coalesce

# Synchronous usage
@coalesce()
def fetch_data(user_id):
    print("Fetching data for user", user_id)
    response = requests.get("https://api.example.com/users", params={"user_id": user_id})
    response.raise_for_status()
    return response.json()

When multiple threads call fetch_data() with the same user_id concurrently, only one will actually execute the function. All others will wait for the result and receive the same return value.

Async Usage

Async support requires Python 3.5+

from concurrent_coalesce import coalesce

# Async usage (Python 3.5+)
@coalesce()
async def fetch_data_async(user_id):
    print("Fetching data for user", user_id)
    async with aiohttp.ClientSession() as session:
        async with session.get("https://api.example.com/users", params={"user_id": user_id}) as response:
            response.raise_for_status()
            return await response.json()

When multiple coroutines call fetch_data_async() with the same user_id concurrently, only one will actually execute the function. All others will wait for the result and receive the same return value.

Custom Key Function

The primary purpose of key_func is to handle unhashable inputs. By default, the decorator uses the function arguments as a key, which requires them to be hashable. When your function receives unhashable arguments (like lists or dictionaries), you can use key_func to convert them into hashable values.

You can also use key_func to customize how arguments are grouped for coalescing:

# user_ids is a list (unhashable), so we convert it to a sorted tuple (hashable)
# This ensures that [1, 2] and [2, 1] are treated as the same request
@coalesce(key_func=lambda user_ids, **kwargs: tuple(sorted(user_ids)))
def fetch_multiple_users(user_ids, include_history=False):
    response = requests.get("https://api.example.com/users", params={
        "user_ids": user_ids,
        "include_history": include_history
    })
    response.raise_for_status()
    return response.json()

How It Works

The coalesce decorator:

  1. Intercepts function calls and generates a key based on the function arguments
  2. For the first call with a given key, the function executes normally
  3. Subsequent calls with the same key (before the first call completes) wait for the result
  4. All callers receive the same return value or exception
  5. After completion, the next call will trigger a new execution

For synchronous functions, the result is returned directly. For coroutines (Python 3.5+), an asyncio.Task is returned that can be awaited.

sequenceDiagram
  autonumber
  participant T1 as Thread 1
  participant T2 as Thread 2
  participant C as Decorator
  participant F as fetch_data()

  T1 ->> F : Call to fetch_data()
  activate C
  activate F
  T2 ->> C : Call to fetch_data()
  Note right of C : Thread 2 is blocked while<br/>Thread 1 processes fetch_data()
  F ->> T1 : Return from fetch_data()
  deactivate F
  C ->> T2 : Return from fetch_data()
  deactivate C

API Reference

@coalesce(key_func=None, *args)

A decorator that coalesces concurrent calls to a function with the same arguments.

The decorator can be used in two ways:

  1. As a decorator @coalesce()
  2. As a direct function call: coalesce(my_function)

Parameters:

  • key_func: Optional callable that takes *args, **kwargs and returns a hashable key. Defaults to using (tuple(args), frozenset(kwargs.items())).
  • *args: If provided, must be a single callable that will be decorated. If no args are provided, returns the decorator function. If multiple args are provided, raises TypeError.

Returns:

  • For synchronous functions: The result of the function call
  • For coroutines (Python 3.5+): An asyncio.Task that can be awaited

Raises:

  • TypeError: If key_func is not callable or if multiple arguments are provided in *args

Development

Building the Package

To build the package locally:

# Install development dependencies
pip install -r requirements-dev.txt

# Build the package
./build.sh

# Or manually:
python -m build

Testing the Build

After building, you can test the package locally:

# Install the built package
pip install dist/*.whl

# Test the import
python test_import.py

Running Tests

# Run all tests
pytest

# Run with coverage
pytest --cov=concurrent_coalesce

License

MIT License

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

concurrent_coalesce-0.4.1.tar.gz (7.8 kB view details)

Uploaded Source

Built Distributions

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

concurrent_coalesce-0.4.1-py3-none-any.whl (6.7 kB view details)

Uploaded Python 3

concurrent_coalesce-0.4.1-py2-none-any.whl (6.6 kB view details)

Uploaded Python 2

File details

Details for the file concurrent_coalesce-0.4.1.tar.gz.

File metadata

  • Download URL: concurrent_coalesce-0.4.1.tar.gz
  • Upload date:
  • Size: 7.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for concurrent_coalesce-0.4.1.tar.gz
Algorithm Hash digest
SHA256 fdca0f75ce5f00f9e98cdd30a7fad68cdf4a575a6aea68087dcbf592a6502de2
MD5 91784d6793ac88659009c050eb59bdda
BLAKE2b-256 13396d0142f8468210db218b5fec1526288998a439afd8602e56dffc4912221f

See more details on using hashes here.

Provenance

The following attestation bundles were made for concurrent_coalesce-0.4.1.tar.gz:

Publisher: pypi.yml on claytonsingh/concurrent-coalesce-python

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file concurrent_coalesce-0.4.1-py3-none-any.whl.

File metadata

File hashes

Hashes for concurrent_coalesce-0.4.1-py3-none-any.whl
Algorithm Hash digest
SHA256 06a34477999a9e687612aa5eddd1f5f686f391c67feb76f47c3da1904382652e
MD5 6f63dffb7b8782f61cfbc119b4fed019
BLAKE2b-256 fe9b6f6fcacab40c1bd753c42551178087388a0a1f339e8abc6bbda030d726bd

See more details on using hashes here.

Provenance

The following attestation bundles were made for concurrent_coalesce-0.4.1-py3-none-any.whl:

Publisher: pypi.yml on claytonsingh/concurrent-coalesce-python

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file concurrent_coalesce-0.4.1-py2-none-any.whl.

File metadata

File hashes

Hashes for concurrent_coalesce-0.4.1-py2-none-any.whl
Algorithm Hash digest
SHA256 7ce3c7b149ccb51a46a0e43be02c5fcf492203eae0e6adb0fcecdb3c4cc1e13e
MD5 9d2eda298a9fbd296319d22aea51e344
BLAKE2b-256 f93132cb917d9f11baaab1506ef10706e2fae1f37aa433eddcb544f4f2b52891

See more details on using hashes here.

Provenance

The following attestation bundles were made for concurrent_coalesce-0.4.1-py2-none-any.whl:

Publisher: pypi.yml on claytonsingh/concurrent-coalesce-python

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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