app-model: Generic Application Schema
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.
Common errors
-
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+).fixUpgrade Pydantic to version 2.0 or newer: `pip install 'pydantic>=2'`. -
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.fixModify your command execution to retrieve the actual result: `result = app.commands.execute('my-app.command_id').result()`. -
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.fixEnsure 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', ...)`.
Warnings
- 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.
- 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.
- 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.
Install
-
pip install app-model
Imports
- Application
from app_model import Application
- Command
from app_model import Command
- Menu
from app_model import Menu
- SubMenu
from app_model import SubMenu
- Action
from app_model import Action
from app_model.types import Action
Quickstart
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}")