Create action submodule, add various examples
This commit is contained in:
parent
87a56ac40b
commit
2bdca72e04
|
@ -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())
|
|
@ -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())
|
|
@ -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.
|
|
@ -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
|
|
@ -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())
|
|
@ -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())
|
|
@ -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())
|
|
@ -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())
|
|
@ -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")
|
||||
|
|
|
@ -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()
|
|
@ -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())
|
|
@ -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())
|
|
@ -7,7 +7,7 @@ Licensed under the MIT License. See LICENSE file for details.
|
|||
|
||||
import logging
|
||||
|
||||
from .action import Action, ActionGroup, ChainedAction, ProcessAction
|
||||
from .action.action import Action, ActionGroup, ChainedAction, ProcessAction
|
||||
from .command import Command
|
||||
from .context import ExecutionContext, SharedContext
|
||||
from .execution_registry import ExecutionRegistry
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
"""
|
||||
Falyx CLI Framework
|
||||
|
||||
Copyright (c) 2025 rtj.dev LLC.
|
||||
Licensed under the MIT License. See LICENSE file for details.
|
||||
"""
|
||||
|
||||
from .action import (
|
||||
Action,
|
||||
ActionGroup,
|
||||
BaseAction,
|
||||
ChainedAction,
|
||||
FallbackAction,
|
||||
LiteralInputAction,
|
||||
ProcessAction,
|
||||
)
|
||||
from .action_factory import ActionFactoryAction
|
||||
from .http_action import HTTPAction
|
||||
from .io_action import BaseIOAction, ShellAction
|
||||
from .menu_action import MenuAction
|
||||
from .select_file_action import SelectFileAction
|
||||
from .selection_action import SelectionAction
|
||||
from .signal_action import SignalAction
|
||||
|
||||
__all__ = [
|
||||
"Action",
|
||||
"ActionGroup",
|
||||
"BaseAction",
|
||||
"ChainedAction",
|
||||
"ProcessAction",
|
||||
"ActionFactoryAction",
|
||||
"HTTPAction",
|
||||
"BaseIOAction",
|
||||
"ShellAction",
|
||||
"SelectionAction",
|
||||
"SelectFileAction",
|
||||
"MenuAction",
|
||||
"SignalAction",
|
||||
"FallbackAction",
|
||||
"LiteralInputAction",
|
||||
]
|
|
@ -48,7 +48,7 @@ from falyx.hook_manager import Hook, HookManager, HookType
|
|||
from falyx.logger import logger
|
||||
from falyx.options_manager import OptionsManager
|
||||
from falyx.retry import RetryHandler, RetryPolicy
|
||||
from falyx.themes.colors import OneColors
|
||||
from falyx.themes import OneColors
|
||||
from falyx.utils import ensure_async
|
||||
|
||||
|
|
@ -4,13 +4,13 @@ from typing import Any
|
|||
|
||||
from rich.tree import Tree
|
||||
|
||||
from falyx.action import BaseAction
|
||||
from falyx.action.action import BaseAction
|
||||
from falyx.context import ExecutionContext
|
||||
from falyx.execution_registry import ExecutionRegistry as er
|
||||
from falyx.hook_manager import HookType
|
||||
from falyx.logger import logger
|
||||
from falyx.protocols import ActionFactoryProtocol
|
||||
from falyx.themes.colors import OneColors
|
||||
from falyx.themes import OneColors
|
||||
|
||||
|
||||
class ActionFactoryAction(BaseAction):
|
|
@ -13,11 +13,11 @@ from typing import Any
|
|||
import aiohttp
|
||||
from rich.tree import Tree
|
||||
|
||||
from falyx.action import Action
|
||||
from falyx.action.action import Action
|
||||
from falyx.context import ExecutionContext, SharedContext
|
||||
from falyx.hook_manager import HookManager, HookType
|
||||
from falyx.logger import logger
|
||||
from falyx.themes.colors import OneColors
|
||||
from falyx.themes import OneColors
|
||||
|
||||
|
||||
async def close_shared_http_session(context: ExecutionContext) -> None:
|
|
@ -23,13 +23,13 @@ from typing import Any
|
|||
|
||||
from rich.tree import Tree
|
||||
|
||||
from falyx.action import BaseAction
|
||||
from falyx.action.action import BaseAction
|
||||
from falyx.context import ExecutionContext
|
||||
from falyx.exceptions import FalyxError
|
||||
from falyx.execution_registry import ExecutionRegistry as er
|
||||
from falyx.hook_manager import HookManager, HookType
|
||||
from falyx.logger import logger
|
||||
from falyx.themes.colors import OneColors
|
||||
from falyx.themes import OneColors
|
||||
|
||||
|
||||
class BaseIOAction(BaseAction):
|
|
@ -1,6 +1,5 @@
|
|||
# Falyx CLI Framework — (c) 2025 rtj.dev LLC — MIT Licensed
|
||||
"""menu_action.py"""
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
from prompt_toolkit import PromptSession
|
||||
|
@ -8,91 +7,16 @@ from rich.console import Console
|
|||
from rich.table import Table
|
||||
from rich.tree import Tree
|
||||
|
||||
from falyx.action import BaseAction
|
||||
from falyx.action.action import BaseAction
|
||||
from falyx.context import ExecutionContext
|
||||
from falyx.execution_registry import ExecutionRegistry as er
|
||||
from falyx.hook_manager import HookType
|
||||
from falyx.logger import logger
|
||||
from falyx.menu import MenuOptionMap
|
||||
from falyx.selection import prompt_for_selection, render_table_base
|
||||
from falyx.signal_action import SignalAction
|
||||
from falyx.signals import BackSignal, QuitSignal
|
||||
from falyx.themes.colors import OneColors
|
||||
from falyx.utils import CaseInsensitiveDict, chunks
|
||||
|
||||
|
||||
@dataclass
|
||||
class MenuOption:
|
||||
"""Represents a single menu option with a description and an action to execute."""
|
||||
|
||||
description: str
|
||||
action: BaseAction
|
||||
style: str = OneColors.WHITE
|
||||
|
||||
def __post_init__(self):
|
||||
if not isinstance(self.description, str):
|
||||
raise TypeError("MenuOption description must be a string.")
|
||||
if not isinstance(self.action, BaseAction):
|
||||
raise TypeError("MenuOption action must be a BaseAction instance.")
|
||||
|
||||
def render(self, key: str) -> str:
|
||||
"""Render the menu option for display."""
|
||||
return f"[{OneColors.WHITE}][{key}][/] [{self.style}]{self.description}[/]"
|
||||
|
||||
|
||||
class MenuOptionMap(CaseInsensitiveDict):
|
||||
"""
|
||||
Manages menu options including validation, reserved key protection,
|
||||
and special signal entries like Quit and Back.
|
||||
"""
|
||||
|
||||
RESERVED_KEYS = {"Q", "B"}
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
options: dict[str, MenuOption] | None = None,
|
||||
allow_reserved: bool = False,
|
||||
):
|
||||
super().__init__()
|
||||
self.allow_reserved = allow_reserved
|
||||
if options:
|
||||
self.update(options)
|
||||
self._inject_reserved_defaults()
|
||||
|
||||
def _inject_reserved_defaults(self):
|
||||
self._add_reserved(
|
||||
"Q",
|
||||
MenuOption("Exit", SignalAction("Quit", QuitSignal()), OneColors.DARK_RED),
|
||||
)
|
||||
self._add_reserved(
|
||||
"B",
|
||||
MenuOption("Back", SignalAction("Back", BackSignal()), OneColors.DARK_YELLOW),
|
||||
)
|
||||
|
||||
def _add_reserved(self, key: str, option: MenuOption) -> None:
|
||||
"""Add a reserved key, bypassing validation."""
|
||||
norm_key = key.upper()
|
||||
super().__setitem__(norm_key, option)
|
||||
|
||||
def __setitem__(self, key: str, option: MenuOption) -> None:
|
||||
if not isinstance(option, MenuOption):
|
||||
raise TypeError(f"Value for key '{key}' must be a MenuOption.")
|
||||
norm_key = key.upper()
|
||||
if norm_key in self.RESERVED_KEYS and not self.allow_reserved:
|
||||
raise ValueError(
|
||||
f"Key '{key}' is reserved and cannot be used in MenuOptionMap."
|
||||
)
|
||||
super().__setitem__(norm_key, option)
|
||||
|
||||
def __delitem__(self, key: str) -> None:
|
||||
if key.upper() in self.RESERVED_KEYS and not self.allow_reserved:
|
||||
raise ValueError(f"Cannot delete reserved option '{key}'.")
|
||||
super().__delitem__(key)
|
||||
|
||||
def items(self, include_reserved: bool = True):
|
||||
for k, v in super().items():
|
||||
if not include_reserved and k in self.RESERVED_KEYS:
|
||||
continue
|
||||
yield k, v
|
||||
from falyx.themes import OneColors
|
||||
from falyx.utils import chunks
|
||||
|
||||
|
||||
class MenuAction(BaseAction):
|
|
@ -5,7 +5,6 @@ from __future__ import annotations
|
|||
import csv
|
||||
import json
|
||||
import xml.etree.ElementTree as ET
|
||||
from enum import Enum
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
|
@ -15,7 +14,8 @@ from prompt_toolkit import PromptSession
|
|||
from rich.console import Console
|
||||
from rich.tree import Tree
|
||||
|
||||
from falyx.action import BaseAction
|
||||
from falyx.action.action import BaseAction
|
||||
from falyx.action.types import FileReturnType
|
||||
from falyx.context import ExecutionContext
|
||||
from falyx.execution_registry import ExecutionRegistry as er
|
||||
from falyx.hook_manager import HookType
|
||||
|
@ -25,41 +25,7 @@ from falyx.selection import (
|
|||
prompt_for_selection,
|
||||
render_selection_dict_table,
|
||||
)
|
||||
from falyx.themes.colors import OneColors
|
||||
|
||||
|
||||
class FileReturnType(Enum):
|
||||
"""Enum for file return types."""
|
||||
|
||||
TEXT = "text"
|
||||
PATH = "path"
|
||||
JSON = "json"
|
||||
TOML = "toml"
|
||||
YAML = "yaml"
|
||||
CSV = "csv"
|
||||
TSV = "tsv"
|
||||
XML = "xml"
|
||||
|
||||
@classmethod
|
||||
def _get_alias(cls, value: str) -> str:
|
||||
aliases = {
|
||||
"yml": "yaml",
|
||||
"txt": "text",
|
||||
"file": "path",
|
||||
"filepath": "path",
|
||||
}
|
||||
return aliases.get(value, value)
|
||||
|
||||
@classmethod
|
||||
def _missing_(cls, value: object) -> FileReturnType:
|
||||
if isinstance(value, str):
|
||||
normalized = value.lower()
|
||||
alias = cls._get_alias(normalized)
|
||||
for member in cls:
|
||||
if member.value == alias:
|
||||
return member
|
||||
valid = ", ".join(member.value for member in cls)
|
||||
raise ValueError(f"Invalid FileReturnType: '{value}'. Must be one of: {valid}")
|
||||
from falyx.themes import OneColors
|
||||
|
||||
|
||||
class SelectFileAction(BaseAction):
|
|
@ -6,7 +6,7 @@ from prompt_toolkit import PromptSession
|
|||
from rich.console import Console
|
||||
from rich.tree import Tree
|
||||
|
||||
from falyx.action import BaseAction
|
||||
from falyx.action.action import BaseAction
|
||||
from falyx.context import ExecutionContext
|
||||
from falyx.execution_registry import ExecutionRegistry as er
|
||||
from falyx.hook_manager import HookType
|
||||
|
@ -18,7 +18,7 @@ from falyx.selection import (
|
|||
render_selection_dict_table,
|
||||
render_selection_indexed_table,
|
||||
)
|
||||
from falyx.themes.colors import OneColors
|
||||
from falyx.themes import OneColors
|
||||
from falyx.utils import CaseInsensitiveDict
|
||||
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
# Falyx CLI Framework — (c) 2025 rtj.dev LLC — MIT Licensed
|
||||
"""signal_action.py"""
|
||||
from rich.tree import Tree
|
||||
|
||||
from falyx.action.action import Action
|
||||
from falyx.signals import FlowSignal
|
||||
from falyx.themes import OneColors
|
||||
|
||||
|
||||
class SignalAction(Action):
|
||||
"""
|
||||
An action that raises a control flow signal when executed.
|
||||
|
||||
Useful for exiting a menu, going back, or halting execution gracefully.
|
||||
"""
|
||||
|
||||
def __init__(self, name: str, signal: Exception):
|
||||
self.signal = signal
|
||||
super().__init__(name, action=self.raise_signal)
|
||||
|
||||
async def raise_signal(self, *args, **kwargs):
|
||||
raise self.signal
|
||||
|
||||
@property
|
||||
def signal(self):
|
||||
return self._signal
|
||||
|
||||
@signal.setter
|
||||
def signal(self, value: FlowSignal):
|
||||
if not isinstance(value, FlowSignal):
|
||||
raise TypeError(
|
||||
f"Signal must be an FlowSignal instance, got {type(value).__name__}"
|
||||
)
|
||||
self._signal = value
|
||||
|
||||
def __str__(self):
|
||||
return f"SignalAction(name={self.name}, signal={self._signal.__class__.__name__})"
|
||||
|
||||
async def preview(self, parent: Tree | None = None):
|
||||
label = f"[{OneColors.LIGHT_RED}]⚡ SignalAction[/] '{self.signal.__class__.__name__}'"
|
||||
tree = parent.add(label) if parent else Tree(label)
|
||||
if not parent:
|
||||
self.console.print(tree)
|
|
@ -0,0 +1,37 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class FileReturnType(Enum):
|
||||
"""Enum for file return types."""
|
||||
|
||||
TEXT = "text"
|
||||
PATH = "path"
|
||||
JSON = "json"
|
||||
TOML = "toml"
|
||||
YAML = "yaml"
|
||||
CSV = "csv"
|
||||
TSV = "tsv"
|
||||
XML = "xml"
|
||||
|
||||
@classmethod
|
||||
def _get_alias(cls, value: str) -> str:
|
||||
aliases = {
|
||||
"yml": "yaml",
|
||||
"txt": "text",
|
||||
"file": "path",
|
||||
"filepath": "path",
|
||||
}
|
||||
return aliases.get(value, value)
|
||||
|
||||
@classmethod
|
||||
def _missing_(cls, value: object) -> FileReturnType:
|
||||
if isinstance(value, str):
|
||||
normalized = value.lower()
|
||||
alias = cls._get_alias(normalized)
|
||||
for member in cls:
|
||||
if member.value == alias:
|
||||
return member
|
||||
valid = ", ".join(member.value for member in cls)
|
||||
raise ValueError(f"Invalid FileReturnType: '{value}'. Must be one of: {valid}")
|
|
@ -8,7 +8,7 @@ from prompt_toolkit.key_binding import KeyBindings
|
|||
from rich.console import Console
|
||||
|
||||
from falyx.options_manager import OptionsManager
|
||||
from falyx.themes.colors import OneColors
|
||||
from falyx.themes import OneColors
|
||||
from falyx.utils import CaseInsensitiveDict, chunks
|
||||
|
||||
|
||||
|
|
|
@ -26,19 +26,19 @@ from pydantic import BaseModel, ConfigDict, Field, PrivateAttr, field_validator
|
|||
from rich.console import Console
|
||||
from rich.tree import Tree
|
||||
|
||||
from falyx.action import Action, ActionGroup, BaseAction, ChainedAction
|
||||
from falyx.action.action import Action, ActionGroup, BaseAction, ChainedAction
|
||||
from falyx.action.io_action import BaseIOAction
|
||||
from falyx.context import ExecutionContext
|
||||
from falyx.debug import register_debug_hooks
|
||||
from falyx.exceptions import FalyxError
|
||||
from falyx.execution_registry import ExecutionRegistry as er
|
||||
from falyx.hook_manager import HookManager, HookType
|
||||
from falyx.io_action import BaseIOAction
|
||||
from falyx.logger import logger
|
||||
from falyx.options_manager import OptionsManager
|
||||
from falyx.prompt_utils import confirm_async, should_prompt_user
|
||||
from falyx.retry import RetryPolicy
|
||||
from falyx.retry_utils import enable_retries_recursively
|
||||
from falyx.themes.colors import OneColors
|
||||
from falyx.themes import OneColors
|
||||
from falyx.utils import _noop, ensure_async
|
||||
|
||||
console = Console(color_system="auto")
|
||||
|
|
|
@ -13,12 +13,12 @@ import yaml
|
|||
from pydantic import BaseModel, Field, field_validator, model_validator
|
||||
from rich.console import Console
|
||||
|
||||
from falyx.action import Action, BaseAction
|
||||
from falyx.action.action import Action, BaseAction
|
||||
from falyx.command import Command
|
||||
from falyx.falyx import Falyx
|
||||
from falyx.logger import logger
|
||||
from falyx.retry import RetryPolicy
|
||||
from falyx.themes.colors import OneColors
|
||||
from falyx.themes import OneColors
|
||||
|
||||
console = Console(color_system="auto")
|
||||
|
||||
|
|
|
@ -37,7 +37,7 @@ from rich.table import Table
|
|||
|
||||
from falyx.context import ExecutionContext
|
||||
from falyx.logger import logger
|
||||
from falyx.themes.colors import OneColors
|
||||
from falyx.themes import OneColors
|
||||
|
||||
|
||||
class ExecutionRegistry:
|
||||
|
|
|
@ -38,7 +38,7 @@ from rich.console import Console
|
|||
from rich.markdown import Markdown
|
||||
from rich.table import Table
|
||||
|
||||
from falyx.action import Action, BaseAction
|
||||
from falyx.action.action import Action, BaseAction
|
||||
from falyx.bottom_bar import BottomBar
|
||||
from falyx.command import Command
|
||||
from falyx.context import ExecutionContext
|
||||
|
@ -56,7 +56,7 @@ from falyx.options_manager import OptionsManager
|
|||
from falyx.parsers import get_arg_parsers
|
||||
from falyx.retry import RetryPolicy
|
||||
from falyx.signals import BackSignal, QuitSignal
|
||||
from falyx.themes.colors import OneColors, get_nord_theme
|
||||
from falyx.themes import OneColors, get_nord_theme
|
||||
from falyx.utils import CaseInsensitiveDict, chunks, get_program_invocation
|
||||
from falyx.version import __version__
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ from typing import Any, Callable
|
|||
from falyx.context import ExecutionContext
|
||||
from falyx.exceptions import CircuitBreakerOpen
|
||||
from falyx.logger import logger
|
||||
from falyx.themes.colors import OneColors
|
||||
from falyx.themes import OneColors
|
||||
|
||||
|
||||
class ResultReporter:
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
from falyx.action import BaseAction
|
||||
from falyx.signals import BackSignal, QuitSignal
|
||||
from falyx.themes import OneColors
|
||||
from falyx.utils import CaseInsensitiveDict
|
||||
|
||||
|
||||
@dataclass
|
||||
class MenuOption:
|
||||
"""Represents a single menu option with a description and an action to execute."""
|
||||
|
||||
description: str
|
||||
action: BaseAction
|
||||
style: str = OneColors.WHITE
|
||||
|
||||
def __post_init__(self):
|
||||
if not isinstance(self.description, str):
|
||||
raise TypeError("MenuOption description must be a string.")
|
||||
if not isinstance(self.action, BaseAction):
|
||||
raise TypeError("MenuOption action must be a BaseAction instance.")
|
||||
|
||||
def render(self, key: str) -> str:
|
||||
"""Render the menu option for display."""
|
||||
return f"[{OneColors.WHITE}][{key}][/] [{self.style}]{self.description}[/]"
|
||||
|
||||
|
||||
class MenuOptionMap(CaseInsensitiveDict):
|
||||
"""
|
||||
Manages menu options including validation, reserved key protection,
|
||||
and special signal entries like Quit and Back.
|
||||
"""
|
||||
|
||||
RESERVED_KEYS = {"Q", "B"}
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
options: dict[str, MenuOption] | None = None,
|
||||
allow_reserved: bool = False,
|
||||
):
|
||||
super().__init__()
|
||||
self.allow_reserved = allow_reserved
|
||||
if options:
|
||||
self.update(options)
|
||||
self._inject_reserved_defaults()
|
||||
|
||||
def _inject_reserved_defaults(self):
|
||||
from falyx.action import SignalAction
|
||||
|
||||
self._add_reserved(
|
||||
"Q",
|
||||
MenuOption("Exit", SignalAction("Quit", QuitSignal()), OneColors.DARK_RED),
|
||||
)
|
||||
self._add_reserved(
|
||||
"B",
|
||||
MenuOption("Back", SignalAction("Back", BackSignal()), OneColors.DARK_YELLOW),
|
||||
)
|
||||
|
||||
def _add_reserved(self, key: str, option: MenuOption) -> None:
|
||||
"""Add a reserved key, bypassing validation."""
|
||||
norm_key = key.upper()
|
||||
super().__setitem__(norm_key, option)
|
||||
|
||||
def __setitem__(self, key: str, option: MenuOption) -> None:
|
||||
if not isinstance(option, MenuOption):
|
||||
raise TypeError(f"Value for key '{key}' must be a MenuOption.")
|
||||
norm_key = key.upper()
|
||||
if norm_key in self.RESERVED_KEYS and not self.allow_reserved:
|
||||
raise ValueError(
|
||||
f"Key '{key}' is reserved and cannot be used in MenuOptionMap."
|
||||
)
|
||||
super().__setitem__(norm_key, option)
|
||||
|
||||
def __delitem__(self, key: str) -> None:
|
||||
if key.upper() in self.RESERVED_KEYS and not self.allow_reserved:
|
||||
raise ValueError(f"Cannot delete reserved option '{key}'.")
|
||||
super().__delitem__(key)
|
||||
|
||||
def items(self, include_reserved: bool = True):
|
||||
for k, v in super().items():
|
||||
if not include_reserved and k in self.RESERVED_KEYS:
|
||||
continue
|
||||
yield k, v
|
|
@ -8,7 +8,7 @@ from prompt_toolkit.formatted_text import (
|
|||
)
|
||||
|
||||
from falyx.options_manager import OptionsManager
|
||||
from falyx.themes.colors import OneColors
|
||||
from falyx.themes import OneColors
|
||||
from falyx.validators import yes_no_validator
|
||||
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ from __future__ import annotations
|
|||
|
||||
from typing import Any, Protocol
|
||||
|
||||
from falyx.action import BaseAction
|
||||
from falyx.action.action import BaseAction
|
||||
|
||||
|
||||
class ActionFactoryProtocol(Protocol):
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Falyx CLI Framework — (c) 2025 rtj.dev LLC — MIT Licensed
|
||||
"""retry_utils.py"""
|
||||
from falyx.action import Action, BaseAction
|
||||
from falyx.action.action import Action, BaseAction
|
||||
from falyx.hook_manager import HookType
|
||||
from falyx.retry import RetryHandler, RetryPolicy
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ from rich.console import Console
|
|||
from rich.markup import escape
|
||||
from rich.table import Table
|
||||
|
||||
from falyx.themes.colors import OneColors
|
||||
from falyx.themes import OneColors
|
||||
from falyx.utils import chunks
|
||||
from falyx.validators import int_range_validator, key_validator
|
||||
|
||||
|
|
|
@ -1,31 +0,0 @@
|
|||
# Falyx CLI Framework — (c) 2025 rtj.dev LLC — MIT Licensed
|
||||
"""signal_action.py"""
|
||||
from falyx.action import Action
|
||||
from falyx.signals import FlowSignal
|
||||
|
||||
|
||||
class SignalAction(Action):
|
||||
"""
|
||||
An action that raises a control flow signal when executed.
|
||||
|
||||
Useful for exiting a menu, going back, or halting execution gracefully.
|
||||
"""
|
||||
|
||||
def __init__(self, name: str, signal: Exception):
|
||||
if not isinstance(signal, FlowSignal):
|
||||
raise TypeError(
|
||||
f"Signal must be an FlowSignal instance, got {type(signal).__name__}"
|
||||
)
|
||||
|
||||
async def raise_signal(*args, **kwargs):
|
||||
raise signal
|
||||
|
||||
super().__init__(name=name, action=raise_signal)
|
||||
self._signal = signal
|
||||
|
||||
@property
|
||||
def signal(self):
|
||||
return self._signal
|
||||
|
||||
def __str__(self):
|
||||
return f"SignalAction(name={self.name}, signal={self._signal.__class__.__name__})"
|
|
@ -0,0 +1,15 @@
|
|||
"""
|
||||
Falyx CLI Framework
|
||||
|
||||
Copyright (c) 2025 rtj.dev LLC.
|
||||
Licensed under the MIT License. See LICENSE file for details.
|
||||
"""
|
||||
|
||||
from .colors import ColorsMeta, NordColors, OneColors, get_nord_theme
|
||||
|
||||
__all__ = [
|
||||
"OneColors",
|
||||
"NordColors",
|
||||
"get_nord_theme",
|
||||
"ColorsMeta",
|
||||
]
|
|
@ -1 +1 @@
|
|||
__version__ = "0.1.24"
|
||||
__version__ = "0.1.25"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[tool.poetry]
|
||||
name = "falyx"
|
||||
version = "0.1.24"
|
||||
version = "0.1.25"
|
||||
description = "Reliable and introspectable async CLI action framework."
|
||||
authors = ["Roland Thomas Jr <roland@rtj.dev>"]
|
||||
license = "MIT"
|
||||
|
@ -15,6 +15,7 @@ pydantic = "^2.0"
|
|||
python-json-logger = "^3.3.0"
|
||||
toml = "^0.10"
|
||||
pyyaml = "^6.0"
|
||||
aiohttp = "^3.11"
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
pytest = "^8.3.5"
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
# test_command.py
|
||||
import pytest
|
||||
|
||||
from falyx.action import Action, ActionGroup, ChainedAction
|
||||
from falyx.action import Action, ActionGroup, BaseIOAction, ChainedAction
|
||||
from falyx.command import Command
|
||||
from falyx.execution_registry import ExecutionRegistry as er
|
||||
from falyx.io_action import BaseIOAction
|
||||
from falyx.retry import RetryPolicy
|
||||
|
||||
asyncio_default_fixture_loop_scope = "function"
|
||||
|
|
Loading…
Reference in New Issue