Skip to main content

Give Python functions your unsolicited input

Project description

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)

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

cold_call-0.1.0.tar.gz (11.5 kB view details)

Uploaded Source

Built Distribution

cold_call-0.1.0-py3-none-any.whl (6.7 kB view details)

Uploaded Python 3

File details

Details for the file cold_call-0.1.0.tar.gz.

File metadata

  • Download URL: cold_call-0.1.0.tar.gz
  • Upload date:
  • Size: 11.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/4.0.2 CPython/3.11.6

File hashes

Hashes for cold_call-0.1.0.tar.gz
Algorithm Hash digest
SHA256 ce860d4d8bcafd0100115f8666fe950339d349fb183359ded5d8033659b013bf
MD5 420dfc9356ceb336f9155441a5d0d2df
BLAKE2b-256 347a95da6f4546acd28f86452540af3f2dc9e9ce11479fd5938065fdd4a86b5c

See more details on using hashes here.

File details

Details for the file cold_call-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: cold_call-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 6.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/4.0.2 CPython/3.11.6

File hashes

Hashes for cold_call-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 9543165d18126c44464bae1d45e0c1f4dc9dae93f1fbfb62268fec3091c5aecd
MD5 dc94643995842ecb90527a33f5b22409
BLAKE2b-256 81fc7ea42e0dee1ceaf5754884e217763dbe1b777de95994ea119d5008fda7ef

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