New Command: Difference between revisions
PedroLacerda (talk | contribs) No edit summary |
PedroLacerda (talk | contribs) No edit summary |
||
| Line 6: | Line 6: | ||
from pymol import cmd | from pymol import cmd | ||
from pathlib import Path | from pathlib import Path | ||
from typing import | from pprint import pprint | ||
from enum import Enum, StrEnum | from typing import Any, Optional | ||
from enum import Enum#, StrEnum | |||
class CoolFlag(Enum): | class CoolFlag(Enum): | ||
| Line 15: | Line 14: | ||
SEQUENCE = 2 | SEQUENCE = 2 | ||
class Method(StrEnum): | #class Method(StrEnum): | ||
# SINGLE = "single" | |||
# COMPLETE = "complete" | |||
# WARD = "ward" | |||
Point = tuple[float, float, float] | |||
@cmd.new_command | @cmd.new_command | ||
def nice_tool( | def nice_tool( | ||
# Regular types are supported | |||
title: str, | |||
my_var: int | float, | my_var: int | float, | ||
extended_calculation: bool, | |||
# Simple composite types | |||
a_point: Point, | a_point: Point, | ||
other_point: tuple[int, int, int] = (0, 0, 0), | |||
other_point: | |||
old_style: Any = "anything as string", | old_style: Any = "anything as string", | ||
this_list: Optional[list[bool]] = None, | |||
# Support for Enum types | |||
flag: CoolFlag = CoolFlag.STRUCTURE, | flag: CoolFlag = CoolFlag.STRUCTURE, | ||
# method: Method = Method.WARD, | |||
quiet: bool = True, # | |||
_self=cmd # | # Arbitrary types that allows initializate with str values | ||
dirname: Path = Path('~'), | |||
# Special arguments | |||
quiet: bool = True, # automatic 'quiet=1' on command-line | |||
_self=cmd # used in multi-threaded applications | |||
): | ): | ||
pprint(locals()) | |||
</source> | </source> | ||
These code blocks ahead are sample usage of the above function. | These code blocks ahead are sample usage of the above function. | ||
<source | |||
PyMOL> nice_tool 10, 0.1 2.3 4.5 | <source> | ||
PyMOL> nice_tool Have a nice tool, 10, False, 0.1 2.3 4.5, this_list=true 0 yes 0, flag=SEQUENCE | |||
</source> | </source> | ||
| Line 46: | Line 58: | ||
{'_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), | 'a_point': (0.1, 2.3, 4.5), | ||
'dirname': PosixPath(' | 'dirname': PosixPath('~'), | ||
'extended_calculation': | 'extended_calculation': False, | ||
'flag': <CoolFlag.SEQUENCE: 2>, | 'flag': <CoolFlag.SEQUENCE: 2>, | ||
'method': <Method.WARD: 'ward'>, | 'method': <Method.WARD: 'ward'>, | ||
| Line 58: | Line 70: | ||
</source> | </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]. Inspect <code>cmd.do()</code> calls because they contain code exactly as they would be written | 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]. Inspect <code>cmd.do()</code> calls because they contain code exactly as they would be written into command line. | ||
== Advantages == | == Advantages == | ||
| Line 70: | Line 82: | ||
<source lang=python> | <source lang=python> | ||
# works the same for developers | # works the same for developers | ||
>>> nice_tool(10, [0.1, 2.3, 4.5] | >>> nice_tool('Have a nice tool', 10, False, [0.1, 2.3, 4.5], this_list=[True, False, True, False], flag=CoolFlag.SEQUENCE) | ||
>>> nice_tool.func(10, [0.1, 2.3, 4.5] | >>> nice_tool.func('Have a nice tool', 10, False, [0.1, 2.3, 4.5], this_list=[True, False, True, False], flag=CoolFlag.SEQUENCE) | ||
</source> | </source> | ||
| Line 79: | Line 91: | ||
# 2.5x improvement for 1000000 calls | # 2.5x improvement for 1000000 calls | ||
>>> from timeit import timeit | >>> from timeit import timeit | ||
>>> timeit("nice_tool(10, [0.1, 2.3, 4.5] | >>> timeit("nice_tool('Have a nice tool', 10, False, [0.1, 2.3, 4.5], this_list=[True, False, True, False], flag=CoolFlag.SEQUENCE)", globals=locals()) | ||
0.374392487006844 | 0.374392487006844 | ||
>>> timeit("nice_tool.func(10, [0.1, 2.3, 4.5] | >>> timeit("nice_tool.func('Have a nice tool', 10, False, [0.1, 2.3, 4.5], this_list=[True, False, True, False], flag=CoolFlag.SEQUENCE)", globals=locals()) | ||
0.12843744499696186 | 0.12843744499696186 | ||
</source> | </source> | ||
Revision as of 00:55, 30 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 pprint import pprint
from typing import Any, Optional
from enum import Enum#, StrEnum
class CoolFlag(Enum):
STRUCTURE = 1
SEQUENCE = 2
#class Method(StrEnum):
# SINGLE = "single"
# COMPLETE = "complete"
# WARD = "ward"
Point = tuple[float, float, float]
@cmd.new_command
def nice_tool(
# Regular types are supported
title: str,
my_var: int | float,
extended_calculation: bool,
# Simple composite types
a_point: Point,
other_point: tuple[int, int, int] = (0, 0, 0),
old_style: Any = "anything as string",
this_list: Optional[list[bool]] = None,
# Support for Enum types
flag: CoolFlag = CoolFlag.STRUCTURE,
# method: Method = Method.WARD,
# Arbitrary types that allows initializate with str values
dirname: Path = Path('~'),
# Special arguments
quiet: bool = True, # automatic 'quiet=1' on command-line
_self=cmd # used in multi-threaded applications
):
pprint(locals())
These code blocks ahead are sample usage of the above function.
PyMOL> nice_tool Have a nice tool, 10, False, 0.1 2.3 4.5, this_list=true 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': False,
'flag': <CoolFlag.SEQUENCE: 2>,
'method': <Method.WARD: 'ward'>,
'my_var': 10,
'old_style': 'anything as string',
'other_point': (0, 0, 0),
'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 into 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('Have a nice tool', 10, False, [0.1, 2.3, 4.5], this_list=[True, False, True, False], flag=CoolFlag.SEQUENCE)
>>> nice_tool.func('Have a nice tool', 10, False, [0.1, 2.3, 4.5], this_list=[True, False, True, False], flag=CoolFlag.SEQUENCE)
Here a quick benchmark (remove the print statement before trying it).
# 2.5x improvement for 1000000 calls
>>> from timeit import timeit
>>> timeit("nice_tool('Have a nice tool', 10, False, [0.1, 2.3, 4.5], this_list=[True, False, True, False], flag=CoolFlag.SEQUENCE)", globals=locals())
0.374392487006844
>>> timeit("nice_tool.func('Have a nice tool', 10, False, [0.1, 2.3, 4.5], this_list=[True, False, True, False], flag=CoolFlag.SEQUENCE)", globals=locals())
0.12843744499696186