Skip to content

Running an App

app runs an application. Give it the root component, already called with its arguments, and run it on an event loop with asyncio.run:

import asyncio

from counterweight import app
from counterweight.components import component
from counterweight.elements import Text


@component
def root(name: str) -> Text:
    return Text(content=f"Hello, {name}!")


if __name__ == "__main__":
    asyncio.run(app(root("world")))

It switches the terminal to its alternate screen, reads input raw and turns on mouse reporting, then renders the root and handles input until something returns Quit, which by default Ctrl+C does. It restores the terminal before it returns, and before an error raised in a component, a handler or an effect propagates out of it.

What else it takes

The rest of app's parameters each belong to a part of the framework, with its own page:

To Pass See
Title the terminal window title SetTitle
Change what Ctrl+C and Ctrl+Z do, or give other keys defaults unclaimed_keys Keys the terminal would act on
Read the time in another time zone, or freeze it clock use_clock
Drive it without a person, or without a terminal autopilot, headless, dimensions Autopilot
Record it as an asciicast recording Recording
Catch components that go stale check_purity Performance
See other libraries' debug logs in the devlog log_levels The devlog
Match a font that isn't twice as tall as it is wide cell_aspect_ratio Gradients
Draw on a terminal other than standard input and output input_stream, output_stream Running programs

Which version is running

counterweight.__version__ is the version installed, which counterweight version prints too.

API

counterweight.app async

app(
    root: Component,
    output_stream: TextIO = stdout,
    input_stream: TextIO = stdin,
    headless: bool = False,
    dimensions: tuple[int, int] | None = None,
    autopilot: Iterable[AutopilotStep] = (),
    check_purity: bool = False,
    clock: Clock = SystemClock(),
    log_levels: Mapping[str, int] = frozendict(),
    title: str | None = None,
    unclaimed_keys: Mapping[
        str, AnyControl
    ] = DEFAULT_UNCLAIMED_KEYS,
    cell_aspect_ratio: float = DEFAULT_CELL_ASPECT_RATIO,
    recording: Recording | None = None,
) -> None
PARAMETER DESCRIPTION
root

The root component of the application, already called with its arguments, such as app(root(path)).

TYPE: Component

output_stream

The stream to which the application will write its output.

TYPE: TextIO DEFAULT: stdout

input_stream

The stream from which the application will read its input.

TYPE: TextIO DEFAULT: stdin

headless

If True, the application will not attempt to read from the input stream or write to the output stream. This is primarily useful when combined with the autopilot parameter.

TYPE: bool DEFAULT: False

dimensions

The dimensions of the terminal window, in characters. If None, the dimensions of the terminal window will be used.

TYPE: tuple[int, int] | None DEFAULT: None

autopilot

An iterable of events and controls to be processed by the application, and of Wait and WaitFor, which hold the rest back until real time has passed or the screen shows something. This is primarily useful for testing or generating screenshots and recordings programmatically. Each step is handled once the renders the last one caused are painted, and the effects they started have run up to where each first waits on something, as a person's next key press would be. Note that the autopilot will not be processed until after the initial render cycle, and that using the autopilot does not automatically cause the application to quit!

TYPE: Iterable[AutopilotStep] DEFAULT: ()

check_purity

If True, check that components are pure: that their output depends only on their arguments and their hooks. A render keeps the previous output of every component it doesn't re-run (because its state didn't change and its parent didn't re-run, or because it's a memo component whose arguments didn't change), so an impure one goes stale. With this on, each render is followed by a second one that re-runs every component against copies of its hooks, and if that builds a different tree, the application raises ImpureComponent, naming the first component whose output differs. Elements are compared with ==, and where that fails, piece by piece, so handlers are compared by their code and the values they captured, since a re-run builds new ones. The second run also raises InconsistentHookExecution for a component that calls a different number of hooks, and computes each use_memo that keeps its cached value again, raising StaleMemo if the result differs, naming the component and the hook. It costs as much as a render that re-runs everything, so turn it on in tests rather than in production.

TYPE: bool DEFAULT: False

clock

Where use_clock reads the time, once per render. The SystemClock reads the time in UTC unless it's given a timezone. Pass a FrozenClock to make output that doesn't depend on when the application runs, such as a screenshot, and move it with the AdvanceClock control.

TYPE: Clock DEFAULT: SystemClock()

log_levels

Levels by logger name, with "" the root logger, as in logging.config, for the records the devlog captures while the app runs. They're merged over a base that captures counterweight's records and the app's own at every level, and everyone else's from INFO up, where the app's own are those of the top-level package of the module that defines root's function (__main__ for an app run as a script). Pass {"some_library": logging.DEBUG} to see a library's debug records too, or {"my_app": logging.INFO} to hide the app's own. An app that runs while another has the devlog open, such as one run headless in another's handler, writes its records into that devlog, at the levels the other app set.

TYPE: Mapping[str, int] DEFAULT: frozendict()

title

The title to give the terminal window when the application starts, as SetTitle would, before the first frame is drawn. If None, the terminal keeps the title it has.

TYPE: str | None DEFAULT: None

unclaimed_keys

What a key press does when no handler claims it, by its key, as a browser takes a key's default action unless a listener prevents it. The application reads the terminal raw, so every key reaches it as a key press, including Ctrl+C and Ctrl+Z, which a terminal otherwise turns into signals, and the defaults, DEFAULT_UNCLAIMED_KEYS, give those two what they'd do there: Ctrl+C returns Quit, and Ctrl+Z Suspend.to_shell(). A handler that claims one of those keys, such as one that passes keys on to a program, keeps it from taking its default. Pass {} to give unclaimed keys no defaults, or merge onto the defaults to change some, as in DEFAULT_UNCLAIMED_KEYS | {Key.ControlQ: Quit()}.

TYPE: Mapping[str, AnyControl] DEFAULT: DEFAULT_UNCLAIMED_KEYS

cell_aspect_ratio

How many times taller than it is wide a cell of the terminal's font is, which gradients compute their angles and circles with, so that a 45 degree gradient or a circle looks like one. Most terminal fonts are about 2.

TYPE: float DEFAULT: DEFAULT_CELL_ASPECT_RATIO

recording

Where and how to record the application as an asciicast, which asciinema-player plays back, whether it's headless or not. If None, the application isn't recorded.

TYPE: Recording | None DEFAULT: None

counterweight.__version__ module-attribute

__version__ = metadata.version(PACKAGE_NAME)

The version of counterweight that's installed, such as "0.1.0".