Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions GEMINI.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Instructions for Gemini CLI in a `uv` Python project

This `GEMINI.md` file provides context and instructions for the Gemini CLI when working with this
Python project, which utilizes `uv` for environment and package management.

## General Instructions

- **Environment Management:** Prefer using `uv` for all Python environment management tasks.
- **Package Installation:** Always use `uv` to install packages and ensure they are installed within
the project's virtual environment.
- **Running Scripts/Commands:**
- To run Python scripts within the project's virtual environment, use `uv run ...`.
- To run programs directly from a PyPI package (installing it on the fly if necessary), use
`uvx ...` (shortcut for `uv tool run`).
- **New Dependencies:** If a new dependency is required, please state the reason for its inclusion.

## Python Code Standards

To ensure Python code adheres to required standards, the following commands **must** be run before
creating or modifying any `.py` files:

```bash
make check
```

To run unit tests use the following command:

```bash
make test
```

To make sure the documentation builds properly, use the following command:

```bash
make docs-test
```

All 3 of the above commands should be run prior to committing code.
8 changes: 4 additions & 4 deletions cmd2/argparse_completer.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Module efines the ArgparseCompleter class which provides argparse-based tab completion to cmd2 apps.
"""Module defines the ArgparseCompleter class which provides argparse-based tab completion to cmd2 apps.

See the header of argparse_custom.py for instructions on how to use these features.
"""
Expand Down Expand Up @@ -533,7 +533,7 @@ def _format_completions(self, arg_state: _ArgumentState, completions: list[str]
if not self._cmd2_app.matches_sorted:
# If all orig_value types are numbers, then sort by that value
if all_nums:
completion_items.sort(key=lambda c: c.orig_value) # type: ignore[no-any-return]
completion_items.sort(key=lambda c: c.orig_value)

# Otherwise sort as strings
else:
Expand Down Expand Up @@ -726,12 +726,12 @@ def _complete_arg(
if not arg_choices.is_completer:
choices_func = arg_choices.choices_provider
if isinstance(choices_func, ChoicesProviderFuncWithTokens):
completion_items = choices_func(*args, **kwargs) # type: ignore[arg-type]
completion_items = choices_func(*args, **kwargs)
else: # pragma: no cover
# This won't hit because runtime checking doesn't check function argument types and will always
# resolve true above. Mypy, however, does see the difference and gives an error that can't be
# ignored. Mypy issue #5485 discusses this problem
completion_items = choices_func(*args) # type: ignore[arg-type]
completion_items = choices_func(*args)
# else case is already covered above
else:
completion_items = arg_choices
Expand Down
4 changes: 2 additions & 2 deletions cmd2/argparse_custom.py
Original file line number Diff line number Diff line change
Expand Up @@ -1433,7 +1433,7 @@ def __init__(
description=description, # type: ignore[arg-type]
epilog=epilog, # type: ignore[arg-type]
parents=parents if parents else [],
formatter_class=formatter_class, # type: ignore[arg-type]
formatter_class=formatter_class,
prefix_chars=prefix_chars,
fromfile_prefix_chars=fromfile_prefix_chars,
argument_default=argument_default,
Expand Down Expand Up @@ -1498,7 +1498,7 @@ def format_help(self) -> str:
formatter = self._get_formatter()

# usage
formatter.add_usage(self.usage, self._actions, self._mutually_exclusive_groups) # type: ignore[arg-type]
formatter.add_usage(self.usage, self._actions, self._mutually_exclusive_groups)

# description
formatter.add_text(self.description)
Expand Down
2 changes: 1 addition & 1 deletion cmd2/clipboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import typing

import pyperclip # type: ignore[import]
import pyperclip # type: ignore[import-untyped]


def get_paste_buffer() -> str:
Expand Down
36 changes: 18 additions & 18 deletions cmd2/cmd2.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@

# NOTE: When using gnureadline with Python 3.13, start_ipython needs to be imported before any readline-related stuff
with contextlib.suppress(ImportError):
from IPython import start_ipython # type: ignore[import]
from IPython import start_ipython

from .rl_utils import (
RlType,
Expand All @@ -163,7 +163,7 @@
if rl_type == RlType.NONE: # pragma: no cover
Cmd2Console(sys.stderr).print(rl_warning, style=Cmd2Style.WARNING)
else:
from .rl_utils import ( # type: ignore[attr-defined]
from .rl_utils import (
readline,
rl_force_redisplay,
)
Expand Down Expand Up @@ -1068,7 +1068,7 @@ def _unregister_subcommands(self, cmdset: Union[CommandSet, 'Cmd']) -> None:

for action in command_parser._actions:
if isinstance(action, argparse._SubParsersAction):
action.remove_parser(subcommand_name) # type: ignore[arg-type,attr-defined]
action.remove_parser(subcommand_name) # type: ignore[attr-defined]
break

@property
Expand Down Expand Up @@ -3094,11 +3094,11 @@ def _redirect_output(self, statement: Statement) -> utils.RedirectionSavedState:
kwargs['executable'] = shell

# For any stream that is a StdSim, we will use a pipe so we can capture its output
proc = subprocess.Popen( # type: ignore[call-overload] # noqa: S602
proc = subprocess.Popen( # noqa: S602
statement.pipe_to,
stdin=subproc_stdin,
stdout=subprocess.PIPE if isinstance(self.stdout, utils.StdSim) else self.stdout, # type: ignore[unreachable]
stderr=subprocess.PIPE if isinstance(sys.stderr, utils.StdSim) else sys.stderr, # type: ignore[unreachable]
stderr=subprocess.PIPE if isinstance(sys.stderr, utils.StdSim) else sys.stderr,
shell=True,
**kwargs,
)
Expand All @@ -3115,7 +3115,7 @@ def _redirect_output(self, statement: Statement) -> utils.RedirectionSavedState:
subproc_stdin.close()
new_stdout.close()
raise RedirectionError(f'Pipe process exited with code {proc.returncode} before command could run')
redir_saved_state.redirecting = True # type: ignore[unreachable]
redir_saved_state.redirecting = True
cmd_pipe_proc_reader = utils.ProcReader(proc, cast(TextIO, self.stdout), sys.stderr)

self.stdout = new_stdout
Expand Down Expand Up @@ -3351,7 +3351,7 @@ def complete_none(text: str, state: int) -> str | None: # pragma: no cover # n
parser.add_argument(
'arg',
suppress_tab_hint=True,
choices=choices, # type: ignore[arg-type]
choices=choices,
choices_provider=choices_provider,
completer=completer,
)
Expand Down Expand Up @@ -4435,7 +4435,7 @@ def complete_set_value(
arg_name,
metavar=arg_name,
help=settable.description,
choices=settable.choices, # type: ignore[arg-type]
choices=settable.choices,
choices_provider=settable.choices_provider,
completer=settable.completer,
)
Expand Down Expand Up @@ -4566,15 +4566,15 @@ def do_shell(self, args: argparse.Namespace) -> None:
# still receive the SIGINT since it is in the same process group as us.
with self.sigint_protection:
# For any stream that is a StdSim, we will use a pipe so we can capture its output
proc = subprocess.Popen( # type: ignore[call-overload] # noqa: S602
proc = subprocess.Popen( # noqa: S602
expanded_command,
stdout=subprocess.PIPE if isinstance(self.stdout, utils.StdSim) else self.stdout, # type: ignore[unreachable]
stderr=subprocess.PIPE if isinstance(sys.stderr, utils.StdSim) else sys.stderr, # type: ignore[unreachable]
stderr=subprocess.PIPE if isinstance(sys.stderr, utils.StdSim) else sys.stderr,
shell=True,
**kwargs,
)

proc_reader = utils.ProcReader(proc, cast(TextIO, self.stdout), sys.stderr) # type: ignore[arg-type]
proc_reader = utils.ProcReader(proc, cast(TextIO, self.stdout), sys.stderr)
proc_reader.wait()

# Save the return code of the application for use in a pyscript
Expand Down Expand Up @@ -4656,9 +4656,9 @@ def _set_up_py_shell_env(self, interp: InteractiveConsole) -> _SavedCmd2Env:
# Save off the current completer and set a new one in the Python console
# Make sure it tab completes from its locals() dictionary
cmd2_env.readline_settings.completer = readline.get_completer()
interp.runcode("from rlcompleter import Completer") # type: ignore[arg-type]
interp.runcode("import readline") # type: ignore[arg-type]
interp.runcode("readline.set_completer(Completer(locals()).complete)") # type: ignore[arg-type]
interp.runcode(compile("from rlcompleter import Completer", "<stdin>", "exec"))
interp.runcode(compile("import readline", "<stdin>", "exec"))
interp.runcode(compile("readline.set_completer(Completer(locals()).complete)", "<stdin>", "exec"))

# Set up sys module for the Python console
self._reset_py_display()
Expand Down Expand Up @@ -4889,18 +4889,18 @@ def do_ipy(self, _: argparse.Namespace) -> bool | None: # pragma: no cover

# Detect whether IPython is installed
try:
import traitlets.config.loader as traitlets_loader # type: ignore[import]
import traitlets.config.loader as traitlets_loader

# Allow users to install ipython from a cmd2 prompt when needed and still have ipy command work
try:
_dummy = start_ipython # noqa: F823
except NameError:
from IPython import start_ipython # type: ignore[import]
from IPython import start_ipython

from IPython.terminal.interactiveshell import ( # type: ignore[import]
from IPython.terminal.interactiveshell import (
TerminalInteractiveShell,
)
from IPython.terminal.ipapp import ( # type: ignore[import]
from IPython.terminal.ipapp import (
TerminalIPythonApp,
)
except ImportError:
Expand Down
4 changes: 2 additions & 2 deletions cmd2/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ def cmd_wrapper(*args: Any, **kwargs: Any) -> bool | None:
cmd2_app, statement = _parse_positionals(args)
_, parsed_arglist = cmd2_app.statement_parser.get_command_arg_list(command_name, statement, preserve_quotes)
args_list = _arg_swap(args, statement, parsed_arglist)
return func(*args_list, **kwargs) # type: ignore[call-arg]
return func(*args_list, **kwargs)

command_name = func.__name__[len(constants.COMMAND_FUNC_PREFIX) :]
cmd_wrapper.__doc__ = func.__doc__
Expand Down Expand Up @@ -336,7 +336,7 @@ def cmd_wrapper(*args: Any, **kwargs: dict[str, Any]) -> bool | None:
delattr(ns, constants.NS_ATTR_SUBCMD_HANDLER)

args_list = _arg_swap(args, statement_arg, *new_args)
return func(*args_list, **kwargs) # type: ignore[call-arg]
return func(*args_list, **kwargs)

command_name = func.__name__[len(constants.COMMAND_FUNC_PREFIX) :]

Expand Down
2 changes: 1 addition & 1 deletion cmd2/parsing.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ class Macro:


@dataclass(frozen=True)
class Statement(str): # type: ignore[override] # noqa: SLOT000
class Statement(str): # noqa: SLOT000
"""String subclass with additional attributes to store the results of parsing.

The ``cmd`` module in the standard library passes commands around as a
Expand Down
11 changes: 8 additions & 3 deletions cmd2/rl_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@

# Prefer statically linked gnureadline if installed due to compatibility issues with libedit
try:
import gnureadline as readline # type: ignore[import]
import gnureadline as readline # type: ignore[import-not-found]
except ImportError:
# Note: If this actually fails, you should install gnureadline on Linux/Mac or pyreadline3 on Windows.
with contextlib.suppress(ImportError):
import readline # type: ignore[no-redef]
import readline


class RlType(Enum):
Expand Down Expand Up @@ -279,7 +279,7 @@ def rl_in_search_mode() -> bool: # pragma: no cover
readline_state = ctypes.c_int.in_dll(readline_lib, "rl_readline_state").value
return bool(in_search_mode & readline_state)
if rl_type == RlType.PYREADLINE:
from pyreadline3.modes.emacs import ( # type: ignore[import]
from pyreadline3.modes.emacs import ( # type: ignore[import-not-found]
EmacsMode,
)

Expand All @@ -294,3 +294,8 @@ def rl_in_search_mode() -> bool: # pragma: no cover
)
return readline.rl.mode.process_keyevent_queue[-1] in search_funcs
return False


__all__ = [
'readline',
]
4 changes: 2 additions & 2 deletions cmd2/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -697,7 +697,7 @@ def do_echo(self, arglist):
for item in func:
setattr(item, constants.CMD_ATTR_HELP_CATEGORY, category)
elif inspect.ismethod(func):
setattr(func.__func__, constants.CMD_ATTR_HELP_CATEGORY, category) # type: ignore[attr-defined]
setattr(func.__func__, constants.CMD_ATTR_HELP_CATEGORY, category)
else:
setattr(func, constants.CMD_ATTR_HELP_CATEGORY, category)

Expand All @@ -716,7 +716,7 @@ def get_defining_class(meth: Callable[..., Any]) -> type[Any] | None:
if inspect.ismethod(meth) or (
inspect.isbuiltin(meth) and hasattr(meth, '__self__') and hasattr(meth.__self__, '__class__')
):
for cls in inspect.getmro(meth.__self__.__class__): # type: ignore[attr-defined]
for cls in inspect.getmro(meth.__self__.__class__):
if meth.__name__ in cls.__dict__:
return cls
meth = getattr(meth, '__func__', meth) # fallback to __qualname__ parsing
Expand Down
Loading