Package: cold-call
cold-callGive Python functions your unsolicited input.cold-callis implemented in pure
Python, fully type-annotated, and has zero runtime dependencies.Table of ContentsInstallationLicenseUsageInstallationpip install cold-callLicensecold-callis distributed under the terms of theMITlicense.Usagecold-callenables you to throw any arguments or keyword arguments that you like
at an arbitrary function, and call that function using thekeyswhich match
the correspondingparameternames of the function to provide values. For example:fromcold_callimportcold_calldeffunc(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_callenables you to pass aadditionalkeys,
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 todestructure an object passed
into the function using the function's parameter spec.
The following two code examples are equivalent:// JavaScriptconstfoo({name,age})=>{console.log(`${name}:${age}`);};// prints "Joe: 30"foo({name:"Joe",age:30,birthday:"01/06/1990"})# Pythondeffoo(name:str,age:int)->None:print(f"{name}:{age}")# prints "Joe: 30"cold_call(foo,name="Joe",age=30,birthday="01/06/1990",)Thecold_callfunction 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 bepassedto 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 (seeCalls).For example:deffoo(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 tocold_callare 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 tocold_callareignored, unless
the function specifiesvariadic positional or keyword arguments(*argsor**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:deffoo(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_callalso works with functions that have more specific signatures:NOTE: here5is used as thebargument, as theaargument is explicitly
specified by keyword.defpicky(a:str,/,b:int,*,c:bool,)->int:print(f"{a=},{b=},{c=}")returnb*2# prints "a=gotcha, b=5, c=False"x=cold_call(picky,5,a="gotcha",c=False,)assertx==10ColdCallercold-callalso provides a convenience class for use with the standard-librarydataclasses.
This class implements a single method,call, which allows you to runcold_callon a function with the data that the dataclass instance stores:fromdataclassesimportdataclassfromcold_callimportColdCallerdefuser_action(name:str)->None:print(f"user{name}is doing things!")defis_authorized(name:str,is_admin:bool)->bool:ifnotis_admin:print(f"forbidden: user{name}is not an admin")returnis_adminandname!="Steve"# Steve is banned@dataclassclassUser(ColdCaller):name:strage:intis_admin:bool=Falsejoe=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_callableLastly, for conveniencecold-callexports a decorator,cold_callable, which
can be used to wrap a function so that it can always accept arbitrary input
without erroring:fromcold_callimportcold_callable@cold_callabledeffoo(name:str,age:int)->None:print(f"{name}:{age}")# prints "Joe: 42"foo("Tim",21,name="Joe",age=42)