Running effects.
run_sync: on the calling thread, with pyfect's own scheduler. Sleeps, forks, races andblockingall work;from_asyncdoes not (there is no asyncio loop).run_async: inside a running asyncio loop. Cancelling the awaiting task interrupts the fiber and waits for its finalizers.run_fork: start the effect and get itsFiberback at once, on the current fiber's run or the running asyncio loop.run_callbackis the same with an exit callback, returning a function that interrupts it.run_main: for a script's main program, like Effect'srunMain. Runs on a fresh asyncio loop; SIGINT and SIGTERM interrupt the fiber so finalizers run. It accepts effects that can fail, as Effect's does: the failure is logged through the loggers, andteardownturns the exit into the process status;default_teardowngives 0 on success, 130 when interrupted, or the error'sexit_code(TaggedError.exit_code, default 1).
Each of run_sync and run_async has an _exit variant that
returns the Exit instead of raising, and accepts effects that can fail.
The plain variants only accept effects whose errors are handled
(E = Never). Every run function except run_main takes a
context with the services the effect needs (Effect's run*With);
without one, R must be Never. Anything else is a type error at the
call.
Classes#
Functions#
run_sync_exit#
run_sync_exit(eff, *, context=_EMPTY) -> Exit[Any, Any]| Parameter | Type | Description |
|---|---|---|
eff | Effect[Any, Any, Any] | |
context | Context = _EMPTY |
Returns Exit[Any, Any]
def run_sync_exit(eff: Effect[Any, Any, Any], *, context: Context = _EMPTY) -> Exit[Any, Any]:
require_effect(eff, "eff")
scheduler = SyncScheduler()
fiber: Fiber[Any, Any] = new_root(scheduler, context)
fiber._start(eff)
return drive(scheduler, fiber)run_sync#
run_sync(eff, *, context=_EMPTY) -> Any| Parameter | Type | Description |
|---|---|---|
eff | Effect[Any, Never, Any] | |
context | Context = _EMPTY |
Returns Any
def run_sync(eff: Effect[Any, Never, Any], *, context: Context = _EMPTY) -> Any:
return unwrap(run_sync_exit(eff, context=context))drive#
drive(scheduler, fiber) -> Exit[A, E]Run scheduler on this thread until fiber is done. On Ctrl-C
the fiber is interrupted and its finalizers run before the
KeyboardInterrupt propagates; a second Ctrl-C aborts.
| Parameter | Type | Description |
|---|---|---|
scheduler | SyncScheduler | |
fiber | Fiber[A, E] |
Returns Exit[A, E]
def drive[A, E](scheduler: SyncScheduler, fiber: Fiber[A, E]) -> Exit[A, E]:
"""Run ``scheduler`` on this thread until ``fiber`` is done. On Ctrl-C
the fiber is interrupted and its finalizers run before the
``KeyboardInterrupt`` propagates; a second Ctrl-C aborts."""
try:
scheduler.run_until(lambda: fiber._exit is not None)
except KeyboardInterrupt:
fiber.unsafe_interrupt_as(None)
scheduler.run_until(lambda: fiber._exit is not None)
raise
ex = fiber._exit
assert ex is not None
return exrun_async_exit#
run_async_exit(eff, *, context=_EMPTY) -> Exit[Any, Any]| Parameter | Type | Description |
|---|---|---|
eff | Effect[Any, Any, Any] | |
context | Context = _EMPTY |
Returns Exit[Any, Any]
async def run_async_exit(
eff: Effect[Any, Any, Any], *, context: Context = _EMPTY
) -> Exit[Any, Any]:
require_effect(eff, "eff")
fiber: Fiber[Any, Any] = new_root(AsyncioScheduler.current(), context)
fiber._start(eff)
return await await_fiber(fiber)run_async#
run_async(eff, *, context=_EMPTY) -> Any| Parameter | Type | Description |
|---|---|---|
eff | Effect[Any, Never, Any] | |
context | Context = _EMPTY |
Returns Any
async def run_async(eff: Effect[Any, Never, Any], *, context: Context = _EMPTY) -> Any:
return unwrap(await run_async_exit(eff, context=context))run_fork#
run_fork(eff, *, context=_EMPTY, scheduler=None, uninterruptible=False) -> Fiber[Any, Any]Start eff on a fiber with no parent and return the fiber.
Called from a fiber, the new fiber joins that fiber's run and runs at
once, up to its first wait. Otherwise it runs on the running asyncio
loop, again at once. With neither, scheduler
runs it (driving it is up to the caller); without one this raises
NoRunError. Inside a run, scheduler is the fiber's
CurrentScheduler.
| Parameter | Type | Description |
|---|---|---|
eff | Effect[Any, Any, Any] | |
context | Context = _EMPTY | |
scheduler | Scheduler | None = None | |
uninterruptible | bool = False |
Returns Fiber[Any, Any]
def run_fork(
eff: Effect[Any, Any, Any],
*,
context: Context = _EMPTY,
scheduler: Scheduler | None = None,
uninterruptible: bool = False,
) -> Fiber[Any, Any]:
"""Start ``eff`` on a fiber with no parent and return the fiber.
Called from a fiber, the new fiber joins that fiber's run and runs at
once, up to its first wait. Otherwise it runs on the running asyncio
loop, again at once. With neither, ``scheduler``
runs it (driving it is up to the caller); without one this raises
``NoRunError``. Inside a run, ``scheduler`` is the fiber's
``CurrentScheduler``."""
require_effect(eff, "eff")
run, now = ambient_run(scheduler)
fiber: Fiber[Any, Any] = new_root(
run,
context,
scheduler=None if scheduler is run else scheduler,
uninterruptible=uninterruptible,
)
start_root(fiber, eff, now=now)
return fiberrun_callback#
run_callback(eff, *, context=_EMPTY, on_exit=None, scheduler=None, uninterruptible=False) -> Interruptorrun_fork, calling on_exit with the exit when the fiber is
done. Returns a function that interrupts the fiber.
| Parameter | Type | Description |
|---|---|---|
eff | Effect[Any, Any, Any] | |
context | Context = _EMPTY | |
on_exit | Callable[[Exit[Any, Any]], None] | None = None | |
scheduler | Scheduler | None = None | |
uninterruptible | bool = False |
Returns Interruptor
def run_callback(
eff: Effect[Any, Any, Any],
*,
context: Context = _EMPTY,
on_exit: Callable[[Exit[Any, Any]], None] | None = None,
scheduler: Scheduler | None = None,
uninterruptible: bool = False,
) -> Interruptor:
"""``run_fork``, calling ``on_exit`` with the exit when the fiber is
done. Returns a function that interrupts the fiber."""
fiber = run_fork(eff, context=context, scheduler=scheduler, uninterruptible=uninterruptible)
if on_exit is not None:
fiber.unsafe_add_observer(on_exit)
def interrupt(by: int | None = None, /) -> None:
fiber.unsafe_interrupt_as(by)
return interruptdefault_teardown#
default_teardown(ex) -> intEffect's defaultTeardown: 0 on success, 130 when only
interrupted, otherwise the squashed error's exit_code (1 unless a
TaggedError sets it).
| Parameter | Type | Description |
|---|---|---|
ex | Exit[Any, Any] |
Returns int
def default_teardown(ex: Exit[Any, Any]) -> int:
"""Effect's ``defaultTeardown``: 0 on success, 130 when only
interrupted, otherwise the squashed error's ``exit_code`` (1 unless a
``TaggedError`` sets it)."""
if isinstance(ex, Success):
return 0
if ex.cause.has_interrupts_only:
return 130
error = ex.cause.squash()
return error.exit_code if isinstance(error, TaggedError) else 1run_main#
run_main(eff, *, teardown=default_teardown, disable_error_reporting=False) -> ARun as the program's main fiber; see the module docstring.
Returns the value when the program succeeds and teardown gives 0;
otherwise ends the process with teardown's status. A failure that
is not only an interrupt is first logged at ERROR through the
default loggers (its pretty cause), as Effect's runMain does,
unless disable_error_reporting or the squashed error's
error_reported is False.
| Parameter | Type | Description |
|---|---|---|
eff | Effect[A, E, Never] | |
teardown | Teardown = default_teardown | |
disable_error_reporting | bool = False |
Returns A
def run_main[A, E](
eff: Effect[A, E, Never],
*,
teardown: Teardown = default_teardown,
disable_error_reporting: bool = False,
) -> A:
"""Run as the program's main fiber; see the module docstring.
Returns the value when the program succeeds and ``teardown`` gives 0;
otherwise ends the process with ``teardown``'s status. A failure that
is not only an interrupt is first logged at ``ERROR`` through the
default loggers (its pretty cause), as Effect's ``runMain`` does,
unless ``disable_error_reporting`` or the squashed error's
``error_reported`` is ``False``."""
require_effect(eff, "eff")
main = eff if disable_error_reporting else eff.tap_cause(_log_failure)
ex = asyncio.run(_run_main_exit(main))
code = teardown(ex)
if isinstance(ex, Success) and code == 0:
return ex.value
raise SystemExit(code)await_fiber#
await_fiber(fiber) -> Exit[A, E]Wait on the running loop for fiber, whose run is the loop's.
Cancelling the awaiting task interrupts the fiber and waits for it.
| Parameter | Type | Description |
|---|---|---|
fiber | Fiber[A, E] |
Returns Exit[A, E]
async def await_fiber[A, E](fiber: Fiber[A, E]) -> Exit[A, E]:
"""Wait on the running loop for ``fiber``, whose run is the loop's.
Cancelling the awaiting task interrupts the fiber and waits for it."""
done: asyncio.Future[Exit[A, E]] = asyncio.get_running_loop().create_future()
fiber.unsafe_add_observer(done.set_result)
try:
return await asyncio.shield(done)
except asyncio.CancelledError:
fiber.unsafe_interrupt_as(None)
await done
raiseunwrap#
unwrap(ex) -> AThe value, or raise the squashed error (with the full cause as a note when there is more than one reason).
| Parameter | Type | Description |
|---|---|---|
ex | Exit[A, Any] |
Returns A
def unwrap[A](ex: Exit[A, Any]) -> A:
"""The value, or raise the squashed error (with the full cause as a
note when there is more than one reason)."""
if isinstance(ex, Success):
return ex.value
error = ex.cause.squash()
if len(ex.cause.reasons) > 1:
error.add_note("Full cause:\n" + ex.cause.pretty())
raise error