New Command: Difference between revisions

From PyMOLWiki
Jump to navigation Jump to search
No edit summary
No edit summary
Line 40: Line 40:
These code blocks ahead are sample usage of the above function.
These code blocks ahead are sample usage of the above function.
<source lang=sh>
<source lang=sh>
PyMOL> nice_tool 10, 0.1 2.3 4.5, Have a nice tool, this_list=1 0 yes 0
PyMOL> nice_tool 10, 0.1 2.3 4.5, Have a nice tool, this_list=1 0 yes 0, flag=SEQUENCE
</source>
</source>


<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'>}
{'_self': <module 'pymol.cmd' from '/home/peu/Desktop/pymol-open-source/modules/pymol/cmd.py'>,
'a_point': (0.1, 2.3, 4.5),
'dirname': PosixPath('.'),
'extended_calculation': True,
'flag': <CoolFlag.SEQUENCE: 2>,
'method': <Method.WARD: 'ward'>,
'my_var': 10,
'old_style': 'anything as string',
'other_point': (0, 0, 0),
'pprint': <function pprint at 0x7f57eb0e6ac0>,
'quiet': False,
'this_list': [True, False, True, False],
'title': 'Have a nice tool'}
</source>
</source>



Revision as of 23:06, 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
from enum import Enum, StrEnum

Point = Tuple[float, float, float]

class CoolFlag(Enum):
    STRUCTURE = 1
    SEQUENCE = 2

class Method(StrEnum):
    SINGLE = "single"
    COMPLETE = "complete"
    WARD = "ward"

@cmd.new_command
def nice_tool(
    my_var: 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",
    flag: CoolFlag = CoolFlag.STRUCTURE,
    method: Method = Method.WARD,
    quiet: bool = True,  # special 'quiet=False' on command-line
    _self=cmd  # special for multi-threaded applications
):
    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, flag=SEQUENCE
{'_self': <module 'pymol.cmd' from '/home/peu/Desktop/pymol-open-source/modules/pymol/cmd.py'>,
 'a_point': (0.1, 2.3, 4.5),
 'dirname': PosixPath('.'),
 'extended_calculation': True,
 'flag': <CoolFlag.SEQUENCE: 2>,
 'method': <Method.WARD: 'ward'>,
 'my_var': 10,
 'old_style': 'anything as string',
 'other_point': (0, 0, 0),
 'pprint': <function pprint at 0x7f57eb0e6ac0>,
 'quiet': False,
 'this_list': [True, False, True, False],
 'title': 'Have a nice tool'}

If you need more examples, here a non exhaustive list of examples [1]. Inspect cmd.do() calls because they contain code exactly as they would be written in command line.

Advantages

It improves on extend, a consolidated exposing mechanism. It works by parsing the arguments given at command-line by users, enforcing correct types at runtime, ensuring typing strictness and so easing the development. 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 parse arguments, this feature introspects 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 necessary. The introspection mechanism is a simple observation on the caller stack frame filename which can be a slowness factor. Because of this, we provide also direct access with zero overhead for developers 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).

# 2.5x improvement for 1000000 calls
>>> from timeit import timeit
>>> timeit("nice_tool(10, [0.1, 2.3, 4.5], 'Have a nice tool', this_list=[True, False, True, False])", globals=locals())
0.374392487006844
>>> timeit("nice_tool.func(10, [0.1, 2.3, 4.5], 'Have a nice tool', this_list=[True, False, True, False])", globals=locals())
0.12843744499696186