-
Notifications
You must be signed in to change notification settings - Fork 328
Declaring new commands #470
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
6df8dcb
edebb39
51bbbbb
849d3b9
d921f8c
056a062
4c8e8f5
4b248c8
5a11eb3
cf75cd2
b2784ed
966f128
3f49c4f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -26,11 +26,14 @@ | |||||
| import inspect | ||||||
| import glob | ||||||
| import shlex | ||||||
| import tokenize | ||||||
| import builtins | ||||||
| from io import BytesIO | ||||||
| from enum import Enum | ||||||
| from functools import wraps | ||||||
| from pathlib import Path | ||||||
| from textwrap import dedent | ||||||
| from typing import List | ||||||
| from typing import Tuple, Iterable, get_args, Optional, Union, Any, NewType, List, get_origin | ||||||
|
|
||||||
| import re | ||||||
| import os | ||||||
|
|
@@ -600,45 +603,117 @@ def get_state_list(states_str): | |||||
| states_list = sorted(set(map(int, output))) | ||||||
| return _cmd.delete_states(_self._COb, name, states_list) | ||||||
|
|
||||||
| class Selection(str): | ||||||
| pass | ||||||
|
|
||||||
|
|
||||||
| def _parse_bool(value: str): | ||||||
| if isinstance(value, str): | ||||||
| def _into_types(type, value): | ||||||
| if repr(type) == 'typing.Any': | ||||||
| return value | ||||||
| elif type is bool: | ||||||
| if isinstance(value, bool): | ||||||
| return value | ||||||
| if value.lower() in ["yes", "1", "true", "on", "y"]: | ||||||
| return True | ||||||
| elif value.lower() in ["no", "0", "false", "off", "n"]: | ||||||
| return False | ||||||
| else: | ||||||
| raise Exception("Invalid boolean value: %s" % value) | ||||||
| elif isinstance(value, bool): | ||||||
| return value | ||||||
| else: | ||||||
| raise Exception(f"Unsuported boolean flag {value}") | ||||||
|
|
||||||
| def _parse_list_str(value): | ||||||
| return shlex.split(value) | ||||||
| raise pymol.CmdException("Invalid boolean value: %s" % value) | ||||||
|
|
||||||
| elif isinstance(type, builtins.type): | ||||||
| return type(value) | ||||||
|
|
||||||
| if origin := get_origin(type): | ||||||
| if not repr(origin).startswith('typing.') and issubclass(origin, tuple): | ||||||
| args = get_args(type) | ||||||
| new_values = [] | ||||||
| for i, new_value in enumerate(shlex.split(value)): | ||||||
| new_values.append(_into_types(args[i], new_value)) | ||||||
| return tuple(new_values) | ||||||
|
|
||||||
| elif origin == Union: | ||||||
| args = get_args(type) | ||||||
| found = False | ||||||
| for i, arg in enumerate(args): | ||||||
| try: | ||||||
| found = True | ||||||
| return _into_types(arg, value) | ||||||
| except: | ||||||
| found = False | ||||||
| if not found: | ||||||
| raise pymol.CmdException("Union was not able to cast %s" % value) | ||||||
|
||||||
| raise pymol.CmdException("Union was not able to cast %s" % value) | |
| raise pymol.CmdException(f"Union was not able to cast {value}") |
Outdated
Copilot
AI
Oct 10, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error message is misleading. The variable type here is a type annotation, not a value type. Consider using {type!r} or a more descriptive message like 'Unsupported type annotation {type}'.
| raise pymol.CmdException(f"Unsupported argument type {type}") | |
| raise pymol.CmdException(f"Unsupported type annotation {type!r}") |
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
docstring/annotation. I assume this returns a dict[param_name] = param_comment ?
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, given the tests, this only retrieves comments within the def(...) scope? I am not sure if I've ever come across parameter documentation this way--this probably should be noted somewhere.
def func(
# some value
x: int): -> int
return x * xBut
def func(x: int): -> int
:param x: some value
return x * xis what i'm more familiar with.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could also move the docstring for immediately before the declaration, so parameters came after docstring, if you like...
This is very good in my opinion because would tidify docstring with parameter documentation and typing. But tooling would not get it (e.g. integrated documentation in IDEs).
Python isn't the most syntactically flexible language but I like it plasticity enough...
I'll probably drop the documentation for now. But in my opinion we show use some strong convention, so we can generate rich documentation for new style command line plugins.
Outdated
Copilot
AI
Oct 10, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This loop will run indefinitely if it encounters a '(' token. It should use an if statement instead of while, or include proper bounds checking to prevent infinite loops.
| while tokens[i].string == "(": | |
| if tokens[i+1].string == "(": |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This loop will run indefinitely if it encounters a '(' token. It should use an if statement instead of while, or include proper bounds checking to prevent infinite loops.
Maybe the "(" happens not immediately after the "def", in fact there is the function name in between, and I always saw them in the same line. Pretty sure that that tokens[i+1] doesn't suffice to skip the full function name. This is a while cursor is not parenthesis increase i+=1.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, I'm wrong. While loop and if clause are equivalent here same there as after "def" always comes the function name and then "(".
I.e. they are the same.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd be happy if you @JarrettSJohnson could write or ask someone to write some code challenging the current implementation?
Outdated
Copilot
AI
Oct 10, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This loop can cause an IndexError when accessing tokens[i+1] if i reaches the end of the tokens list. Add bounds checking: i+1 < len(tokens) to the condition.
| while not (tokens[i].type == tokenize.NAME and tokens[i+1].string == ":"): | |
| while i+1 < len(tokens) and not (tokens[i].type == tokenize.NAME and tokens[i+1].string == ":"): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a read only function. If it is broken, may be easier to rewrite than to fix.
This loop can cause an IndexError when accessing
tokens[i+1]ifireaches the end of the tokens list. Add bounds checking:i+1 < len(tokens)to the condition.
I don't know, but seems that if you are inside a function declaration, the current token is a name, and your code is valid Python, the next token sometime it will happen to be a ":".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My mom always says to me to think before to talk. As you can see, she is correct right now as usual.
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Having all of this being called every time a command runs makes me a little nervous, especially if these commands are done in a loop. A lot of this info is just redundantly calculated?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added an attribute to direct assess the original unwrapped function. It's just cmd.foo.func() instead of cmd.foo(). Both works the same, but the first works faster.
Copilot
AI
Oct 10, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The spacing is inconsistent with missing space after the first comma. Should be [inner, 0, 0, ',', parsing.STRICT] for better readability.
| _self.keyword[name] = [inner, 0,0,',',parsing.STRICT] | |
| _self.keyword[name] = [inner, 0, 0, ',', parsing.STRICT] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error message should use f-string formatting for consistency with similar errors elsewhere in the function.