Skip to content

Commit c1ae23b

Browse files
authored
Merge branch 'main' into enable_suspend
2 parents 79b783e + e57cd62 commit c1ae23b

12 files changed

Lines changed: 101 additions & 71 deletions

File tree

.github/workflows/typecheck.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,4 @@ jobs:
3232
python-version: ${{ matrix.python-version }}
3333

3434
- name: Check typing
35-
run: uv run mypy .
35+
run: uv run ty check

Makefile

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ check: ## Run code quality tools.
1818
@uv lock --locked
1919
@echo "🚀 Auto-formatting/Linting code and documentation: Running prek"
2020
@uv run prek run -a
21-
@echo "🚀 Static type checking: Running mypy"
22-
@uv run mypy
21+
@echo "🚀 Static type checking: Running ty"
22+
@uv run ty check
2323

2424
.PHONY: format
2525
format: ## Perform ruff formatting
@@ -31,7 +31,7 @@ lint: ## Perform ruff linting
3131

3232
.PHONY: typecheck
3333
typecheck: ## Perform type checking
34-
@uv run mypy
34+
@uv run ty check
3535

3636
.PHONY: test
3737
test: ## Test the code with pytest.
@@ -76,7 +76,7 @@ publish: validate-tag build ## Publish a release to PyPI, uses token from ~/.pyp
7676
# Define variables for files/directories to clean
7777
BUILD_DIRS = build dist *.egg-info
7878
DOC_DIRS = build
79-
MYPY_DIRS = .mypy_cache dmypy.json dmypy.sock
79+
TY_DIRS = .ty_cache .red_knot_cache
8080
TEST_DIRS = .cache .pytest_cache htmlcov
8181
TEST_FILES = .coverage coverage.xml
8282

@@ -90,10 +90,10 @@ clean-docs: ## Clean documentation artifacts
9090
@echo "🚀 Removing documentation artifacts"
9191
@uv run python -c "import shutil; import os; [shutil.rmtree(d, ignore_errors=True) for d in '$(DOC_DIRS)'.split() if os.path.isdir(d)]"
9292

93-
.PHONY: clean-mypy
94-
clean-mypy: ## Clean mypy artifacts
95-
@echo "🚀 Removing mypy artifacts"
96-
@uv run python -c "import shutil; import os; [shutil.rmtree(d, ignore_errors=True) for d in '$(MYPY_DIRS)'.split() if os.path.isdir(d)]"
93+
.PHONY: clean-ty
94+
clean-ty: ## Clean ty artifacts
95+
@echo "🚀 Removing ty artifacts"
96+
@uv run python -c "import shutil; import os; [shutil.rmtree(d, ignore_errors=True) for d in '$(TY_DIRS)'.split() if os.path.isdir(d)]"
9797

9898
.PHONY: clean-pycache
9999
clean-pycache: ## Clean pycache artifacts
@@ -112,7 +112,7 @@ clean-test: ## Clean test artifacts
112112
@uv run python -c "from pathlib import Path; [Path(f).unlink(missing_ok=True) for f in '$(TEST_FILES)'.split()]"
113113

114114
.PHONY: clean
115-
clean: clean-build clean-docs clean-mypy clean-pycache clean-ruff clean-test ## Clean all artifacts
115+
clean: clean-build clean-docs clean-ty clean-pycache clean-ruff clean-test ## Clean all artifacts
116116
@echo "🚀 Cleaned all artifacts"
117117

118118
.PHONY: help

cmd2/annotated.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -708,10 +708,10 @@ def __init__(self, *args: Any, container_factory: Callable[[list[Any]], Any] | N
708708

709709
def __call__(
710710
self,
711-
_parser: argparse.ArgumentParser,
711+
parser: argparse.ArgumentParser, # noqa: ARG002
712712
namespace: argparse.Namespace,
713713
values: Any,
714-
_option_string: str | None = None,
714+
option_string: str | None = None, # noqa: ARG002
715715
) -> None:
716716
result = values
717717
if self._container_factory is not None and isinstance(values, list):
@@ -879,7 +879,7 @@ def _resolve_union(
879879
raise TypeError(f"Union type {type_names} is ambiguous for auto-resolution.")
880880

881881
parts = [_resolve_base_type(member, allow_unknown_entry=allow_unknown_entry) for member in non_none]
882-
# Every part is an Enum (guarded above), so each has a converter; the None-filter keeps mypy happy.
882+
# Every part is an Enum (guarded above), so each has a converter; the None-filter keeps the type checker happy.
883883
converters = [part.converter for part in parts if part.converter is not None]
884884
choices = _dedupe_choices(choice for part in parts for choice in (part.choices or []))
885885

@@ -2189,7 +2189,7 @@ def _find_argument_block(hint: Any) -> type[ArgumentBlock] | None:
21892189
return None
21902190

21912191

2192-
def _init_field_names(dc_type: type) -> list[str]:
2192+
def _init_field_names(dc_type: Any) -> list[str]:
21932193
"""Names of a dataclass's ``init`` fields in definition order (the flat argument names of a block)."""
21942194
return [f.name for f in fields(dc_type) if f.init]
21952195

@@ -3113,6 +3113,9 @@ def cmd_wrapper(*args: Any, **kwargs: Any) -> bool | None:
31133113
except SystemExit as exc:
31143114
raise Cmd2ArgparseError from exc
31153115

3116+
if ns is None:
3117+
raise ValueError("ns is None")
3118+
31163119
setattr(ns, constants.NS_ATTR_STATEMENT, statement)
31173120
handler = getattr(ns, constants.NS_ATTR_SUBCOMMAND_FUNC, None)
31183121
if base_command and handler is not None:

cmd2/argparse_utils.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -747,8 +747,8 @@ def __init__(
747747
super().__init__(
748748
prog=prog,
749749
usage=usage,
750-
description=description, # type: ignore[arg-type]
751-
epilog=epilog, # type: ignore[arg-type]
750+
description=description, # type: ignore[arg-type, ty:invalid-argument-type]
751+
epilog=epilog, # type: ignore[arg-type, ty:invalid-argument-type]
752752
parents=parents,
753753
formatter_class=formatter_class,
754754
prefix_chars=prefix_chars,
@@ -772,15 +772,15 @@ def __init__(
772772
self.description: HelpContent | None # type: ignore[assignment]
773773
self.epilog: HelpContent | None # type: ignore[assignment]
774774

775-
def print_usage(self, file: IO[str] | None = None) -> None: # type:ignore[override]
775+
def print_usage(self, file: IO[str] | None = None) -> None: # type: ignore[override, ty:invalid-method-override]
776776
"""Override to ensure the formatter is aware of the target file."""
777777
if file is None:
778778
file = self._thread_locals.current_output_file
779779

780780
with self.output_to(file):
781781
super().print_usage(file)
782782

783-
def print_help(self, file: IO[str] | None = None) -> None: # type:ignore[override]
783+
def print_help(self, file: IO[str] | None = None) -> None: # type: ignore[override, ty:invalid-method-override]
784784
"""Override to ensure the formatter is aware of the target file."""
785785
if file is None:
786786
file = self._thread_locals.current_output_file
@@ -831,7 +831,7 @@ def _build_subparsers_prog_prefix(self, positionals: list[argparse.Action]) -> s
831831
temp_parser = Cmd2ArgumentParser(
832832
prog=self.prog,
833833
usage=None,
834-
formatter_class=self.formatter_class,
834+
formatter_class=cast(type[Cmd2HelpFormatter], self.formatter_class),
835835
add_help=False,
836836
)
837837

@@ -1037,7 +1037,8 @@ def error(self, message: str) -> NoReturn:
10371037

10381038
def _get_formatter(self, *_args: Any, **_kwargs: Any) -> Cmd2HelpFormatter:
10391039
"""Override with customizations for Cmd2HelpFormatter."""
1040-
return self.formatter_class(prog=self.prog, file=self._thread_locals.current_output_file)
1040+
formatter_class = cast(type[Cmd2HelpFormatter], self.formatter_class)
1041+
return formatter_class(prog=self.prog, file=self._thread_locals.current_output_file)
10411042

10421043
def format_help(self, *args: Any, **kwargs: Any) -> str:
10431044
"""Override to add a newline."""

cmd2/cmd2.py

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -850,7 +850,7 @@ def _autoload_commands(self) -> None:
850850
all_commandset_defs = CommandSet.__subclasses__()
851851
existing_commandset_types = [type(command_set) for command_set in self._installed_command_sets]
852852

853-
def load_commandset_by_type(commandset_types: list[type[CommandSet[Any]]]) -> None:
853+
def load_commandset_by_type(commandset_types: Sequence[type[CommandSet[Any]]]) -> None:
854854
for cmdset_type in commandset_types:
855855
# check if the type has sub-classes. We will only auto-load leaf class types.
856856
subclasses = cmdset_type.__subclasses__()
@@ -2548,11 +2548,11 @@ def _perform_completion(
25482548
completer.complete, tokens=raw_tokens[1:] if spec.preserve_quotes else tokens[1:], cmd_set=cmd_set
25492549
)
25502550
else:
2551-
completer_func = self.completedefault # type: ignore[assignment]
2551+
completer_func = self.completedefault # type: ignore[assignment, ty:invalid-assignment]
25522552

25532553
# Not a recognized macro or command
25542554
else:
2555-
completer_func = self.completedefault # type: ignore[assignment]
2555+
completer_func = self.completedefault # type: ignore[assignment, ty:invalid-assignment]
25562556

25572557
# Otherwise we are completing the command token or performing custom completion
25582558
else:
@@ -2969,7 +2969,7 @@ def onecmd_plus_hooks(
29692969
with self.sigint_protection:
29702970
if py_bridge_call:
29712971
# Start saving command's stdout at this point
2972-
self.stdout.pause_storage = False # type: ignore[attr-defined]
2972+
self.stdout.pause_storage = False # type: ignore[attr-defined, ty:invalid-assignment]
29732973

29742974
redir_saved_state = self._redirect_output(statement)
29752975

@@ -3008,7 +3008,7 @@ def onecmd_plus_hooks(
30083008

30093009
if py_bridge_call:
30103010
# Stop saving command's stdout before command finalization hooks run
3011-
self.stdout.pause_storage = True # type: ignore[attr-defined]
3011+
self.stdout.pause_storage = True # type: ignore[attr-defined, ty:invalid-assignment]
30123012
except (SkipPostcommandHooks, EmptyStatement):
30133013
# Don't do anything, but do allow command finalization hooks to run
30143014
pass
@@ -3513,7 +3513,7 @@ def _read_raw_input(
35133513
self.active_session = self.main_session
35143514

35153515
# We're not at a terminal, so we're likely reading from a file or a pipe.
3516-
prompt_obj = prompt() if callable(prompt) else prompt
3516+
prompt_obj = prompt if isinstance(prompt, (ANSI, str)) else prompt()
35173517
prompt_str = prompt_obj.value if isinstance(prompt_obj, ANSI) else prompt_obj
35183518

35193519
# If this is an interactive pipe, then display the prompt first
@@ -3803,7 +3803,7 @@ def _build_alias_parser() -> Cmd2ArgumentParser:
38033803
"An alias is a command that enables replacement of a word by another string.",
38043804
)
38053805
alias_parser = argparse_utils.DEFAULT_ARGUMENT_PARSER(description=alias_description)
3806-
alias_parser.epilog = TextGroup(
3806+
alias_parser.epilog = TextGroup( # type: ignore[assignment, ty:invalid-assignment]
38073807
"See Also",
38083808
"macro",
38093809
)
@@ -3835,7 +3835,7 @@ def _build_alias_create_parser(cls) -> Cmd2ArgumentParser:
38353835
"for the actual command the alias resolves to."
38363836
),
38373837
)
3838-
alias_create_parser.epilog = TextGroup("Notes", alias_create_notes)
3838+
alias_create_parser.epilog = TextGroup("Notes", alias_create_notes) # type: ignore[assignment, ty:invalid-assignment]
38393839

38403840
# Add arguments
38413841
alias_create_parser.add_argument("name", help="name of this alias")
@@ -4017,7 +4017,7 @@ def _build_macro_parser() -> Cmd2ArgumentParser:
40174017
"A macro is similar to an alias, but it can contain argument placeholders.",
40184018
)
40194019
macro_parser = argparse_utils.DEFAULT_ARGUMENT_PARSER(description=macro_description)
4020-
macro_parser.epilog = TextGroup(
4020+
macro_parser.epilog = TextGroup( # type: ignore[assignment, ty:invalid-assignment]
40214021
"See Also",
40224022
"alias",
40234023
)
@@ -4080,7 +4080,7 @@ def _build_macro_create_parser(cls) -> Cmd2ArgumentParser:
40804080
"This default behavior changes if custom completion for macro arguments has been implemented."
40814081
),
40824082
)
4083-
macro_create_parser.epilog = TextGroup("Notes", macro_create_notes)
4083+
macro_create_parser.epilog = TextGroup("Notes", macro_create_notes) # type: ignore[assignment, ty:invalid-assignment]
40844084

40854085
# Add arguments
40864086
macro_create_parser.add_argument("name", help="name of this macro")
@@ -4575,7 +4575,7 @@ def do_shortcuts(self, _: argparse.Namespace) -> None:
45754575
@staticmethod
45764576
def _build__eof_parser() -> Cmd2ArgumentParser:
45774577
_eof_parser = argparse_utils.DEFAULT_ARGUMENT_PARSER(description="Called when Ctrl-D is pressed.")
4578-
_eof_parser.epilog = TextGroup(
4578+
_eof_parser.epilog = TextGroup( # type: ignore[assignment, ty:invalid-assignment]
45794579
"Note",
45804580
"This command is for internal use and is not intended to be called from the command line.",
45814581
)
@@ -5035,7 +5035,7 @@ def py_quit() -> None:
50355035
# Check if we are running Python code
50365036
if py_code_to_run:
50375037
try: # noqa: SIM105
5038-
interp.runcode(py_code_to_run) # type: ignore[arg-type]
5038+
interp.runcode(py_code_to_run) # type: ignore[arg-type, ty:invalid-argument-type]
50395039
except BaseException: # noqa: BLE001, S110
50405040
# We don't care about any exception that happened in the Python code
50415041
pass
@@ -5421,11 +5421,11 @@ def _initialize_history(self, hist_file: str) -> None:
54215421
try:
54225422
import lzma as decompress_lib
54235423

5424-
decompress_exceptions: tuple[type[Exception]] = (decompress_lib.LZMAError,)
5424+
decompress_exceptions: tuple[type[Exception], ...] = (decompress_lib.LZMAError,)
54255425
except ModuleNotFoundError: # pragma: no cover
54265426
import bz2 as decompress_lib # type: ignore[no-redef]
54275427

5428-
decompress_exceptions: tuple[type[Exception]] = (OSError, ValueError) # type: ignore[no-redef]
5428+
decompress_exceptions: tuple[type[Exception], ...] = (OSError, ValueError) # type: ignore[no-redef]
54295429

54305430
try:
54315431
history_json = decompress_lib.decompress(compressed_bytes).decode(encoding="utf-8")
@@ -5474,7 +5474,7 @@ def _persist_history(self) -> None:
54745474
def _build_edit_parser(cls) -> Cmd2ArgumentParser:
54755475
edit_description = "Run a text editor and optionally open a file with it."
54765476
edit_parser = argparse_utils.DEFAULT_ARGUMENT_PARSER(description=edit_description)
5477-
edit_parser.epilog = TextGroup(
5477+
edit_parser.epilog = TextGroup( # type: ignore[assignment, ty:invalid-assignment]
54785478
"Note",
54795479
Text.assemble(
54805480
"To set a new editor, run: ",
@@ -5596,7 +5596,7 @@ def _build__relative_run_script_parser(cls) -> Cmd2ArgumentParser:
55965596
_relative_run_script_parser = cls._build_base_run_script_parser()
55975597

55985598
# Append to existing description
5599-
_relative_run_script_parser.description = Group(
5599+
_relative_run_script_parser.description = Group( # type: ignore[assignment, ty:invalid-assignment]
56005600
cast(Group, _relative_run_script_parser.description),
56015601
"\n",
56025602
(
@@ -5605,7 +5605,7 @@ def _build__relative_run_script_parser(cls) -> Cmd2ArgumentParser:
56055605
),
56065606
)
56075607

5608-
_relative_run_script_parser.epilog = TextGroup(
5608+
_relative_run_script_parser.epilog = TextGroup( # type: ignore[assignment, ty:invalid-assignment]
56095609
"Note",
56105610
"This command is intended to be used from within a text script.",
56115611
)

cmd2/decorators.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,7 @@ def arg_decorator(func: ArgparseCommandFunc[CmdOrSetT]) -> RawCommandFunc[CmdOrS
310310
:return: Function that takes raw input and converts to an argparse Namespace to passed to the wrapped function.
311311
"""
312312

313-
@functools.wraps(func)
313+
@functools.wraps(func) # type: ignore[arg-type, ty:invalid-argument-type]
314314
def cmd_wrapper(*args: Any, **kwargs: Any) -> bool | None:
315315
"""Command function wrapper which translates command line into argparse Namespace and call actual command function.
316316
@@ -345,9 +345,9 @@ def cmd_wrapper(*args: Any, **kwargs: Any) -> bool | None:
345345
parsing_results: tuple[argparse.Namespace] | tuple[argparse.Namespace, list[str]]
346346
with arg_parser.output_to(cmd_app.stdout):
347347
if with_unknown_args:
348-
parsing_results = arg_parser.parse_known_args(command_arg_list, initial_namespace)
348+
parsing_results = arg_parser.parse_known_args(command_arg_list, initial_namespace) # type: ignore[assignment, ty:invalid-assignment]
349349
else:
350-
parsing_results = (arg_parser.parse_args(command_arg_list, initial_namespace),)
350+
parsing_results = (arg_parser.parse_args(command_arg_list, initial_namespace),) # type: ignore[assignment, ty:invalid-assignment]
351351
except SystemExit as exc:
352352
raise Cmd2ArgparseError from exc
353353

cmd2/pt_utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ def __init__(
148148
self._cmd_app = cmd_app
149149
self.custom_settings = custom_settings
150150

151-
def get_completions(self, document: Document, _complete_event: object) -> Iterable[Completion]:
151+
def get_completions(self, document: Document, complete_event: object) -> Iterable[Completion]: # noqa: ARG002
152152
"""Get completions for the current input."""
153153
# Find the beginning of the current word based on delimiters
154154
line = document.text

cmd2/rich_utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ def __repr__(self) -> str:
105105

106106

107107
# Controls when ANSI style sequences are allowed in output
108-
ALLOW_STYLE = AllowStyle.TERMINAL
108+
ALLOW_STYLE: AllowStyle = AllowStyle.TERMINAL
109109

110110

111111
class Cmd2HelpFormatter(RichHelpFormatter):

pyproject.toml

Lines changed: 2 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,12 @@ dev = [
4040
"codecov>=2.1",
4141
"ipython>=8.23",
4242
"mkdocstrings[python]>=1",
43-
"mypy>=1.13",
4443
"prek>=0.3.5",
4544
"pytest>=8.1.1",
4645
"pytest-cov>=5",
4746
"pytest-mock>=3.14.1",
4847
"ruff>=0.14.10",
48+
"ty>=0.0.73",
4949
"uv-publish>=1.3",
5050
"zensical>=0.0.17",
5151
]
@@ -63,33 +63,7 @@ test = [
6363
"pytest-cov>=5",
6464
"pytest-mock>=3.14.1",
6565
]
66-
validate = ["mypy>=1.13", "ruff>=0.14.10", "types-setuptools>=80.8.0"]
67-
68-
[tool.mypy]
69-
disallow_incomplete_defs = true
70-
disallow_untyped_calls = true
71-
disallow_untyped_defs = true
72-
exclude = [
73-
"^.git/",
74-
"^.venv/",
75-
"^build/", # .build directory
76-
"^docs/", # docs directory
77-
"^dist/",
78-
"^examples/", # examples directory
79-
"^noxfile\\.py$", # nox config file
80-
"setup\\.py$", # any files named setup.py
81-
"^site/",
82-
"^tests/", # tests directory
83-
]
84-
files = ['.']
85-
show_column_numbers = true
86-
show_error_codes = true
87-
show_error_context = true
88-
strict = true
89-
warn_redundant_casts = true
90-
warn_return_any = true
91-
warn_unreachable = true
92-
warn_unused_ignores = false
66+
validate = ["ruff>=0.14.10", "ty>=0.0.73", "types-setuptools>=80.8.0"]
9367

9468
[tool.pytest.ini_options]
9569
testpaths = ["tests"]

ruff.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,16 @@ exclude = [
77
".git-rewrite",
88
".hg",
99
".ipynb_checkpoints",
10-
".mypy_cache",
1110
".nox",
1211
".pants.d",
1312
".pyenv",
1413
".pytest_cache",
1514
".pytype",
15+
".red_knot_cache",
1616
".ruff_cache",
1717
".svn",
1818
".tox",
19+
".ty_cache",
1920
".venv",
2021
".vscode",
2122
"__pypackages__",

0 commit comments

Comments
 (0)