New Command: Difference between revisions
PedroLacerda (talk | contribs) |
PedroLacerda (talk | contribs) No edit summary |
||
| (27 intermediate revisions by the same user not shown) | |||
| Line 4: | Line 4: | ||
<source lang=python> | <source lang=python> | ||
from pathlib import Path | |||
from pprint import pprint | |||
from typing import Any, Optional | |||
from enum import Enum | |||
from pymol import cmd | from pymol import cmd | ||
from | from pymol_new_command import new_command | ||
from | |||
class CoolFlag(Enum): | |||
STRUCTURE = 1 | |||
SEQUENCE = 2 | |||
## Supported only on Python 3.11+ | |||
#from enum import StrEnum | |||
#class Method(StrEnum): | |||
# SINGLE = "single" | |||
# COMPLETE = "complete" | |||
# WARD = "ward" | |||
Point = | Point = tuple[float, float, float] | ||
@ | @new_command | ||
def nice_tool( | def nice_tool( | ||
my_var: | # Regular types are supported | ||
title: str, | |||
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: | this_list: Optional[list[bool]] = None, | ||
this_list: Optional[ | # Support for Enum types | ||
flag: CoolFlag = CoolFlag.STRUCTURE, | |||
old_style: Any = "anything as string", | # method: Method = Method.WARD, | ||
quiet: bool = True, # | |||
_self=cmd # | # Arbitrary types that allows initialization with str values | ||
dirname: Path = Path('~'), | |||
# Special arguments | |||
old_style: Any = "anything as string", # raw unparsed str | |||
quiet: bool = True, # automatic 'quiet=1' on command-line | |||
_self=cmd # used in multi-threaded applications | |||
): | ): | ||
pprint(locals()) | |||
</source> | </source> | ||
These code | These code block 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> | ||
<source lang=python> | <source lang=python> | ||
{' | {'_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'} | |||
</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]. | 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]. Look for and inspect <code>cmd.do()</code> calls and because they contain code exactly as they would be written into command line. | ||
== | == Installation == | ||
The PyMOL open-source builtin [[new_command]] doesn't support all features listed here. To use the full featured version, you may install and test it through my implementation experimental proposal. | |||
It is also advantageous for developers consuming the | <source lang=sh> | ||
pip install https://github.com/pslacerda1/pymol_new_command/archive/refs/heads/main.zip | |||
</source> | |||
If you have Propietary PyMOL, it's recommended to run the previous pip command on the console. Otherwise, use any standard Python package installation method you want. | |||
== Advantages == | |||
It improves on [[extend]], the standard command definition mechanism. It works by parsing the arguments given at command-line by users, enforcing correct types at runtime, and ensuring typing strictness. It is also advantageous for developers consuming the function/command directly by the API because types can also be enforced statically by MyPy. | |||
== Zero-overhead with direct access == | == Zero-overhead with direct access == | ||
Before | Before parsing arguments, this feature introspects quickly if it was called by the PyMOL parser and so can further benefit from 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 also provide direct access with zero overhead for developers by the .func attribute. | ||
<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> | ||
Here a quick benchmark (remove the print statement before trying it). | Here a quick benchmark (remove the print statement before trying it). | ||
<source lang=python> | <source lang=python> | ||
from timeit import timeit | # 2.5x improvement for 1000000 calls | ||
>>> timeit("nice_tool(10, [0.1, 2.3, 4.5] | >>> 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()) | |||
>>> timeit("nice_tool.func(10, [0.1, 2.3, 4.5] | 0.374392487006844 | ||
0. | >>> 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 | |||
</source> | </source> | ||
Latest revision as of 19:54, 22 May 2026
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 pathlib import Path
from pprint import pprint
from typing import Any, Optional
from enum import Enum
from pymol import cmd
from pymol_new_command import new_command
class CoolFlag(Enum):
STRUCTURE = 1
SEQUENCE = 2
## Supported only on Python 3.11+
#from enum import StrEnum
#class Method(StrEnum):
# SINGLE = "single"
# COMPLETE = "complete"
# WARD = "ward"
Point = tuple[float, float, float]
@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),
this_list: Optional[list[bool]] = None,
# Support for Enum types
flag: CoolFlag = CoolFlag.STRUCTURE,
# method: Method = Method.WARD,
# Arbitrary types that allows initialization with str values
dirname: Path = Path('~'),
# Special arguments
old_style: Any = "anything as string", # raw unparsed str
quiet: bool = True, # automatic 'quiet=1' on command-line
_self=cmd # used in multi-threaded applications
):
pprint(locals())
These code block 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]. Look for and inspect cmd.do() calls and because they contain code exactly as they would be written into command line.
Installation
The PyMOL open-source builtin new_command doesn't support all features listed here. To use the full featured version, you may install and test it through my implementation experimental proposal.
pip install https://github.com/pslacerda1/pymol_new_command/archive/refs/heads/main.zip
If you have Propietary PyMOL, it's recommended to run the previous pip command on the console. Otherwise, use any standard Python package installation method you want.
Advantages
It improves on extend, the standard command definition mechanism. It works by parsing the arguments given at command-line by users, enforcing correct types at runtime, and ensuring typing strictness. It is also advantageous for developers consuming the function/command directly by the API because types can also be enforced statically by MyPy.
Zero-overhead with direct access
Before parsing arguments, this feature introspects quickly if it was called by the PyMOL parser and so can further benefit from 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 also provide 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