Extracting the function and arguments from a coroutine - python-asyncio

Is it possible to extract the function and arguments of a coroutine object in python3.6?
Context: currently I have something like this:
async def func(*args):
...
ret = await remotely(func, x, y)
Under the hood, remotely pickles func, x, and y, scp's that to a different server, where it unpickles them, executes func(x,y), pickles the result, scp's that back, and finally unpickles it into ret.
This API feels distasteful to me, I'd prefer to have:
ret = await remotely(func(x, y))
I could do this if I could pickle the coroutine object represented by func(x, y), but when I tried that, I get:
TypeError: can't pickle coroutine objects
So my alternate hope is that I can extract f, x, and y from f(x, y), hence this question.

So when you do ret = await remotely(func(x, y)), you actually construct the coroutine object for func. Fortunately, you can extract the information you need from coroutine objects, which you can send over for remote execution.
So first of all you can get the function name using the __qualname__ attribute. This will give you the fully qualified name, i.e. if the coroutine is nested, it will get you the full path to your function.
Next, you can extract the argument values from the frame object of the coroutine.
So this is how your remote function would look like
async def remote(cr):
# Get the function name
fname = cr.__qualname__
# Get the argument values
frame = cr.cr_frame
args = frame.f_locals # dict object
result = await ... # your scp stuff
return result
There is just one caveat. You should indicate that the function should be only used the way that you have posted, i.e.
ret = await remotely(func(x, y))
...in other words, the coroutine should be "fresh", and not half-way executed (which is almost not possible if you initiate it right passing it to remote). Otherwise, the f_locals value might include any other local variable that is defined before any awaits.

Found no clean solution. But we can find function as a referrer to coroutine's code object:
import gc
import inspect
def get_function_from_coroutine(coroutine: Coroutine) -> Callable:
referrers = gc.get_referrers(coroutine.cr_code)
return next(filter(lambda ref: inspect.isfunction(ref), referrers))
def get_kwargs_from_coroutine(coroutine: Coroutine) -> dict[str, Any]:
return coroutine.cr_frame.f_locals
async def foo(a: str, b: int):
return a * b
coro = foo("test", b=2)
print(get_function_from_coroutine(coro)) # <function foo at 0x7ff61ece9820>
print(get_kwargs_from_coroutine(coro)) # {'a': 'test', 'b': 2}

Related

async python functions call in loop using pydash flow

I have two async function like below
async def get_job_details(key: str) -> dict:
...
return data
async def get_resource(data: dict) -> dict:
...
return data
I want to call both functions in loop using pydash flow like below
py_.flow(await get_job_details, await get_resource)(key)
Getting error TypeError: object function can't be used in 'await' expression
This works fine without async. Even if I call them without flow that works too
data = await get_job_details(key)
data = await get_resource(data)
But I want to use some looping here , since there is a possibility of having more function call and must be called sequentially since they are dependent .

How can I get argument values for a given lambda from the outside, without explicitly returning its `binding`?

I want to be able to run a lambda and get at its argument values (the values of a and b below).
I can achieve this by explicitly having the lambda return a binding, and then getting the values out of this binding:
fun = lambda { |a, b = Time.now| binding }
fun_binding = fun.call(123)
puts "A is #{fun_binding.local_variable_get(:a)} and B is #{fun_binding.local_variable_get(:b)}"
# Outputs e.g.: A is 123 and B is 2022-04-29 20:14:07 +0200
Is it possible to get these values without running any code inside the lambda body? I.e. could I do
fun = lambda { |a, b = Time.now| }
fun.call(123)
and still get the a and b values somehow from outside the lambda?
(To provide context – I'm asking because I've experimented with this as a shortcut syntax for instantiating objects with ivars/readers automatically assigned. At this stage I'm more curious about what's possible than what is advisable – so I invite any solutions, advisable or not…)
I'm not sure how advisable this is, but you can use the TracePoint class with the :b_call event to grab the lambda's binding:
fun = lambda { |a, b = Time.now| }
fun_binding = nil
TracePoint.trace(:b_call) do |tp|
fun_binding = tp.binding
tp.disable
end
fun.call(123)
puts "A is #{fun_binding.local_variable_get(:a)} and B is #{fun_binding.local_variable_get(:b)}"
This is not a perfect solution and will fail if one of the argument default values enters a block on it's own (since the default values are evaluated before the :b_call event is fired for the lambda), but that could be fixed with some additional filtering inside the TracePoint handler.

what is ContractPromise? near-sdk-as to near-sdk-rs comparison

I've found this function in an assemblyscript project for a NEAR contract:
export function assert_single_promise_success(): void {
const x = ContractPromise.getResults()
assert(x.length == 1, "Expected exactly one promise result")
assert(x[0].succeeded, "Expected PromiseStatus to be successful")
}
What does ContractPromise.getResults() do exactly? How should implement the same thing in rust?
here is something similar in Rust from one of the examples in the SDK repo
require!(env::promise_results_count() == 2);
let data0: Vec<u8> = match env::promise_result(0) {
PromiseResult::Successful(x) => x,
_ => env::panic_str("Promise with index 0 failed"),
};
I'm going to give an answer, comments taken directly from the implementation of ContractPromise.getResults(), which can be found here. The implementation also has an example on how to use the function, which may be useful.
Method to receive async (one or multiple) results from the remote
contract in the callback.
#returns An array of results based on the number of promises the
callback was created on. If the callback using then was scheduled
only on one result, then one result will be returned.

Why isn't Python NewType compatible with isinstance and type?

This doesn't seem to work:
from typing import NewType
MyStr = NewType("MyStr", str)
x = MyStr("Hello World")
isinstance(x, MyStr)
I don't even get False, but TypeError: isinstance() arg 2 must be a type or tuple of types because MyStr is a function and isinstance wants one or more type.
Even assert type(x) == MyStr or is MyStr fails.
What am I doing wrong?
Cross-reference: inheritance from str or int
Even more detailed in the same question: https://stackoverflow.com/a/2673802/1091677
If you would like to subclass Python's str, you would need to do the following way:
class MyStr(str):
# Class instances construction in Python follows this two-step call:
# First __new__, which allocates the immutable structure,
# Then __init__, to set up the mutable part.
# Since str in python is immutable, it has no __init__ method.
# All data for str must be set at __new__ execution, so instead
# of overriding __init__, we override __new__:
def __new__(cls, *args, **kwargs):
return str.__new__(cls, *args, **kwargs)
Then:
x = MyStr("Hello World")
isinstance(x, MyStr)
returns True as expected
As at python 3.10...
I'd speculate that the answer to
"Why isn't Python NewType compatible with isinstance and type?"
... is "It is a limitation of NewType".
I'd speculate that the answer to
"What am I doing wrong?"
... is "nothing". You are assuming NewType makes a new runtime type, it appears that it doesn't.
And for what it's worth, I wish it did work.
maybe you want a type that is methods like str does but is not a str?
A simple way to get that effect is just:
class MyStr:
value:str
def __init__(self,value:str):
self.value=value
pass
... but that means using all the string methods is "manual", e.g.
x=MyStr('fred')
x.value.startswith('fr')
... you could use #dataclass to add compare etc.
This is not a one-size-fits-all answer, but it might suit your application.
then make that simple
class MyStr:
value:str
def __init__(self,value:str):
self.value=value
...generic like Str in (incomplete) https://github.com/urnest/urnest/blob/master/xju/newtype.py
... and you can write:
class MyStrType:pass; class MyOtherStrType:pass
class MyStr(Str[MyStrType]):
pass
class MyOtherStr(Str[MyOtherStrType]):
pass
x=MyStr('fred')
y=MyOtherStr('fred')
x < y # ! mypy error distinct types MyStr and MyOtherStr
That's what I was after, which might be what you were after? I have to provide Int,Str separately but in my experience distinct int, str, float, bool, bytes types give a lot of readability and error-rejection leverage. I will add Float, Bool, Bytes to xju.newtype soon and give them all the full set of methods.
looks might have been "fixed" in python 3.10:
https://docs.python.org/3/library/typing.html?highlight=typing#newtype
says:
Changed in version 3.10: NewType is now a class rather than a function. There is some additional runtime cost when calling NewType over a regular function. However, this cost will be reduced in 3.11.0.
I don't have 3.10 handy as I write this to try your example.

web2py: using function in LOAD (ajax)

Is it possible to use =LOAD(...) with a function rather then controller/function string
e.g:
Controller:
def test():
print "test"
def index():
return dict(test=test)
View:
{{=LOAD(test, ajax=True)}}
rather then:
View:
{{=LOAD('controller', 'test', ajax=True)}}
The main reason being, I want to use lambda/generated functions which cannot be accessed this way.
No. But not because the syntax is not supported, because it is logically impossible: the LOAD() is executed in a different http request than the one in which the lambda would be executed and therefore the latter would be undefined. Moreover to perform the ajax callback, the called function must have a name, cannot be a lambda. We could come up with a creative use of cache so that LOAD stores the lambda in cache:
def callback():
""" a generic callback """
return cache.ram(request.args(0),lambda:None,None)(**request.vars)
def LOAD2(f,vars={}):
""" a new load function """
import uuid
u = str(uuid.uuid4())
cache.ram(u,lambda f=f:f,0)
return LOAD(request.controller,'callback',args=u,vars=vars,ajax=True)
def index():
""" example of usage """
a = LOAD2(lambda:'hello world')
return dict(a=a)
But this would only work with cache.ram and would require periodic cache cleanup.

Resources