Recipes
Execute actions programmatically
Access any Action in a Model with
Model.get_action.
import menuet
model = menuet.Model()
menuet.deserialize(
{
"action": [{
"id": "hello-world",
"cb": "print('Hello World !')",
}],
},
model
)
model.get_action("hello-world").cb()
Root keys
The root_keys argument of load
and loads can be used to parse a menu configuration from a
sub-table.
from textwrap import dedent
import menuet
model = menuet.Model()
menuet.loads(
dedent("""\
[[path.to.my-menu.action]]
id = "print-version"
label = "Print Version"
cb = "from importlib.metadata import version; print(version('myapp'))"
"""),
model,
root_keys=("path", "to", "my-menu"),
)
Menu in pyproject.toml
Loading menu from a sub-table can be used to define a menu in a
pyproject.toml.
pyproject.toml
[project]
name = "myapp"
verion = "1.0.0"
[[tool.myapp.my-menu.action]]
id = "hello-world"
label = "Print Hello"
cb = "print('Hello World !')"
from pathlib import Path
import menuet
model = menuet.Model()
menuet.loads(
Path("pyproject.toml").read_text(),
model,
root_keys=("tool", "myapp", "my-menu"),
)
model.get_action("hello-world").cb()
Multiple menus in the same file
Sub-table may be used to define multiple menus in the same file.
menu.toml
[[foo.action]]
id = "print-foo"
label = "Print Hello Foo"
menu = ["Foo Menu"]
cb = "print('Hello from foo')"
[[bar.action]]
id = "print-bar"
label = "Print Hello Bar"
menu = ["Bar Menu"]
cb = "print('Hello from bar')"
Load multiple files in a Model
from pathlib import Path
import menuet
from menuet.builders.text import TextMenuBuilder
model = menuet.Model()
loads(Path("menu.toml").read_text(), model)
loads(Path("other-menu.toml").read_text(), model)
Build model from code
from functools import partial
from importlib.metadata import version
from pathlib import Path
import menuet
model = menuet.Model()
model.add_action(
menuet.Action(
id="hello-world",
label="Hello World",
cb="print('Hello World !')",
),
)
model.add_action(
menuet.Action(
id="myapp-version",
label=f"myapp, v{version('myapp')}",
cb=partial(version, "myapp"),
menu=("Version",)
),
)
model.add_menu(
menuet.Action(
label="Version",
icon=Path(__file__).parent / "icon.svg",
),
)
Build model from Entry Points
In this example, the myapp packages build a menu that's populated by plugins,
discovered through
entry points.
The main function initialize the Model
and lookup the myapp.menu entry point group to discover its
Actions.
myapp/__main__.py
from __future__ import annotations
import logging
from importlib.metadata import entry_points
from PySide6 import QtWidgets
import menuet
from menuet.builders.qt import QMenuBuilder
logger = logging.getLogger("myapp")
def main() -> None:
model = menuet.Model()
_load_entry_points(model, "myapp.menu")
app = QtWidgets.QApplication([])
window = QtWidgets.QMainWindow()
builder = QMenuBuilder(model, root_menu="My App")
window.menuBar().addMenu(builder.build())
window.show()
app.exec()
def _load_entry_points(model: menuet.Model, group: str) -> None:
for ep in entry_points(group=group):
try:
func = ep.load()
except Exception:
logger.exception("Failed to load entry point: %s", ep)
continue
try:
actions = func()
except Exception:
logger.exception("Failed to call entry point: %s", ep)
continue
if not isinstance(actions, list):
logger.error("Expected 'list', found %s: %s", type(actions), ep)
continue
for item in actions:
if isinstance(item, menuet.Menu):
model.add_menu(item)
elif isinstance(item, menuet.Action):
model.add_action(item)
else:
logger.error("Expected 'Action' or 'Menu', got %s: %s", type(item), ep)
if __name__ == "__main__":
main()
The plugin myplugin make its actions available to myapp by defining it an
myapp.menu entry point in itspyproject.toml file.
pyproject.toml
[project]
name = "myplugin"
verion = "1.0.0"
[project.entry-points."myapp.menu"]
myplugin = "myplugin.actions:actions"
The actions hook function returns a list of Actions
and Menus to add to myapp menu.
myplugin/actions.py
import menuet
def actions() -> list[menuet.Menu | menuet.Action]:
return [
menuet.Action(
id="hello-world",
label="Hello World",
cb="print('Hello World !')",
),
menuet.Action(
id="open-gui",
label="Open GUI",
menu=("Sub Menu",),
cb=open_dialog,
),
]
def open_dialog() -> None:
from PySide6 import QtWidgets
QtWidgets.QMessageBox.information(None, "Demo", "Example Dialog")