3.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.
3.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
Providerfor 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.
3.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/ComponentPrinterinstead, so output stays consistent (and translatable) across the whole application.
3.1.3. Types of runners
Main Runners (
runners/main/) — the primary entry points of the application, run viapython run.Tool Runners (
runners/tools/) — secondary, dev/ops-style tasks (seeding data, running a benchmark, generating config), run viapython run_tools.
To define a class as a runner, it must:
Live in either
runners/mainorrunners/tools.Inherit from
Runner(infrastructure.framework.appcraft.core.runner.Runner).Mark the methods meant to be invokable with the
@Runner.runnerdecorator — a method without it (e.g. a helper) is never shown as a selectable action.
3.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.framework.appcraft.app.provider import AppProvider
from infrastructure.framework.appcraft.core.runner import Runner
from presentation.cli.app import AppCLIPresentation
class AppRunner(Runner):
@Runner.runner
def start(self):
app_adapter = AppProvider()
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
3.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).
3.1.6. Running Tool Runners
To execute a Tool Runner, use:
python run_tools
Same behavior as python run, but scanning runners/tools instead of runners/main.
3.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 underrunners/main(orrunners/toolsforpython run_tools) containing the runner class, without the.pyextension.class_name: theRunnersubclass 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.