4.1. Runner Layer

Runners are the entry points of the generated application — they bootstrap the process and call into the Presentation Layer. The runners you write live in runners/main/ and runners/tools/ — see Types of runners below.

4.1.1. What to Do in Runners

  • Keep them thin. A runner method should build its dependencies (adapter, repository, use case, presentation) and call one presentation method — nothing more.

  • Wire dependencies explicitly. Instantiate the concrete classes a runner needs directly (or via a Provider for interchangeable ones) — there’s no dependency-injection container to configure.

  • One responsibility per method. Each @Runner.runner-decorated method should do one thing end to end.

4.1.2. What to Avoid in Runners

  • Business logic. A runner orchestrates a call into Presentation; it never contains business rules or complex decision-making itself.

  • Heavy or long-running work inline, beyond what the use case it calls already does — a runner is a thin entry point, not a place to implement additional processing.

  • ``print()`` for output. Use the Presentation Layer’s Printer/ComponentPrinter instead, so output stays consistent (and translatable) across the whole application.

4.1.3. Types of runners

  • Main Runners (runners/main/) — the primary entry points of the application, run via python run.

  • Tool Runners (runners/tools/) — secondary, dev/ops-style tasks (seeding data, running a benchmark, generating config), run via python tools.

To define a class as a runner, it must:

  • Live in either runners/main or runners/tools.

  • Inherit from Runner (infrastructure.core.runner.Runner).

  • Mark the methods meant to be invokable with the @Runner.runner decorator — a method without it (e.g. a helper) is never shown as a selectable action.

4.1.4. Example of a Runner

runners/main/app.py — every fresh project ships this one:

from application.use_cases.app.get import GetAppUseCase
from infrastructure.app.adapter import AppAdapter
from infrastructure.core.runner import Runner
from presentation.cli.app import AppCLIPresentation


class AppRunner(Runner):
    @Runner.runner
    def start(self):
        app_adapter = AppAdapter()
        app_use_case = GetAppUseCase(app_provider=app_adapter)
        presentation = AppCLIPresentation(app_use_case=app_use_case)
        presentation.start()

    def non_runner1(self):
        # This method does not show in the runner menu —
        # it isn't decorated with @Runner.runner.
        pass

4.1.5. Running Applications

To execute the Main runners within your project, use:

python run

This runs the main runner(s) located in runners/main. If there’s more than one candidate at any step, you’ll be prompted to choose the file, class, and method interactively (this needs the prompt_toolkit template installed — without it, the full path must always be passed inline, as below).

4.1.6. Running Tool Runners

To execute a Tool Runner, use:

python tools

Same behavior as python run, but scanning runners/tools instead of runners/main.

4.1.7. Executing a Specific Runner

You can always target a runner directly instead of going through the interactive menu:

python run file_name class_name method_name
  • file_name: the module under runners/main (or runners/tools for python tools) containing the runner class, without the .py extension.

  • class_name: the Runner subclass to use.

  • method_name: the @Runner.runner-decorated method to call.

Any piece that has only one possible value can be omitted — if the target file has only one Runner subclass, class_name is inferred; if that class has only one runnable method, method_name is inferred too. In the smallest case, with exactly one file, one class, and one method, python run alone resolves everything automatically, with no prompt at all.

Any extra key=value arguments after the file/class/method are forwarded to the method as keyword arguments — e.g. python run flask Flask start port=8080.