app-model: Generic Application Schema

raw JSON →
0.5.1 verified Fri Apr 17 auth: no python

app-model is a Python library that provides a generic application schema, inspired by frameworks like VS Code and Qt. It allows defining commands, menus, keybindings, and application state in a structured, framework-agnostic way. Built on Pydantic, it offers strong typing and data validation. The current version is 0.5.1, with releases typically tied to feature development and bug fixes.

pip install app-model
error pydantic.v1.error_wrappers.ValidationError: ...
cause You are likely running `app-model` with Pydantic v1.x installed, which is incompatible with recent versions of `app-model` (v0.4.0+).
fix
Upgrade Pydantic to version 2.0 or newer: pip install 'pydantic>=2'.
error AttributeError: 'Future' object has no attribute 'my_expected_attribute'
cause You forgot to call `.result()` on the `Future` object returned by `app.commands.execute()`, attempting to access attributes directly on the Future itself.
fix
Modify your command execution to retrieve the actual result: result = app.commands.execute('my-app.command_id').result().
error app_model.types.CommandNotFoundError: Command 'unknown_id' not found
cause The command ID provided to `app.commands.execute()` or `app.register_menu_item()` does not correspond to any command that has been registered with the `Application` instance.
fix
Ensure the command ID matches exactly one of your registered commands. Verify the ID in app.register_command(Command('YOUR_ID', ...)) and app.commands.execute('YOUR_ID', ...).
breaking app-model v0.4.0+ requires Pydantic v2.0 or newer. Using Pydantic v1.x will lead to `ValidationError` or `AttributeError` issues, particularly when models are instantiated or validated.
fix Ensure `pydantic>=2.0` is installed in your environment: `pip install 'pydantic>=2'`.
gotcha The `app.commands.execute()` method returns a `Future` object, not the direct result of the command. If you need the result immediately in a synchronous context, you must call `.result()` on the returned Future. In an `async` context, `await` the Future.
fix Always append `.result()` for synchronous calls: `app.commands.execute(...).result()`. For async functions, use `await app.commands.execute(...)`.
gotcha The `Menu` enums were refactored in v0.5.0. If you were previously using patterns like `MenuType.APP`, these have been simplified to `Menu.APP`. Direct `Menu` enum members are now used for registering menu items.
fix Update your menu registration code to use the direct `Menu` enum members, e.g., `Menu.APP` instead of `MenuType.APP` or string literal names.

This quickstart demonstrates how to set up a basic `Application`, define and register commands using `Command` objects, associate them with menus via `Menu` enumerations, and execute commands, retrieving their results. Note the use of `.result()` to unwrap the `Future` returned by `execute` for synchronous retrieval.

from app_model import Application, Command, Menu

def say_hello(name: str = "World") -> str:
    "A command that greets the provided name."
    return f"Hello, {name}!"

def say_goodbye() -> str:
    "A simple command to say goodbye."
    return "Goodbye!"

# 1. Initialize the application
app = Application("my-first-app")

# 2. Register commands
app.register_command(Command("my-app.hello", say_hello, title="Say Hello"))
app.register_command(Command("my-app.goodbye", say_goodbye, title="Say Goodbye"))

# 3. Register a menu item for the 'hello' command
app.register_menu_item(
    Menu.APP,  # or Menu.FILE, Menu.EDIT, etc.
    Command("my-app.hello", title="Say Hello from Menu", menu_path="File > Hello"),
)

# 4. Execute a command and get its result
hello_result = app.commands.execute("my-app.hello", {"name": "Registry"}).result()
print(f"Hello Command Result: {hello_result}")

goodbye_result = app.commands.execute("my-app.goodbye").result()
print(f"Goodbye Command Result: {goodbye_result}")