Add HTTPAction, update main demo

This commit is contained in:
Roland Thomas Jr 2025-04-30 22:23:59 -04:00
parent bc1637143c
commit fe9758adbf
Signed by: roland
GPG Key ID: 7C3C2B085A4C2872
4 changed files with 235 additions and 33 deletions

View File

@ -5,73 +5,166 @@ 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__
def build_falyx() -> Falyx:
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."""
flx = Falyx(title="🚀 Falyx CLI")
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)
# Example commands
# --- 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", lambda: print("📦 Building...")),
tags=["build"]
action=Action("Build", foo.build),
tags=["build"],
spinner=True,
spinner_message="📦 Building...",
)
flx.add_command(
key="T",
description="Run tests",
action=Action("Test", lambda: print("🧪 Running tests...")),
tags=["test"]
action=Action("Test", foo.test),
tags=["test"],
spinner=True,
spinner_message="🧪 Running tests...",
)
flx.add_command(
key="D",
description="Deploy project",
action=Action("Deploy", lambda: print("🚀 Deploying...")),
tags=["deploy"]
action=Action("Deploy", foo.deploy),
tags=["deploy"],
spinner=True,
spinner_message="🚀 Deploying...",
)
# Example of ChainedAction (pipeline)
build_pipeline = ChainedAction(
# --- Build pipeline (ChainedAction) ---
pipeline = ChainedAction(
name="Full Build Pipeline",
actions=[
Action("Clean", lambda: print("🧹 Cleaning...")),
Action("Build", lambda: print("🔨 Building...")),
Action("Package", lambda: print("📦 Packaging...")),
],
auto_inject=False,
Action("Clean", foo.clean),
Action("Build", foo.build_package),
Action("Package", foo.package),
]
)
flx.add_command(
key="P",
description="Run Build Pipeline",
action=build_pipeline,
tags=["build", "pipeline"]
action=pipeline,
tags=["build", "pipeline"],
spinner=True,
spinner_message="🔨 Running build pipeline...",
spinner_type="line",
)
# Example of ActionGroup (parallel tasks)
# --- Test suite (ActionGroup) ---
test_suite = ActionGroup(
name="Test Suite",
actions=[
Action("Unit Tests", lambda: print("🧪 Running unit tests...")),
Action("Integration Tests", lambda: print("🔗 Running integration tests...")),
Action("Lint", lambda: print("🧹 Running linter...")),
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"]
tags=["test", "parallel"],
spinner=True,
spinner_type="line",
)
await foo.run()
return flx
if __name__ == "__main__":
flx = build_falyx()
asyncio.run(flx.run())
try:
asyncio.run(main())
except (KeyboardInterrupt, EOFError):
pass

View File

@ -830,9 +830,10 @@ class Falyx:
except (EOFError, KeyboardInterrupt):
logger.info("EOF or KeyboardInterrupt. Exiting menu.")
break
logger.info(f"Exiting menu: {self.get_title()}")
if self.exit_message:
self.print_message(self.exit_message)
finally:
logger.info(f"Exiting menu: {self.get_title()}")
if self.exit_message:
self.print_message(self.exit_message)
async def run(self) -> None:
"""Run Falyx CLI with structured subcommands."""

109
falyx/http_action.py Normal file
View File

@ -0,0 +1,109 @@
from typing import Any
import aiohttp
from rich.tree import Tree
from falyx.action import Action
from falyx.context import ExecutionContext, SharedContext
from falyx.themes.colors import OneColors
from falyx.utils import logger
async def close_shared_http_session(context: ExecutionContext) -> None:
try:
shared_context: SharedContext = context.get_shared_context()
session = shared_context.get("http_session")
should_close = shared_context.get("_session_should_close", False)
if session and should_close:
await session.close()
except Exception as error:
logger.warning("⚠️ Error closing shared HTTP session: %s", error)
class HTTPAction(Action):
"""
Specialized Action that performs an HTTP request using aiohttp and the shared context.
Automatically reuses a shared aiohttp.ClientSession stored in SharedContext.
Closes the session at the end of the ActionGroup (via an after-hook).
"""
def __init__(
self,
name: str,
method: str,
url: str,
*,
args: tuple[Any, ...] = (),
headers: dict[str, str] | None = None,
params: dict[str, Any] | None = None,
json: dict[str, Any] | None = None,
data: Any = None,
hooks=None,
inject_last_result: bool = False,
inject_last_result_as: str = "last_result",
retry: bool = False,
retry_policy=None,
):
self.method = method.upper()
self.url = url
self.headers = headers
self.params = params
self.json = json
self.data = data
super().__init__(
name=name,
action=self._request,
args=args,
kwargs={},
hooks=hooks,
inject_last_result=inject_last_result,
inject_last_result_as=inject_last_result_as,
retry=retry,
retry_policy=retry_policy,
)
async def _request(self, *args, **kwargs) -> dict[str, Any]:
assert self.shared_context is not None, "SharedContext is not set"
context: SharedContext = self.shared_context
session = context.get("http_session")
if session is None:
session = aiohttp.ClientSession()
context.set("http_session", session)
context.set("_session_should_close", True)
async with session.request(
self.method,
self.url,
headers=self.headers,
params=self.params,
json=self.json,
data=self.data,
) as response:
body = await response.text()
return {
"status": response.status,
"url": str(response.url),
"headers": dict(response.headers),
"body": body,
}
async def preview(self, parent: Tree | None = None):
label = [
f"[{OneColors.CYAN_b}]🌐 HTTPAction[/] '{self.name}'",
f"\n[dim]Method:[/] {self.method}",
f"\n[dim]URL:[/] {self.url}",
]
if self.inject_last_result:
label.append(f"\n[dim]Injects:[/] '{self.inject_last_result_as}'")
if self.retry_policy and self.retry_policy.enabled:
label.append(
f"\n[dim]↻ Retries:[/] {self.retry_policy.max_retries}x, "
f"delay {self.retry_policy.delay}s, backoff {self.retry_policy.backoff}x"
)
if parent:
parent.add("".join(label))
else:
self.console.print(Tree("".join(label)))

View File

@ -20,7 +20,6 @@ pytest-asyncio = "^0.20"
ruff = "^0.3"
[tool.poetry.scripts]
falyx = "falyx.cli.main:main"
sync-version = "scripts.sync_version:main"
[build-system]