Skip to content

registries #

App-model registries, such as menus, keybindings, commands.

CommandsRegistry #

CommandsRegistry(injection_store: Store | None = None, raise_synchronous_exceptions: bool = False)

Registry for commands (callable objects).

Source code in app_model/registries/_commands_reg.py
114
115
116
117
118
119
120
121
def __init__(
    self,
    injection_store: Store | None = None,
    raise_synchronous_exceptions: bool = False,
) -> None:
    self._commands: dict[str, RegisteredCommand] = {}
    self._injection_store = injection_store
    self._raise_synchronous_exceptions = raise_synchronous_exceptions

execute_command #

execute_command(id: str, *args: Any, execute_asynchronously: bool = False, **kwargs: Any) -> Future

Execute a registered command.

Parameters:

  • id (CommandId) –

    ID of the command to execute

  • *args (Any, default: () ) –

    Positional arguments to pass to the command

  • execute_asynchronously (bool, default: False ) –

    Whether to execute the command asynchronously in a thread, by default False. Note that regardless of this setting, the return value will implement the Future API (so it's necessary) to call result() on the returned object. Eventually, this will default to True, but we need to solve ensure_main_thread Qt threading issues first

  • **kwargs (Any, default: {} ) –

    Keyword arguments to pass to the command

Returns:

  • Future ( Future ) –

    Future object containing the result of the command

Raises:

  • KeyError

    If the command is not registered or has no callbacks.

Source code in app_model/registries/_commands_reg.py
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
def execute_command(
    self,
    id: str,
    *args: Any,
    execute_asynchronously: bool = False,
    **kwargs: Any,
) -> Future:
    """Execute a registered command.

    Parameters
    ----------
    id : CommandId
        ID of the command to execute
    *args: Any
        Positional arguments to pass to the command
    execute_asynchronously : bool
        Whether to execute the command asynchronously in a thread,
        by default `False`.  Note that *regardless* of this setting,
        the return value will implement the `Future` API (so it's necessary)
        to call `result()` on the returned object.  Eventually, this will
        default to True, but we need to solve `ensure_main_thread` Qt threading
        issues first
    **kwargs: Any
        Keyword arguments to pass to the command

    Returns
    -------
    Future: concurrent.futures.Future
        Future object containing the result of the command

    Raises
    ------
    KeyError
        If the command is not registered or has no callbacks.
    """
    try:
        cmd = self[id].run_injected
    except KeyError as e:
        raise KeyError(f"Command {id!r} not registered") from e  # pragma: no cover

    if execute_asynchronously:
        with ThreadPoolExecutor() as executor:
            return executor.submit(cmd, *args, **kwargs)

    future: Future = Future()
    try:
        future.set_result(cmd(*args, **kwargs))
    except Exception as e:
        if self._raise_synchronous_exceptions:
            # note, the caller of this function can also achieve this by
            # calling `future.result()` on the returned future object.
            raise e
        future.set_exception(e)

    return future

register_action #

register_action(action: Action) -> DisposeCallable

Register an Action object.

This is a convenience method that registers the action's callback with the action's ID and title using register_command.

Parameters:

  • action (Action) –

    Action to register

Returns:

  • DisposeCallable

    A function that can be called to unregister the action.

Source code in app_model/registries/_commands_reg.py
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
def register_action(self, action: Action) -> DisposeCallable:
    """Register an Action object.

    This is a convenience method that registers the action's callback
    with the action's ID and title using `register_command`.

    Parameters
    ----------
    action: Action
        Action to register

    Returns
    -------
    DisposeCallable
        A function that can be called to unregister the action.
    """
    return self.register_command(action.id, action.callback, action.title)

register_command #

register_command(id: str, callback: Callable[P, R] | str, title: str) -> DisposeCallable

Register a callable as the handler for command id.

Parameters:

  • id (CommandId) –

    Command identifier

  • callback (Callable) –

    Callable to be called when the command is executed

  • title (str) –

    Title for the command.

Returns:

  • DisposeCallable

    A function that can be called to unregister the command.

Source code in app_model/registries/_commands_reg.py
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
def register_command(
    self, id: str, callback: Callable[P, R] | str, title: str
) -> DisposeCallable:
    """Register a callable as the handler for command `id`.

    Parameters
    ----------
    id : CommandId
        Command identifier
    callback : Callable
        Callable to be called when the command is executed
    title : str
        Title for the command.

    Returns
    -------
    DisposeCallable
        A function that can be called to unregister the command.
    """
    if id in self._commands:
        raise ValueError(
            f"Command {id!r} already registered with callback "
            f"{self._commands[id].callback!r} (new callback: {callback!r})"
        )

    cmd = RegisteredCommand(id, callback, title, self._injection_store)
    self._commands[id] = cmd

    def _dispose() -> None:
        self._commands.pop(id, None)

    self.registered.emit(id)
    return _dispose

KeyBindingsRegistry #

KeyBindingsRegistry()

Registry for keybindings.

Source code in app_model/registries/_keybindings_reg.py
33
34
def __init__(self) -> None:
    self._keybindings: list[_RegisteredKeyBinding] = []

get_keybinding #

get_keybinding(key: str) -> _RegisteredKeyBinding | None

Return the first keybinding that matches the given command ID.

Source code in app_model/registries/_keybindings_reg.py
118
119
120
121
122
123
def get_keybinding(self, key: str) -> _RegisteredKeyBinding | None:
    """Return the first keybinding that matches the given command ID."""
    # TODO: improve me.
    return next(
        (entry for entry in self._keybindings if entry.command_id == key), None
    )

register_action_keybindings #

register_action_keybindings(action: Action) -> DisposeCallable | None

Register all keybindings declared in action.keybindings.

Parameters:

  • action (Action) –

    The action to register keybindings for.

Returns:

  • DisposeCallable | None

    A function that can be called to unregister the keybindings. If no keybindings were registered, returns None.

Source code in app_model/registries/_keybindings_reg.py
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
def register_action_keybindings(self, action: Action) -> DisposeCallable | None:
    """Register all keybindings declared in `action.keybindings`.

    Parameters
    ----------
    action : Action
        The action to register keybindings for.

    Returns
    -------
    DisposeCallable | None
        A function that can be called to unregister the keybindings.  If no
        keybindings were registered, returns None.
    """
    if not (keybindings := action.keybindings):
        return None

    disposers: list[Callable[[], None]] = []
    for keyb in keybindings:
        if action.enablement is not None:
            kwargs = keyb.model_dump()
            kwargs["when"] = (
                action.enablement
                if keyb.when is None
                else action.enablement | keyb.when
            )
            _keyb = type(keyb)(**kwargs)
        else:
            _keyb = keyb
        if d := self.register_keybinding_rule(action.id, _keyb):
            disposers.append(d)

    if not disposers:  # pragma: no cover
        return None

    def _dispose() -> None:
        for disposer in disposers:
            disposer()

    return _dispose

register_keybinding_rule #

register_keybinding_rule(id: str, rule: KeyBindingRule) -> DisposeCallable | None

Register a new keybinding rule.

Parameters:

  • id (str) –

    Command identifier that should be run when the keybinding is triggered

  • rule (KeyBindingRule) –

    KeyBinding information

Returns:

  • Optional[DisposeCallable]

    A callable that can be used to unregister the keybinding

Source code in app_model/registries/_keybindings_reg.py
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
def register_keybinding_rule(
    self, id: str, rule: KeyBindingRule
) -> DisposeCallable | None:
    """Register a new keybinding rule.

    Parameters
    ----------
    id : str
        Command identifier that should be run when the keybinding is triggered
    rule : KeyBindingRule
        KeyBinding information

    Returns
    -------
    Optional[DisposeCallable]
        A callable that can be used to unregister the keybinding
    """
    if plat_keybinding := rule._bind_to_current_platform():
        keybinding = KeyBinding.validate(plat_keybinding)
        entry = _RegisteredKeyBinding(
            keybinding=keybinding,
            command_id=id,
            weight=rule.weight,
            when=rule.when,
        )
        self._keybindings.append(entry)
        self.registered.emit()

        def _dispose() -> None:
            self._keybindings.remove(entry)

        return _dispose
    return None  # pragma: no cover

MenusRegistry #

MenusRegistry()

Registry for menu and submenu items.

Source code in app_model/registries/_menus_reg.py
21
22
def __init__(self) -> None:
    self._menu_items: dict[MenuId, dict[MenuOrSubmenu, None]] = {}

append_action_menus #

append_action_menus(action: Action) -> DisposeCallable | None

Append all MenuRule items declared in action.menus.

Parameters:

  • action (Action) –

    The action containing menus to append.

Returns:

  • DisposeCallable | None

    A function that can be called to unregister the menu items. If no menu items were registered, returns None.

Source code in app_model/registries/_menus_reg.py
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
def append_action_menus(self, action: Action) -> DisposeCallable | None:
    """Append all MenuRule items declared in `action.menus`.

    Parameters
    ----------
    action : Action
        The action containing menus to append.

    Returns
    -------
    DisposeCallable | None
        A function that can be called to unregister the menu items. If no
        menu items were registered, returns `None`.
    """
    disposers: list[Callable[[], None]] = []
    disp1 = self.append_menu_items(
        (
            rule.id,
            MenuItem(
                command=action, when=rule.when, group=rule.group, order=rule.order
            ),
        )
        for rule in action.menus or ()
    )
    disposers.append(disp1)

    if action.palette:
        menu_item = MenuItem(command=action, when=action.enablement)
        disp = self.append_menu_items([(self.COMMAND_PALETTE_ID, menu_item)])
        disposers.append(disp)

    if not disposers:  # pragma: no cover
        return None

    def _dispose() -> None:
        for disposer in disposers:
            disposer()

    return _dispose

append_menu_items #

append_menu_items(items: Iterable[tuple[MenuId, MenuOrSubmenu]]) -> DisposeCallable

Append menu items to the registry.

Parameters:

  • items (Iterable[Tuple[str, MenuOrSubmenu]]) –

    Items to append.

Returns:

  • DisposeCallable

    A function that can be called to unregister the menu items.

Source code in app_model/registries/_menus_reg.py
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
def append_menu_items(
    self, items: Iterable[tuple[MenuId, MenuOrSubmenu]]
) -> DisposeCallable:
    """Append menu items to the registry.

    Parameters
    ----------
    items : Iterable[Tuple[str, MenuOrSubmenu]]
        Items to append.

    Returns
    -------
    DisposeCallable
        A function that can be called to unregister the menu items.
    """
    changed_ids: set[str] = set()
    disposers: list[Callable[[], None]] = []
    for menu_id, item in items:
        item = MenuItem._validate(item)  # type: ignore
        menu_dict = self._menu_items.setdefault(menu_id, {})
        menu_dict[item] = None
        changed_ids.add(menu_id)

        def _remove(dct: dict = menu_dict, _item: Any = item) -> None:
            dct.pop(_item, None)

        disposers.append(_remove)

    def _dispose() -> None:
        for disposer in disposers:
            disposer()
        for id_ in changed_ids:
            if not self._menu_items.get(id_):
                del self._menu_items[id_]
        self.menus_changed.emit(changed_ids)

    if changed_ids:
        self.menus_changed.emit(changed_ids)

    return _dispose

get_menu #

get_menu(menu_id: MenuId) -> list[MenuOrSubmenu]

Return menu items for menu_id.

Source code in app_model/registries/_menus_reg.py
113
114
115
116
def get_menu(self, menu_id: MenuId) -> list[MenuOrSubmenu]:
    """Return menu items for `menu_id`."""
    # using method rather than __getitem__ so that subclasses can use arguments
    return list(self._menu_items[menu_id])

iter_menu_groups #

iter_menu_groups(menu_id: MenuId) -> Iterator[list[MenuOrSubmenu]]

Iterate over menu groups for menu_id.

Groups are broken into sections (lists of menu or submenu items) based on their group attribute. And each group is sorted by order attribute.

Parameters:

  • menu_id (str) –

    The menu ID to return groups for.

Yields:

  • Iterator[List[MenuOrSubmenu]]

    Iterator of menu/submenu groups.

Source code in app_model/registries/_menus_reg.py
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
def iter_menu_groups(self, menu_id: MenuId) -> Iterator[list[MenuOrSubmenu]]:
    """Iterate over menu groups for `menu_id`.

    Groups are broken into sections (lists of menu or submenu items) based on
    their `group` attribute.  And each group is sorted by `order` attribute.

    Parameters
    ----------
    menu_id : str
        The menu ID to return groups for.

    Yields
    ------
    Iterator[List[MenuOrSubmenu]]
        Iterator of menu/submenu groups.
    """
    if menu_id in self:
        yield from _sort_groups(self.get_menu(menu_id))

RegisteredCommand #

RegisteredCommand(id: str, callback: Callable[P, R] | str, title: str, store: Store | None = None)

Bases: Generic[P, R]

Small object to represent a command in the CommandsRegistry.

Used internally by the CommandsRegistry.

This helper class allows us to cache the dependency-injected variant of the command, so that type resolution and dependency injection is performed only once.

Source code in app_model/registries/_commands_reg.py
48
49
50
51
52
53
54
55
56
57
58
59
60
61
def __init__(
    self,
    id: str,
    callback: Callable[P, R] | str,
    title: str,
    store: Store | None = None,
) -> None:
    self.id = id
    self.callback = callback
    self.title = title
    self._injection_store: Store = store or Store.get_store()
    self._resolved_callback = callback if callable(callback) else None
    self._injected_callback: Callable[P, R] | None = None
    self._initialized = True

resolved_callback property #

resolved_callback: Callable[P, R]

Return the resolved command callback.

This property is cached, so the callback types are only resolved once.

run_injected property #

run_injected: Callable[P, R]

Return the command callback with dependencies injected.

This property is cached, so the injected version is only created once.

register_action #

register_action(app: Application | str, id_or_action: str | Action, title: str | None = None, *, callback: CommandCallable | None = None, category: str | None = None, tooltip: str | None = None, icon: IconOrDict | None = None, enablement: expressions.Expr | None = None, menus: list[MenuRuleOrDict] | None = None, keybindings: list[KeyBindingRuleOrDict] | None = None, palette: bool = True) -> CommandDecorator | DisposeCallable

Register an action.

This is a functional form of the Application.register_action() method. It accepts various overloads to allow for a more concise syntax. See examples below.

An Action is the "complete" representation of a command. The command is the function/callback itself, and an action also includes information about where and whether it appears in menus and optional keybinding rules. Since, most of the arguments to this function are simply passed through to the Action constructor, see also docstrings for:

Parameters:

  • app (Application | str) –

    The app in which to register the action. If a string, the app is retrieved or created as necessary using Application.get_or_create(app).

  • id_or_action (str | Action) –

    Either a complete Action object or a string id of the command being registered. If an Action object is provided, then all other arguments are ignored.

  • title (str | None, default: None ) –

    Title by which the command is represented in the UI. Required when id_or_action is a string.

  • callback (CommandHandler | None, default: None ) –

    Callable object that executes this command, by default None. If not provided, a decorator is returned that can be used to decorate a function that executes this action.

  • category (str | None, default: None ) –

    Category string by which the command may be grouped in the UI, by default None

  • tooltip (str | None, default: None ) –

    Tooltip to show when hovered., by default None

  • icon (Icon | None, default: None ) –

    Icon used to represent this command, e.g. on buttons or in menus. by default None

  • enablement (Expr | None, default: None ) –

    Condition which must be true to enable the command in in the UI, by default None

  • menus (list[MenuRuleOrDict] | None, default: None ) –

    List of MenuRule or kwarg dicts containing menu placements for this action, by default None

  • keybindings (list[KeyBindingRuleOrDict] | None, default: None ) –

    List of KeyBindingRule or kwargs dicts containing default keybindings for this action, by default None

  • palette (bool, default: True ) –

    Whether to adds this command to the Command Palette, by default True

Returns:

  • CommandDecorator

    If callback is not provided, then a decorator is returned that can be used to decorate a function as the executor of the command.

  • DisposeCallable

    If callback is provided, or id_or_action is an Action object, then a function is returned that may be used to unregister the action.

Raises:

  • ValueError

    If id_or_action is a string and title is not provided.

  • TypeError

    If id_or_action is not a string or an Action object.

Examples:

This function can be used directly or as a decorator, and accepts arguments in various forms.

Passing an existing Action object#

When the id_or_action argument is an instance of app_model.Action, then all other arguments are ignored, the action object is registered directly, and the return value is a function that may be used to unregister the action is returned.

from app_model import Application, Action, register_action

app = Application.get_or_create("myapp")
action = Action("my_action", title="My Action", callback=lambda: print("hi"))
register_action(app, action)

app.commands.execute_command("my_action")  # prints "hi"

Creating a new Action#

When the id_or_action argument is a string, it is interpreted as the id of the command being registered, in which case title must then also be provided. All other arguments are optional, but may be used to customize the action being created (with keybindings, menus, icons, etc).

register_action(
    app,
    "my_action2",
    title="My Action2",
    callback=lambda: print("hello again!"),
)

app.commands.execute_command("my_action2")  # prints "hello again!"

Usage as a decorator#

If callback is not provided, then a decorator is returned that can be used decorate a function as the executor of the command:

@register_action(app, "my_action3", title="My Action3")
def my_action3():
    print("hello again, again!")


app.commands.execute_command("my_action3")  # prints "hello again, again!"

Passing app as a string#

Note that in all of the above examples, the first app argument may be either an instance of an Application object, or a string name of an application. If a string is provided, then the application is retrieved or created as necessary using Application.get_or_create().

register_action(
    "myapp",  # app name instead of Application instance
    "my_action4",
    title="My Action4",
    callback=lambda: print("hello again, again, again!"),
)
Source code in app_model/registries/_register.py
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
def register_action(
    app: Application | str,
    id_or_action: str | Action,
    title: str | None = None,
    *,
    callback: CommandCallable | None = None,
    category: str | None = None,
    tooltip: str | None = None,
    icon: IconOrDict | None = None,
    enablement: expressions.Expr | None = None,
    menus: list[MenuRuleOrDict] | None = None,
    keybindings: list[KeyBindingRuleOrDict] | None = None,
    palette: bool = True,
) -> CommandDecorator | DisposeCallable:
    """Register an action.

    This is a functional form of the
    [`Application.register_action()`][app_model.Application.register_action] method.
    It accepts various overloads to allow for a more concise syntax.  See examples
    below.

    An `Action` is the "complete" representation of a command.  The command is the
    function/callback itself, and an action also includes information about where and
    whether it appears in menus and optional keybinding rules.  Since, most of the
    arguments to this function are simply passed through to the `Action` constructor,
    see also docstrings for:

    - [`Action`][app_model.types.Action]
    - [`CommandRule`][app_model.types.CommandRule]
    - [`MenuRule`][app_model.types.MenuRule]
    - [`KeyBindingRule`][app_model.types.KeyBindingRule]

    Parameters
    ----------
    app: Application | str
        The app in which to register the action. If a string, the app is retrieved
        or created as necessary using
        [`Application.get_or_create(app)`][app_model.Application.get_or_create].
    id_or_action : str | Action
        Either a complete Action object or a string id of the command being registered.
        If an `Action` object is provided, then all other arguments are ignored.
    title : str | None
        Title by which the command is represented in the UI. Required when
        `id_or_action` is a string.
    callback : CommandHandler | None
        Callable object that executes this command, by default None. If not provided,
        a decorator is returned that can be used to decorate a function that executes
        this action.
    category : str | None
        Category string by which the command may be grouped in the UI, by default None
    tooltip : str | None
        Tooltip to show when hovered., by default None
    icon : Icon | None
        [`Icon`][app_model.types.Icon] used to represent this command,
        e.g. on buttons or in menus. by default None
    enablement : expressions.Expr | None
        Condition which must be true to enable the command in in the UI,
        by default None
    menus : list[MenuRuleOrDict] | None
        List of [`MenuRule`][app_model.types.MenuRule] or kwarg `dicts` containing menu
        placements for this action, by default None
    keybindings : list[KeyBindingRuleOrDict] | None
        List of [`KeyBindingRule`][app_model.types.KeyBindingRule] or kwargs `dicts`
        containing default keybindings for this action, by default None
    palette : bool
        Whether to adds this command to the Command Palette, by default True

    Returns
    -------
    CommandDecorator
        If `callback` is not provided, then a decorator is returned that can be used to
        decorate a function as the executor of the command.
    DisposeCallable
        If `callback` is provided, or `id_or_action` is an `Action` object, then a
        function is returned that may be used to unregister the action.

    Raises
    ------
    ValueError
        If `id_or_action` is a string and `title` is not provided.
    TypeError
        If `id_or_action` is not a string or an `Action` object.

    Examples
    --------
    This function can be used directly or as a decorator, and accepts arguments in
    various forms.

    ## Passing an existing Action object

    When the `id_or_action` argument is an instance of `app_model.Action`, then
    all other arguments are ignored, the action object is registered directly, and the
    return value is a function that may be used to unregister the action is returned.

    ```python
    from app_model import Application, Action, register_action

    app = Application.get_or_create("myapp")
    action = Action("my_action", title="My Action", callback=lambda: print("hi"))
    register_action(app, action)

    app.commands.execute_command("my_action")  # prints "hi"
    ```

    ## Creating a new Action

    When the `id_or_action` argument is a string, it is interpreted as the `id`
    of the command being registered, in which case `title` must then also be provided.
    All other arguments are optional, but may be used to customize the action being
    created (with keybindings, menus, icons, etc).

    ```python
    register_action(
        app,
        "my_action2",
        title="My Action2",
        callback=lambda: print("hello again!"),
    )

    app.commands.execute_command("my_action2")  # prints "hello again!"
    ```

    ## Usage as a decorator

    If `callback` is not provided, then a decorator is returned that can be used
    decorate a function as the executor of the command:

    ```python
    @register_action(app, "my_action3", title="My Action3")
    def my_action3():
        print("hello again, again!")


    app.commands.execute_command("my_action3")  # prints "hello again, again!"
    ```

    ## Passing app as a string

    Note that in all of the above examples, the first `app` argument may be either an
    instance of an [`Application`][app_model.Application] object, or a string name of
    an application.  If a string is provided, then the application is retrieved or
    created as necessary using
    [`Application.get_or_create()`][app_model.Application.get_or_create].

    ```python
    register_action(
        "myapp",  # app name instead of Application instance
        "my_action4",
        title="My Action4",
        callback=lambda: print("hello again, again, again!"),
    )
    ```
    """
    if isinstance(id_or_action, Action):
        return _register_action_obj(app, id_or_action)
    if isinstance(id_or_action, str):
        if not title:
            raise ValueError("'title' is required when 'id' is a string")
        return _register_action_str(
            app=app,
            id=id_or_action,
            title=title,
            category=category,
            tooltip=tooltip,
            icon=icon,
            enablement=enablement,
            callback=callback,
            palette=palette,
            menus=menus,
            keybindings=keybindings,
        )
    raise TypeError("'id_or_action' must be a string or an Action")