ptk-async
This skill should be used when the user asks about "prompt_toolkit Application", "app.run", "run_async", "asyncio", "event loop", "run_in_terminal", "async key binding", "background task", "Application lifecycle", or needs to understand the Application run loop and async patterns in prompt_toolkit.
When & Why to Use This Skill
This Claude skill provides comprehensive guidance on mastering the prompt_toolkit Application lifecycle and asynchronous patterns. It enables developers to build responsive, non-blocking terminal user interfaces (TUIs) by leveraging asyncio integration, background tasks, and efficient event loop management. It covers essential concepts from basic application setup to advanced debouncing and concurrent data loading techniques.
Use Cases
- Developing real-time terminal dashboards that require background data fetching without freezing the user interface.
- Implementing asynchronous key bindings to perform API calls or database queries directly from a TUI.
- Managing complex application lifecycles, including custom event loops and graceful exits with result handling.
- Executing external shell commands seamlessly from within a running prompt_toolkit app using the run_in_terminal utility.
- Optimizing UI performance through invalidation patterns, debounced updates, and parallel task execution.
| name | ptk-async |
|---|---|
| description | This skill should be used when the user asks about "prompt_toolkit Application", "app.run", "run_async", "asyncio", "event loop", "run_in_terminal", "async key binding", "background task", "Application lifecycle", or needs to understand the Application run loop and async patterns in prompt_toolkit. |
prompt_toolkit Application & Async Patterns
The Application class is the core of every prompt_toolkit program. It manages the event loop, rendering, and input handling. Understanding its lifecycle and async patterns is essential for building responsive terminal applications.
Application Basics
from prompt_toolkit.application import Application
from prompt_toolkit.layout import Layout, Window
from prompt_toolkit.layout.controls import FormattedTextControl
app = Application(
layout=Layout(Window(FormattedTextControl('Hello World'))),
full_screen=True,
)
# Run synchronously (blocking)
result = app.run()
# Or run asynchronously
import asyncio
result = asyncio.run(app.run_async())
Application Constructor
Application(
layout: Layout | None = None,
key_bindings: KeyBindingsBase | None = None,
style: BaseStyle | None = None,
full_screen: bool = False,
mouse_support: bool = False,
editing_mode: EditingMode = EditingMode.EMACS,
erase_when_done: bool = False,
refresh_interval: float | None = None,
color_depth: ColorDepth | None = None,
)
Key Parameters:
full_screen- Use entire terminal (vs inline prompt)mouse_support- Enable mouse eventsediting_mode-EditingMode.EMACSorEditingMode.VIrefresh_interval- Auto-refresh interval in seconds (for dynamic content)erase_when_done- Clear screen on exit
Running Applications
Synchronous
result = app.run()
Asynchronous
async def main():
result = await app.run_async()
return result
asyncio.run(main())
With Custom Event Loop
loop = asyncio.new_event_loop()
try:
result = loop.run_until_complete(app.run_async())
finally:
loop.close()
Exiting the Application
@kb.add('c-c')
def exit_app(event):
event.app.exit()
@kb.add('c-q')
def exit_with_result(event):
event.app.exit(result='user quit')
@kb.add('c-x')
def exit_with_exception(event):
event.app.exit(exception=KeyboardInterrupt())
Async Key Binding Handlers
Key handlers can be async coroutines:
@kb.add('c-r')
async def refresh_data(event):
# Non-blocking async operation
data = await fetch_from_api()
event.current_buffer.text = data
# UI automatically refreshes after handler completes
Background Tasks
Using create_background_task
async def background_worker(app):
while True:
await asyncio.sleep(1)
# Update state
app.invalidate() # Request redraw
app = Application(...)
async def main():
# Start background task
task = asyncio.create_task(background_worker(app))
try:
result = await app.run_async()
finally:
task.cancel()
With Application's Background Tasks
async def update_clock(app):
while True:
await asyncio.sleep(1)
app.invalidate()
# Add task when app starts
app.create_background_task(update_clock(app))
run_in_terminal
Execute code while temporarily leaving the app:
from prompt_toolkit.application import run_in_terminal
@kb.add('c-t')
async def run_external(event):
def show_output():
print("Running external command...")
import subprocess
subprocess.run(['ls', '-la'])
input("Press Enter to continue...")
await run_in_terminal(show_output)
Invalidation
Request UI refresh when state changes:
# In async code
app.invalidate()
# In key handler
@kb.add('c-r')
def refresh(event):
update_data()
event.app.invalidate()
# Auto-refresh with refresh_interval
app = Application(
refresh_interval=0.5, # Refresh every 500ms
)
Application Events
Hook into lifecycle events:
app = Application(...)
# Before first render
app.on_reset += lambda: print("App reset")
# After invalidate called
app.on_invalidate += lambda: print("Invalidated")
# Before each render
app.before_render += lambda app: print("About to render")
# After each render
app.after_render += lambda app: print("Rendered")
Accessing Current Application
from prompt_toolkit.application import get_app, get_app_or_none
# In handlers (always available)
@kb.add('c-i')
def handler(event):
app = event.app # Preferred
# In other code
try:
app = get_app()
except RuntimeError:
print("No app running")
# Safe version
app = get_app_or_none()
if app:
app.invalidate()
Common Async Patterns
Debounced Updates
class DebouncedUpdater:
def __init__(self, app, delay=0.3):
self.app = app
self.delay = delay
self._task = None
async def trigger(self):
if self._task:
self._task.cancel()
self._task = asyncio.create_task(self._update())
async def _update(self):
await asyncio.sleep(self.delay)
update_expensive_data()
self.app.invalidate()
Concurrent Data Loading
async def load_data_parallel(app):
results = await asyncio.gather(
fetch_users(),
fetch_products(),
fetch_orders(),
)
app.data = results
app.invalidate()
Reference Codebase
For detailed API:
- Source:
/Users/brandon/src/python-prompt-toolkit/src/prompt_toolkit/application/application.py - Source:
/Users/brandon/src/python-prompt-toolkit/src/prompt_toolkit/application/run_in_terminal.py - Examples:
/Users/brandon/src/python-prompt-toolkit/examples/full-screen/
Additional Resources
Reference Files
references/application-api.md- Complete Application APIreferences/async-patterns.md- Production async patterns