While working on #589 I noticed that now there are three APIs for entering and exiting wakepy modes, but the current_mode() does not always make sense:
Context Manager syntax
current_mode() does not make any sense with the context manager syntax
Example: Plain context manager
from wakepy import keep
# Does not make sense
with keep.running():
mode = current_mode()
print(mode.active_method)
# Better: Use explicit "with X as Y" syntax instead
with keep.running() as mode:
print(mode.active_method)
Example 2: Context manager + subfunction
from wakepy import keep
def do_something()
with keep.running():
long_task()
def long_task():
mode = current_mode()
print(mode.active_method)
this does not make sense since you could as well do this (Example 3)
from wakepy import keep
def do_something()
with keep.running() as mode:
long_task(mode)
def long_task(mode):
print(mode.active_method)
This is much nicer as this is explicit (which is better than implicit). In addition, it's explicit that this function needs a mode as the input. Note that in Example 2
If there are no Modes active in the call stack, raises a NoCurrentModeError
and in reality, you would need to do something like:
def long_task():
try:
mode = current_mode()
print(mode.active_method)
except NoCurrentModeError:
pass
so better would be:
def long_task(mode=None):
if mode:
print(mode.active_method)
In addition to guiding towards better code, dropping support for current_mode() for context manager syntax makes the wakepy source code much simpler, and less likely to have bugs in some corner cases (say, including async.Tasks/threads). Less to test and less to worry about.
Explicit Mode.activate() or Mode.enter()
It's also clear that with explicit Mode.activate() or Mode.enter() syntax, there is no need for current_mode().
from wakepy import keep
mode = keep.running()
mode.enter()
mode = current_mode() # Not possible (or technically could be, but not implemented) + does not make any sense.
print(mode.active_method)
mode.exit()
Decorator syntax
The decorator syntax is more interesting, and it is basically the reason why I added current_mode() to wakepy 1.0.0.
from wakepy import keep
# Only place where current_mode() could make sense
@keep.running
def somefunc()
mode = current_mode()
print(mode.active_method)
but also here guaranteeing that the correct mode in case if nested functions, async.Tasks and threads might not be so simple.
Alternative 1: pass mode as argument when asked. Similar to bind=True in Celery or pass_context in Click.
from wakepy import keep
@keep.running(pass_mode=True)
def somefunc(mode)
print(mode.active_method)
I thought about the syntax in Alternative 1, but I'm not sure if this is the most intuitive way of handling the problem. The reader should understand that "pass_mode" means literally that the somefunc should be able to take an extra argument. It's not clear should the argument be the first or the last. When using the decorated function, one should not pass anything for "mode", but skip the parameter. Also the typing might become a challenge.
Alternative 2: Using lifecycle hooks.
- First: What is the motivation of someone getting the Mode handle? It's most likely for logging / messaging purposes. This can be achieved also using lifecycle hooks.
- Second: The code using the mode (for reacting to lifecycle events) can be taken away from somefunc, and somefunc can be 100% business logic.
- Third: No need to have extra input parameters / change the function signature of the decorated function.
def on_enter(mode):
print(mode.active_method)
@keep.running(on_enter=on_enter)
def somefunc():
# business logic only
-> New ticket for lifecycle hooks: #599
While working on #589 I noticed that now there are three APIs for entering and exiting wakepy modes, but the current_mode() does not always make sense:
Context Manager syntax
current_mode()does not make any sense with the context manager syntaxExample: Plain context manager
Example 2: Context manager + subfunction
this does not make sense since you could as well do this (Example 3)
This is much nicer as this is explicit (which is better than implicit). In addition, it's explicit that this function needs a mode as the input. Note that in Example 2
and in reality, you would need to do something like:
so better would be:
In addition to guiding towards better code, dropping support for
current_mode()for context manager syntax makes the wakepy source code much simpler, and less likely to have bugs in some corner cases (say, including async.Tasks/threads). Less to test and less to worry about.Explicit Mode.activate() or Mode.enter()
It's also clear that with explicit Mode.activate() or Mode.enter() syntax, there is no need for current_mode().
Decorator syntax
The decorator syntax is more interesting, and it is basically the reason why I added current_mode() to wakepy 1.0.0.
but also here guaranteeing that the correct mode in case if nested functions, async.Tasks and threads might not be so simple.
Alternative 1: pass mode as argument when asked. Similar to
bind=Truein Celery orpass_contextin Click.I thought about the syntax in Alternative 1, but I'm not sure if this is the most intuitive way of handling the problem. The reader should understand that "pass_mode" means literally that the
somefuncshould be able to take an extra argument. It's not clear should the argument be the first or the last. When using the decorated function, one should not pass anything for "mode", but skip the parameter. Also the typing might become a challenge.Alternative 2: Using lifecycle hooks.
-> New ticket for lifecycle hooks: #599