Create action submodule, add various examples

This commit is contained in:
2025-05-13 20:07:31 -04:00
parent 87a56ac40b
commit 2bdca72e04
39 changed files with 956 additions and 177 deletions

View File

@ -0,0 +1,48 @@
import asyncio
from falyx import Falyx
from falyx.action import ActionFactoryAction, ChainedAction, HTTPAction, SelectionAction
# Selection of a post ID to fetch (just an example set)
post_selector = SelectionAction(
name="Pick Post ID",
selections=["1", "2", "3", "4", "5"],
title="Choose a Post ID to submit",
prompt_message="Post ID > ",
show_table=True,
)
# Factory that builds and executes the actual HTTP POST request
def build_post_action(post_id) -> HTTPAction:
print(f"Building HTTPAction for Post ID: {post_id}")
return HTTPAction(
name=f"POST to /posts (id={post_id})",
method="POST",
url="https://jsonplaceholder.typicode.com/posts",
json={"title": "foo", "body": "bar", "userId": int(post_id)},
)
post_factory = ActionFactoryAction(
name="Build HTTPAction from Post ID",
factory=build_post_action,
inject_last_result=True,
inject_into="post_id",
preview_kwargs={"post_id": "100"},
)
# Wrap in a ChainedAction
chain = ChainedAction(
name="Submit Post Flow",
actions=[post_selector, post_factory],
auto_inject=True,
)
flx = Falyx()
flx.add_command(
key="S",
description="Submit a Post",
action=chain,
)
asyncio.run(flx.run())

View File

@ -0,0 +1,10 @@
"""config_loading.py"""
from falyx.config import loader
flx = loader("falyx.yaml")
if __name__ == "__main__":
import asyncio
asyncio.run(flx.run())

22
examples/falyx.yaml Normal file
View File

@ -0,0 +1,22 @@
commands:
- key: P
description: Pipeline Demo
action: pipeline_demo.pipeline
tags: [pipeline, demo]
help_text: Run Demployment Pipeline with retries.
- key: G
description: Run HTTP Action Group
action: http_demo.action_group
tags: [http, demo]
- key: S
description: Select a file
action: file_select.sf
tags: [file, select, demo]
- key: M
description: Menu Demo
action: menu_demo.menu
tags: [menu, demo]
help_text: Run a menu demo with multiple options.

171
examples/falyx_demo.py Normal file
View File

@ -0,0 +1,171 @@
"""
Falyx CLI Framework
Copyright (c) 2025 rtj.dev LLC.
Licensed under the MIT License. See LICENSE file for details.
"""
import asyncio
import random
from argparse import Namespace
from falyx.action import Action, ActionGroup, ChainedAction
from falyx.falyx import Falyx
from falyx.parsers import FalyxParsers, get_arg_parsers
from falyx.version import __version__
class Foo:
def __init__(self, flx: Falyx) -> None:
self.flx = flx
async def build(self):
await asyncio.sleep(1)
print("✅ Build complete!")
return "Build complete!"
async def test(self):
await asyncio.sleep(1)
print("✅ Tests passed!")
return "Tests passed!"
async def deploy(self):
await asyncio.sleep(1)
print("✅ Deployment complete!")
return "Deployment complete!"
async def clean(self):
print("🧹 Cleaning...")
await asyncio.sleep(1)
print("✅ Clean complete!")
return "Clean complete!"
async def build_package(self):
print("🔨 Building...")
await asyncio.sleep(1)
print("✅ Build finished!")
return "Build finished!"
async def package(self):
print("📦 Packaging...")
await asyncio.sleep(1)
print("✅ Package complete!")
return "Package complete!"
async def run_tests(self):
print("🧪 Running tests...")
await asyncio.sleep(random.randint(1, 3))
print("✅ Tests passed!")
return "Tests passed!"
async def run_integration_tests(self):
print("🔗 Running integration tests...")
await asyncio.sleep(random.randint(1, 3))
print("✅ Integration tests passed!")
return "Integration tests passed!"
async def run_linter(self):
print("🧹 Running linter...")
await asyncio.sleep(random.randint(1, 3))
print("✅ Linter passed!")
return "Linter passed!"
async def run(self):
await self.flx.run()
def parse_args() -> Namespace:
parsers: FalyxParsers = get_arg_parsers()
return parsers.parse_args()
async def main() -> None:
"""Build and return a Falyx instance with all your commands."""
args = parse_args()
flx = Falyx(
title="🚀 Falyx CLI",
cli_args=args,
columns=5,
welcome_message="Welcome to Falyx CLI!",
exit_message="Goodbye!",
)
foo = Foo(flx)
# --- Bottom bar info ---
flx.bottom_bar.columns = 3
flx.bottom_bar.add_toggle_from_option("V", "Verbose", flx.options, "verbose")
flx.bottom_bar.add_toggle_from_option("U", "Debug Hooks", flx.options, "debug_hooks")
flx.bottom_bar.add_static("Version", f"Falyx v{__version__}")
# --- Command actions ---
# --- Single Actions ---
flx.add_command(
key="B",
description="Build project",
action=Action("Build", foo.build),
tags=["build"],
spinner=True,
spinner_message="📦 Building...",
)
flx.add_command(
key="T",
description="Run tests",
action=Action("Test", foo.test),
tags=["test"],
spinner=True,
spinner_message="🧪 Running tests...",
)
flx.add_command(
key="D",
description="Deploy project",
action=Action("Deploy", foo.deploy),
tags=["deploy"],
spinner=True,
spinner_message="🚀 Deploying...",
)
# --- Build pipeline (ChainedAction) ---
pipeline = ChainedAction(
name="Full Build Pipeline",
actions=[
Action("Clean", foo.clean),
Action("Build", foo.build_package),
Action("Package", foo.package),
],
)
flx.add_command(
key="P",
description="Run Build Pipeline",
action=pipeline,
tags=["build", "pipeline"],
spinner=True,
spinner_message="🔨 Running build pipeline...",
spinner_type="line",
)
# --- Test suite (ActionGroup) ---
test_suite = ActionGroup(
name="Test Suite",
actions=[
Action("Unit Tests", foo.run_tests),
Action("Integration Tests", foo.run_integration_tests),
Action("Lint", foo.run_linter),
],
)
flx.add_command(
key="G",
description="Run All Tests",
action=test_suite,
tags=["test", "parallel"],
spinner=True,
spinner_type="line",
)
await foo.run()
if __name__ == "__main__":
try:
asyncio.run(main())
except (KeyboardInterrupt, EOFError):
pass

26
examples/file_select.py Normal file
View File

@ -0,0 +1,26 @@
import asyncio
from falyx import Falyx
from falyx.action import SelectFileAction
from falyx.action.types import FileReturnType
sf = SelectFileAction(
name="select_file",
suffix_filter=".py",
title="Select a YAML file",
prompt_message="Choose > ",
return_type=FileReturnType.TEXT,
columns=3,
)
flx = Falyx()
flx.add_command(
key="S",
description="Select a file",
action=sf,
help_text="Select a file from the current directory",
)
if __name__ == "__main__":
asyncio.run(flx.run())

67
examples/http_demo.py Normal file
View File

@ -0,0 +1,67 @@
import asyncio
from rich.console import Console
from falyx import ActionGroup, Falyx
from falyx.action import HTTPAction
from falyx.hook_manager import HookType
from falyx.hooks import ResultReporter
console = Console()
action_group = ActionGroup(
"HTTP Group",
actions=[
HTTPAction(
name="Get Example",
method="GET",
url="https://jsonplaceholder.typicode.com/posts/1",
headers={"Accept": "application/json"},
retry=True,
),
HTTPAction(
name="Post Example",
method="POST",
url="https://jsonplaceholder.typicode.com/posts",
headers={"Content-Type": "application/json"},
json={"title": "foo", "body": "bar", "userId": 1},
retry=True,
),
HTTPAction(
name="Put Example",
method="PUT",
url="https://jsonplaceholder.typicode.com/posts/1",
headers={"Content-Type": "application/json"},
json={"id": 1, "title": "foo", "body": "bar", "userId": 1},
retry=True,
),
HTTPAction(
name="Delete Example",
method="DELETE",
url="https://jsonplaceholder.typicode.com/posts/1",
headers={"Content-Type": "application/json"},
retry=True,
),
],
)
reporter = ResultReporter()
action_group.hooks.register(
HookType.ON_SUCCESS,
reporter.report,
)
flx = Falyx("HTTP Demo")
flx.add_command(
key="G",
description="Run HTTP Action Group",
action=action_group,
spinner=True,
)
if __name__ == "__main__":
asyncio.run(flx.run())

113
examples/menu_demo.py Normal file
View File

@ -0,0 +1,113 @@
import asyncio
import time
from falyx import Falyx
from falyx.action import Action, ActionGroup, ChainedAction, MenuAction, ProcessAction
from falyx.menu import MenuOption, MenuOptionMap
# Basic coroutine for Action
async def greet_user():
print("👋 Hello from a regular Action!")
await asyncio.sleep(0.5)
return "Greeted user."
# Chain of tasks
async def fetch_data():
print("📡 Fetching data...")
await asyncio.sleep(1)
return "data123"
async def process_data(last_result):
print(f"⚙️ Processing: {last_result}")
await asyncio.sleep(1)
return f"processed({last_result})"
async def save_data(last_result):
print(f"💾 Saving: {last_result}")
await asyncio.sleep(1)
return f"saved({last_result})"
# Parallel tasks
async def fetch_users():
print("👥 Fetching users...")
await asyncio.sleep(1)
return ["alice", "bob", "carol"]
async def fetch_logs():
print("📝 Fetching logs...")
await asyncio.sleep(2)
return ["log1", "log2"]
# CPU-bound task (simulate via blocking sleep)
def heavy_computation():
print("🧠 Starting heavy computation...")
time.sleep(3)
print("✅ Finished computation.")
return 42
# Define actions
basic_action = Action("greet", greet_user)
chained = ChainedAction(
name="data-pipeline",
actions=[
Action("fetch", fetch_data),
Action("process", process_data, inject_last_result=True),
Action("save", save_data, inject_last_result=True),
],
auto_inject=True,
)
parallel = ActionGroup(
name="parallel-fetch",
actions=[
Action("fetch-users", fetch_users),
Action("fetch-logs", fetch_logs),
],
)
process = ProcessAction(name="compute", action=heavy_computation)
# Menu setup
menu = MenuAction(
name="main-menu",
title="Choose a task to run",
menu_options=MenuOptionMap(
{
"1": MenuOption("Run basic Action", basic_action),
"2": MenuOption("Run ChainedAction", chained),
"3": MenuOption("Run ActionGroup (parallel)", parallel),
"4": MenuOption("Run ProcessAction (heavy task)", process),
}
),
)
flx = Falyx(
title="🚀 Falyx Menu Demo",
welcome_message="Welcome to the Menu Demo!",
exit_message="Goodbye!",
columns=2,
never_prompt=False,
)
flx.add_command(
key="M",
description="Show Menu",
action=menu,
logging_hooks=True,
)
if __name__ == "__main__":
asyncio.run(flx.run())

78
examples/pipeline_demo.py Normal file
View File

@ -0,0 +1,78 @@
import asyncio
from falyx import Action, ActionGroup, ChainedAction
from falyx import ExecutionRegistry as er
from falyx import ProcessAction
from falyx.hook_manager import HookType
from falyx.retry import RetryHandler, RetryPolicy
# Step 1: Fast I/O-bound setup (standard Action)
async def checkout_code():
print("📥 Checking out code...")
await asyncio.sleep(0.5)
# Step 2: CPU-bound task (ProcessAction)
def run_static_analysis():
print("🧠 Running static analysis (CPU-bound)...")
total = 0
for i in range(10_000_000):
total += i % 3
return total
# Step 3: Simulated flaky test with retry
async def flaky_tests():
import random
await asyncio.sleep(0.3)
if random.random() < 0.3:
raise RuntimeError("❌ Random test failure!")
print("🧪 Tests passed.")
return "ok"
# Step 4: Multiple deploy targets (parallel ActionGroup)
async def deploy_to(target: str):
print(f"🚀 Deploying to {target}...")
await asyncio.sleep(0.2)
return f"{target} complete"
def build_pipeline():
retry_handler = RetryHandler(RetryPolicy(max_retries=3, delay=0.5))
# Base actions
checkout = Action("Checkout", checkout_code)
analysis = ProcessAction("Static Analysis", run_static_analysis)
tests = Action("Run Tests", flaky_tests)
tests.hooks.register(HookType.ON_ERROR, retry_handler.retry_on_error)
# Parallel deploys
deploy_group = ActionGroup(
"Deploy to All",
[
Action("Deploy US", deploy_to, args=("us-west",)),
Action("Deploy EU", deploy_to, args=("eu-central",)),
Action("Deploy Asia", deploy_to, args=("asia-east",)),
],
)
# Full pipeline
return ChainedAction("CI/CD Pipeline", [checkout, analysis, tests, deploy_group])
pipeline = build_pipeline()
# Run the pipeline
async def main():
pipeline = build_pipeline()
await pipeline()
er.summary()
await pipeline.preview()
if __name__ == "__main__":
asyncio.run(main())

View File

@ -1,7 +1,7 @@
from rich.console import Console
from falyx import Falyx, ProcessAction
from falyx.themes.colors import NordColors as nc
from falyx.themes import NordColors as nc
console = Console()
falyx = Falyx(title="🚀 Process Pool Demo")

View File

@ -0,0 +1,22 @@
import asyncio
from falyx.selection import (
SelectionOption,
prompt_for_selection,
render_selection_dict_table,
)
menu = {
"A": SelectionOption("Run diagnostics", lambda: print("Running diagnostics...")),
"B": SelectionOption("Deploy to staging", lambda: print("Deploying...")),
}
table = render_selection_dict_table(
title="Main Menu",
selections=menu,
)
key = asyncio.run(prompt_for_selection(menu.keys(), table))
print(f"You selected: {key}")
menu[key.upper()].value()

90
examples/shell_example.py Executable file
View File

@ -0,0 +1,90 @@
#!/usr/bin/env python
import asyncio
from falyx import Action, ChainedAction, Falyx
from falyx.action import ShellAction
from falyx.hook_manager import HookType
from falyx.hooks import ResultReporter
from falyx.utils import setup_logging
# Setup logging
setup_logging()
fx = Falyx("🚀 Falyx Demo")
e = ShellAction("Shell", "echo Hello, {}!")
fx.add_command(
key="R",
description="Echo a message",
action=e,
)
s = ShellAction("Ping", "ping -c 1 {}")
fx.add_command(
key="P",
description="Ping a host",
action=s,
)
async def a1(last_result):
return f"Hello, {last_result}"
async def a2(last_result):
return f"World! {last_result}"
reporter = ResultReporter()
a1 = Action("a1", a1, inject_last_result=True)
a1.hooks.register(
HookType.ON_SUCCESS,
reporter.report,
)
a2 = Action("a2", a2, inject_last_result=True)
a2.hooks.register(
HookType.ON_SUCCESS,
reporter.report,
)
async def normal():
print("Normal")
return "Normal"
async def annotate(last_result):
return f"Annotated: {last_result}"
async def whisper(last_result):
return last_result.lower()
c1 = ChainedAction(
name="ShellDemo",
actions=[
# host,
ShellAction("Ping", "ping -c 1 {}"),
Action("Annotate", annotate),
Action("Whisper", whisper),
],
auto_inject=True,
)
fx.add_command(
key="C",
description="Run a chain of actions",
action=c1,
)
async def main():
await fx.run()
asyncio.run(main())

52
examples/submenu.py Normal file
View File

@ -0,0 +1,52 @@
import asyncio
import random
from falyx import Action, ChainedAction, Falyx
from falyx.utils import setup_logging
setup_logging()
# 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!")
return "ok"
step1 = Action(name="step_1", action=flaky_step, retry=True)
step2 = Action(name="step_2", action=flaky_step, retry=True)
# 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,
logging_hooks=True,
preview_before_confirm=True,
confirm=True,
)
# Create a submenu
submenu = Falyx("Submenu")
submenu.add_command(
key="T",
description="Test",
action=lambda: "test",
logging_hooks=True,
preview_before_confirm=True,
confirm=True,
)
falyx.add_submenu(
key="S",
description="Submenu",
submenu=submenu,
)
if __name__ == "__main__":
asyncio.run(falyx.run())