New Command: Difference between revisions

From PyMOLWiki
Jump to navigation Jump to search
No edit summary
No edit summary
Line 1: Line 1:
IN ACTIVE DEVELOPMENT
[[new_command]] is a new API-only feature which exposes an user defined Python function as a command to other users.
 
[[new_command]] is a new API-only feature which exposes a developer defined Python function as a command to other users.


== In a brief ==
== In a brief ==


If you need more examples, here a non exhaustive list of examples: [https://github.com/schrodinger/pymol-open-source/blob/9c923999732644743565deb99efcc642ecd15442/testing/tests/api/test_commanding.py]
<source lang=python>
 
<source lang="python">
from pymol import cmd
from pymol import cmd
from pathlib import Path
from pathlib import Path
Line 38: Line 34:
<source lang=python>
<source lang=python>
{'my_var': 10, 'a_point': (0.1, 2.3, 4.5), 'title': 'Have a nice tool', 'other_point': (0, 0, 0), 'dirname': PosixPath('.'), 'this_list': [True, False, True, False], 'extended_calculation': True, 'old_style': 'anything as string', 'quiet': False, '_self': <module 'pymol.cmd' from '/home/peu/Desktop/pymol-open-source/modules/pymol/cmd.py'>}
{'my_var': 10, 'a_point': (0.1, 2.3, 4.5), 'title': 'Have a nice tool', 'other_point': (0, 0, 0), 'dirname': PosixPath('.'), 'this_list': [True, False, True, False], 'extended_calculation': True, 'old_style': 'anything as string', 'quiet': False, '_self': <module 'pymol.cmd' from '/home/peu/Desktop/pymol-open-source/modules/pymol/cmd.py'>}
</source>


 
If you need more examples, here a non exhaustive list of examples: [https://github.com/schrodinger/pymol-open-source/blob/9c923999732644743565deb99efcc642ecd15442/testing/tests/api/test_commanding.py]
</source>


== Details ==
== Details ==


It improves on [[extend]], the consolidated exposing mechanism, and works by parsing arguments given by users at command-line, enforcing correct types at runtime, freeing developers from it and ensuring typing strictness. It is also advantageous for developers consuming the exposed function/command directly by the API as types can also be enforced statically by MyPy.
It improves on [[extend]], the consolidated exposing mechanism, and works by parsing arguments given by users at command-line, enforcing correct types at runtime, freeing developers from it and ensuring typing strictness. It is also advantageous for developers consuming the exposed function/command directly by the API as types can also be enforced statically by MyPy.
== Zero-overhead with direct access ==
Before really parse arguments, it is expected this feature to introspect quickly if it was called by the PyMOL parser and can benefit from further parsing refinement or if it was called by another function and this feature is not needed. However this introspection behavior consumes the Python traceback API which can be a slowness factor. Because of this, we provide direct access for developers to the function with zero overhead by the .func attribute.
<source lang=python>
# works the same for developers
>>> nice_tool(10, [0.1, 2.3, 4.5], 'Have a nice tool', this_list=[True, False, True, False])
>>> nice_tool.func(10, [0.1, 2.3, 4.5], 'Have a nice tool', this_list=[True, False, True, False])
</source>
Here a quick benchmark (remove the print statement before trying it).
<source lang=python>
from timeit import timeit
>>> timeit("nice_tool(10, [0.1, 2.3, 4.5], 'Have a nice tool', this_list=[True, False, True, False])")
6.0971875859977445
>>> timeit("nice_tool.func(10, [0.1, 2.3, 4.5], 'Have a nice tool', this_list=[True, False, True, False])")
0.12023865499941166
</source>

Revision as of 14:49, 29 November 2025

new_command is a new API-only feature which exposes an user defined Python function as a command to other users.

In a brief

from pymol import cmd
from pathlib import Path
from typing import List, Tuple, Union, Any, Optional

Point = Tuple[float, float, float]

@cmd.new_command
def nice_tool(
    my_var: Union[int | float],
    a_point: Point,
    title: str,
    other_point: Tuple[int, int, int] = (0, 0, 0),
    dirname: Path = Path('.'),
    this_list: Optional[List[bool]] = None,
    extended_calculation: bool = True,
    old_style: Any = "anything as string",
    quiet: bool = 1,  # special 'quiet=False' on command-line
    _self=cmd  # special for multi-threaded applications
):
    "A cool docstring."
    print(locals())

These code blocks ahead are sample usage of the above function.

PyMOL> nice_tool 10, 0.1 2.3 4.5, Have a nice tool, this_list=1 0 yes 0
{'my_var': 10, 'a_point': (0.1, 2.3, 4.5), 'title': 'Have a nice tool', 'other_point': (0, 0, 0), 'dirname': PosixPath('.'), 'this_list': [True, False, True, False], 'extended_calculation': True, 'old_style': 'anything as string', 'quiet': False, '_self': <module 'pymol.cmd' from '/home/peu/Desktop/pymol-open-source/modules/pymol/cmd.py'>}

If you need more examples, here a non exhaustive list of examples: [1]

Details

It improves on extend, the consolidated exposing mechanism, and works by parsing arguments given by users at command-line, enforcing correct types at runtime, freeing developers from it and ensuring typing strictness. It is also advantageous for developers consuming the exposed function/command directly by the API as types can also be enforced statically by MyPy.

Zero-overhead with direct access

Before really parse arguments, it is expected this feature to introspect quickly if it was called by the PyMOL parser and can benefit from further parsing refinement or if it was called by another function and this feature is not needed. However this introspection behavior consumes the Python traceback API which can be a slowness factor. Because of this, we provide direct access for developers to the function with zero overhead by the .func attribute.

# works the same for developers
>>> nice_tool(10, [0.1, 2.3, 4.5], 'Have a nice tool', this_list=[True, False, True, False])
>>> nice_tool.func(10, [0.1, 2.3, 4.5], 'Have a nice tool', this_list=[True, False, True, False])

Here a quick benchmark (remove the print statement before trying it).

from timeit import timeit
>>> timeit("nice_tool(10, [0.1, 2.3, 4.5], 'Have a nice tool', this_list=[True, False, True, False])")
6.0971875859977445
>>> timeit("nice_tool.func(10, [0.1, 2.3, 4.5], 'Have a nice tool', this_list=[True, False, True, False])")
0.12023865499941166