Skip to main content

Reliable and introspectable async CLI action framework.

Project description

โš”๏ธ Falyx

Python License Async-Ready

Falyx is a battle-ready, introspectable CLI framework for building resilient, asynchronous workflows with:

  • โœ… Modular action chaining and rollback
  • ๐Ÿ” Built-in retry handling
  • โš™๏ธ Full lifecycle hooks (before, after, success, error, teardown)
  • ๐Ÿ“Š Execution tracing, logging, and introspection
  • ๐Ÿง™โ€โ™‚๏ธ Async-first design with Process support
  • ๐Ÿงฉ Extensible CLI menus, customizable bottom bars, and keyboard shortcuts

Built for developers who value clarity, resilience, and visibility in their terminal workflows.


โœจ Why Falyx?

Modern CLI tools deserve the same resilience as production systems. Falyx makes it easy to:

  • Compose workflows using Action, ChainedAction, or ActionGroup
  • Inject the result of one step into the next (last_result / auto_inject)
  • Handle flaky operations with retries, backoff, and jitter
  • Roll back safely on failure with structured undo logic
  • Add observability with timing, tracebacks, and lifecycle hooks
  • Run in both interactive and headless (scriptable) modes
  • Support config-driven workflows with YAML or TOML
  • Visualize tagged command groups and menu state via Rich tables

๐Ÿ”ง Installation

pip install falyx

Or install from source:

git clone https://github.com/rolandtjr/falyx.git
cd falyx
poetry install

โšก Quick Example

import asyncio
import random

from falyx import Falyx
from falyx.action import Action, ChainedAction

# A flaky async step that fails randomly
async def flaky_step():
    await asyncio.sleep(0.2)
    if random.random() < 0.5:
        raise RuntimeError("Random failure!")
    print("ok")
    return "ok"

# Create the actions
step1 = Action(name="step_1", action=flaky_step)
step2 = Action(name="step_2", action=flaky_step)

# Chain the actions
chain = ChainedAction(name="my_pipeline", actions=[step1, step2])

# Create the CLI menu
falyx = Falyx("๐Ÿš€ Falyx Demo")
falyx.add_command(
    key="R",
    description="Run My Pipeline",
    action=chain,
    preview_before_confirm=True,
    confirm=True,
    retry_all=True,
    spinner=True,
    style="cyan",
)

# Entry point
if __name__ == "__main__":
    asyncio.run(falyx.run())
$ python simple.py
                          ๐Ÿš€ Falyx Demo

           [R] Run My Pipeline
           [H] Help              [Y] History   [X] Exit

>
$ python simple.py run r
Command: 'R' โ€” Run My Pipeline
โ””โ”€โ”€ โ›“ ChainedAction 'my_pipeline'
    โ”œโ”€โ”€ โš™ Action 'step_1'
    โ”‚   โ†ป Retries: 3x, delay 1.0s, backoff 2.0x
    โ””โ”€โ”€ โš™ Action 'step_2'
        โ†ป Retries: 3x, delay 1.0s, backoff 2.0x
โ“ Confirm execution of R โ€” Run My Pipeline (calls `my_pipeline`)  [Y/n] > y
[2025-07-20 09:29:35] WARNING   Retry attempt 1/3 failed due to 'Random failure!'.
ok
[2025-07-20 09:29:38] WARNING   Retry attempt 1/3 failed due to 'Random failure!'.
ok

๐Ÿ“ฆ Core Features

  • โœ… Async-native Action, ChainedAction, ActionGroup, ProcessAction
  • ๐Ÿ” Retry policies with delay, backoff, jitter โ€” opt-in per action or globally
  • โ›“ Rollbacks and lifecycle hooks for chained execution
  • ๐ŸŽ›๏ธ Headless or interactive CLI powered by argparse + prompt_toolkit
  • ๐Ÿ“Š In-memory ExecutionRegistry with result tracking, timing, and tracebacks
  • ๐ŸŒ CLI menu construction via config files or Python
  • โšก Bottom bar toggle switches and counters with Ctrl+<key> shortcuts
  • ๐Ÿ” Structured confirmation prompts and help rendering
  • ๐Ÿชต Flexible logging: Rich console for devs, JSON logs for ops

๐Ÿงฐ Building Blocks

  • Action: A single unit of async (or sync) logic
  • ChainedAction: Execute a sequence of actions, with rollback and injection
  • ActionGroup: Run actions concurrently and collect results
  • ProcessAction: Use multiprocessing for CPU-bound workflows
  • Falyx: Interactive or headless CLI controller with history, menus, and theming
  • ExecutionContext: Metadata store per invocation (name, args, result, timing)
  • HookManager: Attach before, after, on_success, on_error, on_teardown

๐Ÿ” Logging

2025-07-20 09:29:32 [falyx] [INFO] Command 'R' selected.
2025-07-20 09:29:32 [falyx] [INFO] [run_key] Executing: R โ€” Run My Pipeline
2025-07-20 09:29:33 [falyx] [INFO] [my_pipeline] Starting -> ChainedAction(name=my_pipeline, actions=['step_1', 'step_2'], args=(), kwargs={}, auto_inject=False, return_list=False)()
2025-07-20 09:29:33 [falyx] [INFO] [step_1] Retrying (1/3) in 1.0s due to 'Random failure!'...
2025-07-20 09:29:35 [falyx] [WARNING] [step_1] Retry attempt 1/3 failed due to 'Random failure!'.
2025-07-20 09:29:35 [falyx] [INFO] [step_1] Retrying (2/3) in 2.0s due to 'Random failure!'...
2025-07-20 09:29:37 [falyx] [INFO] [step_1] Retry succeeded on attempt 2.
2025-07-20 09:29:37 [falyx] [INFO] [step_1] Recovered: step_1
2025-07-20 09:29:37 [falyx] [DEBUG] [step_1] status=OK duration=3.627s result='ok' exception=None
2025-07-20 09:29:37 [falyx] [INFO] [step_2] Retrying (1/3) in 1.0s due to 'Random failure!'...
2025-07-20 09:29:38 [falyx] [WARNING] [step_2] Retry attempt 1/3 failed due to 'Random failure!'.
2025-07-20 09:29:38 [falyx] [INFO] [step_2] Retrying (2/3) in 2.0s due to 'Random failure!'...
2025-07-20 09:29:40 [falyx] [INFO] [step_2] Retry succeeded on attempt 2.
2025-07-20 09:29:40 [falyx] [INFO] [step_2] Recovered: step_2
2025-07-20 09:29:40 [falyx] [DEBUG] [step_2] status=OK duration=3.609s result='ok' exception=None
2025-07-20 09:29:40 [falyx] [DEBUG] [my_pipeline] Success -> Result: 'ok'
2025-07-20 09:29:40 [falyx] [DEBUG] [my_pipeline] Finished in 7.237s
2025-07-20 09:29:40 [falyx] [DEBUG] [my_pipeline] status=OK duration=7.237s result='ok' exception=None
2025-07-20 09:29:40 [falyx] [DEBUG] [Run My Pipeline] status=OK duration=7.238s result='ok' exception=None

๐Ÿ“Š History Tracking

View full execution history:

> history
                                                   ๐Ÿ“Š Execution History

   Index   Name                           Start         End    Duration   Status        Result / Exception
 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
       0   step_1                      09:23:55    09:23:55      0.201s   โœ… Success    'ok'
       1   step_2                      09:23:55    09:24:03      7.829s   โŒ Error      RuntimeError('Random failure!')
       2   my_pipeline                 09:23:55    09:24:03      8.080s   โŒ Error      RuntimeError('Random failure!')
       3   Run My Pipeline             09:23:55    09:24:03      8.082s   โŒ Error      RuntimeError('Random failure!')

Inspect result by index:

> history --result-index 0
Action(name='step_1', action=flaky_step, args=(), kwargs={}, retry=True, rollback=False) ():
ok

Print last result includes tracebacks:

> history --last-result
Command(key='R', description='Run My Pipeline' action='ChainedAction(name=my_pipeline, actions=['step_1', 'step_2'],
args=(), kwargs={}, auto_inject=False, return_list=False)') ():
Traceback (most recent call last):
  File ".../falyx/command.py", line 291, in __call__
    result = await self.action(*combined_args, **combined_kwargs)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File ".../falyx/action/base_action.py", line 91, in __call__
    return await self._run(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File ".../falyx/action/chained_action.py", line 212, in _run
    result = await prepared(*combined_args, **updated_kwargs)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File ".../falyx/action/base_action.py", line 91, in __call__
    return await self._run(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File ".../falyx/action/action.py", line 157, in _run
    result = await self.action(*combined_args, **combined_kwargs)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File ".../falyx/examples/simple.py", line 15, in flaky_step
    raise RuntimeError("Random failure!")
RuntimeError: Random failure!

๐Ÿง  Design Philosophy

โ€œLike a phalanx: organized, resilient, and reliable.โ€

Falyx is designed for developers who donโ€™t just want CLI tools to run โ€” they want them to fail meaningfully, recover intentionally, and log clearly.


Project details


Release history Release notifications | RSS feed

This version

0.2.0

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

falyx-0.2.0.tar.gz (170.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

falyx-0.2.0-py3-none-any.whl (220.7 kB view details)

Uploaded Python 3

File details

Details for the file falyx-0.2.0.tar.gz.

File metadata

  • Download URL: falyx-0.2.0.tar.gz
  • Upload date:
  • Size: 170.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/2.1.3 CPython/3.13.5 Linux/6.19.14-arch1-1

File hashes

Hashes for falyx-0.2.0.tar.gz
Algorithm Hash digest
SHA256 c349e74b7d40884f0f1f4474b3c27970362382731fc3a3bf2472b9236da188c1
MD5 4ea02b5d89112742a2db9827bbe1e0cf
BLAKE2b-256 5895d4e5bd10f57b4ac4f6888e3d6efcb2138bde0e9f3b634252e111bc1ea626

See more details on using hashes here.

File details

Details for the file falyx-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: falyx-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 220.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/2.1.3 CPython/3.13.5 Linux/6.19.14-arch1-1

File hashes

Hashes for falyx-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 ed5d24ab1739234d8e33addd4a292de00e50d11f2ba203adc3f21232a656a277
MD5 fdf9c21ab332a1883dfd61fe57b656c2
BLAKE2b-256 1de39dee37b9c2e4391c302de14ef03440093e8db32b7e12bc5fcb1d4a49aa19

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page