Skip to main content

cold-call

PyPI - Version PyPI - Python Version Black Pre-Commit Enabled Ruff

Give Python functions your unsolicited input. cold-call is implemented in pure Python, fully type-annotated, and has zero runtime dependencies.


Table of Contents

Installation

pip install cold-call

License

cold-call is distributed under the terms of the MIT license.

Usage

cold-call enables you to throw any arguments or keyword arguments that you like at an arbitrary function, and call that function using the keys which match the corresponding parameter names of the function to provide values. For example:

from cold_call import cold_call


def func(a: int, b: str) -> None:
    print(a, b)


data = {"a": 5, "b": "foo"}

# prints "5 foo"
cold_call(func, **data)

On its own, this isn't very interesting - the same can be achieved with the builtin unpack operator (**{"a": 1, ...}):

# prints "5 foo"
func(**data)

However, cold_call enables you to pass a additional keys, which aren't in the function's parameter spec:

data["c"] = 73
# prints "5 foo"
cold_call(func, **data)

# TypeError: func() got an unexpected keyword argument 'c'
func(**data)

This is similar to JavaScript's ability to destructure an object passed into the function using the function's parameter spec. The following two code examples are equivalent:

// JavaScript
const foo({name, age}) => {
    console.log(`${name}: ${age}`);
};

// prints "Joe: 30"
foo({
    name: "Joe",
    age: 30,
    birthday: "01/06/1990"
})
# Python
def foo(name: str, age: int) -> None:
    print(f"{name}: {age}")


# prints "Joe: 30"
cold_call(
    foo,
    name="Joe",
    age=30,
    birthday="01/06/1990",
)

The cold_call function can be called with positional and keyword arguments; the values of the keyword arguments are used in preference to those in the positional arguments, so if a keyword argument matches the name of a parameter that is declared in the function as positional-only, it will be used in preference to any positional arguments.

NOTE: if a parameter can be passed as either a positional or keyword argument, it will be passed to the called function positionally. This is to avoid certain edge cases where Python treats a call to a function as providing multiple values for the same parameter (see Calls).

For example:

def foo(name: str, age: int) -> None:
    print(f"{name}: {age}")


# prints "Joe: 42"
cold_call(foo, "Tim", 21, name="Joe", age=42)

Note that positional arguments to cold_call are always passed to the function positionally, so you should always prefer keyword arguments unless the function you want to call requires positional-only arguments.

Additional positional or keyword arguments to cold_call are ignored, unless the function specifies variadic positional or keyword arguments (*args or **kwargs); in this case, any "left over" positional arguments are used to fill *args, and any "left over" keyword arguments are used to fill **kwargs:

def foo(name: str, *meals: str, age: int, **attrs) -> None:
    print(f"{name}, age: {age}")
    print(f"likes: {', '.join(meals)}")
    print(attrs)


# prints:
# Joe, 42
# likes: pizza, burgers, ice-cream
# {"hobbies": ["tennis"], "city": "London"}
cold_call(
    foo,
    "Joe",
    "pizza",
    "burgers",
    "ice-cream",
    hobbies=["tennis"],
    city="London",
    age=42,
)

cold_call also works with functions that have more specific signatures:

NOTE: here 5 is used as the b argument, as the a argument is explicitly specified by keyword.

def picky(
    a: str,
    /,
    b: int,
    *,
    c: bool,
) -> int:
    print(f"{a=}, {b=}, {c=}")
    return b * 2


# prints "a=gotcha, b=5, c=False"
x = cold_call(
    picky,
    5,
    a="gotcha",
    c=False,
)
assert x == 10

ColdCaller

cold-call also provides a convenience class for use with the standard-library dataclasses. This class implements a single method, call, which allows you to run cold_call on a function with the data that the dataclass instance stores:

from dataclasses import dataclass

from cold_call import ColdCaller


def user_action(name: str) -> None:
    print(f"user {name} is doing things!")


def is_authorized(name: str, is_admin: bool) -> bool:
    if not is_admin:
        print(f"forbidden: user {name} is not an admin")
    return is_admin and name != "Steve"  # Steve is banned


@dataclass
class User(ColdCaller):
    name: str
    age: int
    is_admin: bool = False


joe = User(name="Joe", age=30)

# prints "user Joe is doing things!"
joe.call(user_action)

# prints "forbidden: user Joe is not an admin"
joe.call(is_authorized)

# prints "True"
print(joe.call(is_authorized, is_admin=True))

cold_callable

Lastly, for convenience cold-call exports a decorator, cold_callable, which can be used to wrap a function so that it can always accept arbitrary input without erroring:

from cold_call import cold_callable


@cold_callable
def foo(name: str, age: int) -> None:
    print(f"{name}: {age}")


# prints "Joe: 42"
foo("Tim", 21, name="Joe", age=42)

Release files for cold-call 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for cold-call 0.1.0
File Size Uploaded
cold_call-0.1.0.tar.gz 11.5 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for cold-call 0.1.0
File Interpreter ABI Platform
cold_call-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 18.2 kB

Release files / cold_call-0.1.0.tar.gz

Download URL cold_call-0.1.0.tar.gz
Size 11.5 kB
Tags Source
SHA-256 checksum
How to use checksums
ce860d4d8bcafd0100115f8666fe950339d349fb183359ded5d8033659b013bf
BLAKE2b-256 checksum
How to use checksums
347a95da6f4546acd28f86452540af3f2dc9e9ce11479fd5938065fdd4a86b5c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/4.0.2 CPython/3.11.6

Release files / cold_call-0.1.0-py3-none-any.whl

Download URL cold_call-0.1.0-py3-none-any.whl
Size 6.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
9543165d18126c44464bae1d45e0c1f4dc9dae93f1fbfb62268fec3091c5aecd
BLAKE2b-256 checksum
How to use checksums
81fc7ea42e0dee1ceaf5754884e217763dbe1b777de95994ea119d5008fda7ef
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/4.0.2 CPython/3.11.6

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page