Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion core/src/stdlib/pyscript.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

56 changes: 11 additions & 45 deletions core/src/stdlib/pyscript/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,60 +92,26 @@ def when(target, *args, **kwargs):
elements = selector if isinstance(selector, list) else [selector]

def decorator(func):
if config["type"] == "mpy": # Is MicroPython?
sig = inspect.signature(func)
if sig.parameters:
if is_awaitable(func):

async def wrapper(*args, **kwargs):
"""
This is a very ugly hack to get micropython working because
`inspect.signature` doesn't exist. It may be actually better
to not try any magic for now and raise the error.
"""
try:
return await func(*args, **kwargs)

except TypeError as e:
if "takes" in str(e) and "positional arguments" in str(e):
return await func()
raise
async def wrapper(event):
return await func(event)

else:

def wrapper(*args, **kwargs):
"""
This is a very ugly hack to get micropython working because
`inspect.signature` doesn't exist. It may be actually better
to not try any magic for now and raise the error.
"""
try:
return func(*args, **kwargs)

except TypeError as e:
if "takes" in str(e) and "positional arguments" in str(e):
return func()
raise

wrapper = func
else:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

elif is_awaitable(func):
  # ...
else:
  # ...

maybe?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This conditional (and related else) is to check the number of args the receiving function expects.

TL;DR do you have:

def handler(event):
    ...

or

def handler():
    ...

Inside each branch is where the check for is_awaitable happens. Of course, it could have been done the other way around... but I didn't want to change the original code, just remove the "hack" for MicroPython now it's not needed.

Copy link
Copy Markdown
Contributor

@WebReflection WebReflection Oct 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand what it does and I've mentioned it was a nit ... but these two snippets are identical, the latter one is just (big imho) more logical:

current

def decorator(func):
    sig = inspect.signature(func)
    if sig.parameters:
        if is_awaitable(func):
            async def wrapper(event):
                return await func(event)
        else:
            wrapper = func
    else:
        if is_awaitable(func):
            async def wrapper(*args, **kwargs):
                return await func()
        # this is the last possible branch
        else:
            def wrapper(*args, **kwargs):
                return func()

proposed

def decorator(func):
    sig = inspect.signature(func)
    if sig.parameters:
        if is_awaitable(func):
            async def wrapper(event):
                return await func(event)
        else:
            wrapper = func
    elif is_awaitable(func):
        async def wrapper(*args, **kwargs):
            return await func()
    else:
        def wrapper(*args, **kwargs):
            return func()

anyway, as the logic is identical I am OK with this merged in, after all it might be a subjective style I just like to keep if/elses linear in the indentation whenever I can 😇

sig = inspect.signature(func)
if sig.parameters:
if is_awaitable(func):
# Function doesn't receive events.
if is_awaitable(func):

async def wrapper(event):
return await func(event)
async def wrapper(*args, **kwargs):
return await func()

else:
wrapper = func
else:
# Function doesn't receive events.
if is_awaitable(func):

async def wrapper(*args, **kwargs):
return await func()

else:

def wrapper(*args, **kwargs):
return func()
def wrapper(*args, **kwargs):
return func()

wrapper = wraps(func)(wrapper)
if isinstance(target, Event):
Expand Down
76 changes: 76 additions & 0 deletions core/tests/python/tests/test_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -358,3 +358,79 @@ def handler(result):
# The function should have been called when the whenable object was
# triggered.
assert counter == 1

def test_when_on_different_callables():
"""
The when function works with:

* Synchronous functions
* Asynchronous functions
* Inner functions
* Async inner functions
* Closure functions
* Async closure functions
"""

def func(x=1):
# A simple function.
return x

async def a_func(x=1):
# A simple async function.
return x

def make_inner_func():
# Creates a simple inner function.

def inner_func(x=1):
return x

return inner_func


def make_inner_a_func():
# Creates a simple async inner function.

async def inner_a_func(x=1):
return x

return inner_a_func


def make_closure():
# Creates a simple closure function.
a = 1

def closure_func(x=1):
return a + x

return closure_func


def make_a_closure():
# Creates a simple async closure function.
a = 1

async def closure_a_func(x=1):
return a + x

return closure_a_func


inner_func = make_inner_func()
inner_a_func = make_inner_a_func()
cl_func = make_closure()
cl_a_func = make_a_closure()


whenable = Event()

# Each of these should work with the when function.
when(whenable, func)
when(whenable, a_func)
when(whenable, inner_func)
when(whenable, inner_a_func)
when(whenable, cl_func)
when(whenable, cl_a_func)
# If we get here, everything worked.
assert True
Loading