Skip to content

Commit f8edf5f

Browse files
henryiiiCopilot
andauthored
fix(types): Better typing for .run (#1037)
* fix(types): Better typing for .run Signed-off-by: Henry Schreiner <[email protected]> * Apply suggestions from code review * Update nox/virtualenv.py Co-authored-by: Copilot <[email protected]> * Update pyproject.toml Co-authored-by: Copilot <[email protected]> --------- Signed-off-by: Henry Schreiner <[email protected]> Co-authored-by: Copilot <[email protected]>
1 parent 5dbd95e commit f8edf5f

File tree

8 files changed

+120
-90
lines changed

8 files changed

+120
-90
lines changed

nox/_cli.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
import sys
2424
import urllib.parse
2525
from pathlib import Path
26-
from typing import TYPE_CHECKING, Any, Literal, NoReturn, cast
26+
from typing import TYPE_CHECKING, Literal, NoReturn, cast
2727

2828
import packaging.requirements
2929
import packaging.utils
@@ -38,6 +38,7 @@
3838
from nox.project import load_toml
3939

4040
if TYPE_CHECKING:
41+
from argparse import Namespace
4142
from collections.abc import Generator
4243

4344
__all__ = ["execute_workflow", "main", "nox_main"]
@@ -47,7 +48,7 @@ def __dir__() -> list[str]:
4748
return __all__
4849

4950

50-
def execute_workflow(args: Any) -> int:
51+
def execute_workflow(args: Namespace) -> int:
5152
"""
5253
Execute the appropriate tasks.
5354
"""

nox/logger.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ def format(self, record: Any) -> str:
8282

8383

8484
class LoggerWithSuccessAndOutput(logging.getLoggerClass()): # type: ignore[misc]
85-
def __init__(self, name: str, level: int = logging.NOTSET):
85+
def __init__(self, name: str, level: int = logging.NOTSET) -> None:
8686
super().__init__(name, level)
8787
logging.addLevelName(SESSION_INFO, "SESSION_INFO")
8888
logging.addLevelName(SUCCESS, "SUCCESS")

nox/manifest.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -453,8 +453,8 @@ def __len__(self) -> int:
453453
def keyword_match(expression: str, keywords: Iterable[str]) -> Any:
454454
"""See if an expression matches the given set of keywords."""
455455
# TODO: see if we can use ast.literal_eval here.
456-
locals = KeywordLocals(set(keywords))
457-
return eval(expression, {}, locals) # noqa: S307
456+
my_locals = KeywordLocals(set(keywords))
457+
return eval(expression, {}, my_locals) # noqa: S307
458458

459459

460460
def _null_session_func_(session: Session) -> None:

nox/sessions.py

Lines changed: 62 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,8 @@
2525
import subprocess
2626
import sys
2727
import time
28+
import typing
2829
import unicodedata
29-
from typing import (
30-
TYPE_CHECKING,
31-
Any,
32-
NoReturn,
33-
)
3430

3531
import humanize
3632

@@ -46,7 +42,7 @@
4642
get_virtualenv,
4743
)
4844

49-
if TYPE_CHECKING:
45+
if typing.TYPE_CHECKING:
5046
import argparse
5147
from collections.abc import (
5248
Callable,
@@ -57,7 +53,12 @@
5753
Sequence,
5854
)
5955
from types import TracebackType
60-
from typing import IO
56+
from typing import (
57+
IO,
58+
Any,
59+
Literal,
60+
NoReturn,
61+
)
6162

6263
from nox._decorators import Func
6364
from nox.command import ExternalType
@@ -279,7 +280,7 @@ def install_and_run_script(
279280
stderr: int | IO[str] | None = subprocess.STDOUT,
280281
interrupt_timeout: float | None = DEFAULT_INTERRUPT_TIMEOUT,
281282
terminate_timeout: float | None = DEFAULT_TERMINATE_TIMEOUT,
282-
) -> Any | None:
283+
) -> str | bool | None:
283284
"""
284285
Install dependencies and run a Python script.
285286
"""
@@ -342,6 +343,54 @@ def _run_func(self, func: Callable[..., Any], args: Iterable[Any]) -> Any:
342343
logger.exception(f"Function {func!r} raised {e!r}.")
343344
raise nox.command.CommandFailed() from e
344345

346+
@typing.overload
347+
def run(
348+
self,
349+
*args: str | os.PathLike[str],
350+
env: Mapping[str, str | None] | None = None,
351+
include_outer_env: bool = True,
352+
silent: Literal[False] = ...,
353+
success_codes: Iterable[int] | None = None,
354+
log: bool = True,
355+
external: ExternalType | None = None,
356+
stdout: int | IO[str] | None = None,
357+
stderr: int | IO[str] | None = subprocess.STDOUT,
358+
interrupt_timeout: float | None = DEFAULT_INTERRUPT_TIMEOUT,
359+
terminate_timeout: float | None = DEFAULT_TERMINATE_TIMEOUT,
360+
) -> bool | None: ...
361+
362+
@typing.overload
363+
def run(
364+
self,
365+
*args: str | os.PathLike[str],
366+
env: Mapping[str, str | None] | None = None,
367+
include_outer_env: bool = True,
368+
silent: Literal[True],
369+
success_codes: Iterable[int] | None = None,
370+
log: bool = True,
371+
external: ExternalType | None = None,
372+
stdout: int | IO[str] | None = None,
373+
stderr: int | IO[str] | None = subprocess.STDOUT,
374+
interrupt_timeout: float | None = DEFAULT_INTERRUPT_TIMEOUT,
375+
terminate_timeout: float | None = DEFAULT_TERMINATE_TIMEOUT,
376+
) -> str | None: ...
377+
378+
@typing.overload
379+
def run(
380+
self,
381+
*args: str | os.PathLike[str],
382+
env: Mapping[str, str | None] | None = None,
383+
include_outer_env: bool = True,
384+
silent: bool,
385+
success_codes: Iterable[int] | None = None,
386+
log: bool = True,
387+
external: ExternalType | None = None,
388+
stdout: int | IO[str] | None = None,
389+
stderr: int | IO[str] | None = subprocess.STDOUT,
390+
interrupt_timeout: float | None = DEFAULT_INTERRUPT_TIMEOUT,
391+
terminate_timeout: float | None = DEFAULT_TERMINATE_TIMEOUT,
392+
) -> str | bool | None: ...
393+
345394
def run(
346395
self,
347396
*args: str | os.PathLike[str],
@@ -355,7 +404,7 @@ def run(
355404
stderr: int | IO[str] | None = subprocess.STDOUT,
356405
interrupt_timeout: float | None = DEFAULT_INTERRUPT_TIMEOUT,
357406
terminate_timeout: float | None = DEFAULT_TERMINATE_TIMEOUT,
358-
) -> Any | None:
407+
) -> str | bool | None:
359408
"""Run a command.
360409
361410
Commands must be specified as a list of strings, for example::
@@ -495,7 +544,7 @@ def run_install(
495544
stderr: int | IO[str] | None = subprocess.STDOUT,
496545
interrupt_timeout: float | None = DEFAULT_INTERRUPT_TIMEOUT,
497546
terminate_timeout: float | None = DEFAULT_TERMINATE_TIMEOUT,
498-
) -> Any | None:
547+
) -> str | bool | None:
499548
"""Run a command in the install step.
500549
501550
This is a variant of :meth:`run` that runs even in the presence of
@@ -578,7 +627,7 @@ def run_always(
578627
stderr: int | IO[str] | None = subprocess.STDOUT,
579628
interrupt_timeout: float | None = DEFAULT_INTERRUPT_TIMEOUT,
580629
terminate_timeout: float | None = DEFAULT_TERMINATE_TIMEOUT,
581-
) -> Any | None:
630+
) -> str | bool | None:
582631
"""This is an alias to ``run_install``, which better describes the use case.
583632
584633
:meta private:
@@ -611,7 +660,7 @@ def _run(
611660
stderr: int | IO[str] | None,
612661
interrupt_timeout: float | None,
613662
terminate_timeout: float | None,
614-
) -> Any:
663+
) -> str | bool:
615664
"""Like run(), except that it runs even if --install-only is provided."""
616665
# Legacy support - run a function given.
617666
if callable(args[0]):
@@ -1167,7 +1216,7 @@ def execute(self) -> Result:
11671216
logger.error(f"Session {self.friendly_name} interrupted.")
11681217
raise
11691218

1170-
except Exception as exc:
1219+
except Exception as exc: # noqa: BLE001
11711220
logger.exception(f"Session {self.friendly_name} raised exception {exc!r}")
11721221
self.result = Result(
11731222
self, Status.FAILED, duration=time.perf_counter() - start

nox/virtualenv.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ def pbs_install_python(python_version: str) -> str | None:
232232
version_dir=True,
233233
implementation=implementation,
234234
)
235-
except Exception as err:
235+
except Exception as err: # noqa: BLE001
236236
logger.warning(f"Failed to install a pbs version for {python_version=}: {err}")
237237
return None
238238

@@ -444,7 +444,7 @@ def __init__(
444444
venv_params: Sequence[str] = (),
445445
conda_cmd: str = "conda",
446446
**kwargs: Any,
447-
):
447+
) -> None:
448448
self.location_name = location
449449
self.location = os.path.abspath(location)
450450
self.interpreter = interpreter
@@ -537,7 +537,7 @@ def is_offline() -> bool:
537537
try:
538538
# DNS resolution to detect situation (1) or (2).
539539
host = gethostbyname("repo.anaconda.com")
540-
except BaseException: # pragma: no cover
540+
except OSError: # pragma: no cover
541541
return True
542542
return host is None
543543

@@ -579,7 +579,7 @@ def __init__(
579579
reuse_existing: bool = False,
580580
venv_backend: str = "virtualenv",
581581
venv_params: Sequence[str] = (),
582-
):
582+
) -> None:
583583
# "pypy-" -> "pypy"
584584
if interpreter and interpreter.startswith("pypy-"):
585585
interpreter = interpreter[:4] + interpreter[5:]

pyproject.toml

Lines changed: 22 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -87,55 +87,35 @@ docs = [
8787
metadata.allow-ambiguous-features = true # disable normalization (tox-to-nox) for back-compat
8888

8989
[tool.ruff]
90-
lint.extend-select = [
91-
# "ANN", # flake8-annotations
92-
"ARG", # flake8-unused-arguments
93-
"B", # flake8-bugbear
94-
"C4", # flake8-comprehensions
95-
"EM", # flake8-errmsg
96-
"EXE", # flake8-executable
97-
"FA", # flake8-future-annotations
98-
"FBT", # flake8-boolean-trap
99-
"FLY", # flynt
100-
"FURB", # refurb
101-
"G", # flake8-logging-format
102-
"I", # isort
103-
"ICN", # flake8-import-conventions
104-
"ISC", # flake8-implicit-str-concat
105-
"LOG", # flake8-logging
106-
"N", # flake8-naming
107-
"PERF", # perflint
108-
"PGH", # pygrep-hooks
109-
"PIE", # flake8-pie
110-
"PL", # pylint
111-
"PT", # flake8-pytest-style
112-
# "PTH", # flake8-use-pathlib
113-
"PYI", # flake8-pyi
114-
"Q", # flake8-quotes
115-
"RET", # flake8-return
116-
"RUF", # Ruff-specific
117-
"S", # eval -> literal_eval
118-
"SIM", # flake8-simplify
119-
"SLOT", # flake8-slots
120-
"T10", # flake8-debugger
121-
"TC", # flake8-type-checking
122-
"TRY", # tryceratops
123-
"UP", # pyupgrade
124-
"YTT", # flake8-2020
125-
]
90+
show-fixes = true
91+
lint.select = [ "ALL" ]
12692
lint.ignore = [
93+
"A002", # Function arguments can shadow builtins
94+
"ANN401", # Any allowed (TODO: some maybe can be removed)
95+
"C9", # Complexity
96+
"COM812", # Trailing commas inform the formatter
97+
"D", # Too many docs
98+
"E501", # Line too long
99+
"FIX", # We have todos
127100
"N802", # Function name should be lowercase
128101
"N818", # Error suffix for errors
129102
"PLR09", # Too many X
130103
"PLR2004", # Magic value used in comparison
131-
"S101", # Use of assert detected
104+
"PTH", # Path usage (TODO)
105+
"RSE102", # Parens on exception
106+
"S101", # Assert is used by mypy and pytest
132107
"S603", # subprocess call - check for execution of untrusted input
133-
]
134-
lint.per-file-ignores."tests/*.py" = [
135-
"FBT001", # Boolean args okay for fixtures
136-
]
137-
lint.per-file-ignores."tests/resources/**.py" = [ "ARG001", "TRY002" ]
108+
"SLF001", # Private member access
109+
"T20", # We use print
110+
"TD", # TODO format
111+
]
112+
lint.per-file-ignores.".github/**.py" = [ "INP001" ]
113+
lint.per-file-ignores."docs/**.py" = [ "ERA001", "INP001" ]
114+
lint.per-file-ignores."tests/*.py" = [ "FBT001", "INP001" ]
115+
lint.per-file-ignores."tests/resources/**.py" = [ "ANN", "ARG001", "DTZ001", "ERA001", "TRY002" ]
138116
lint.typing-modules = [ "nox._typing" ]
117+
lint.flake8-annotations.allow-star-arg-any = true
118+
lint.flake8-builtins.ignorelist = [ "copyright" ]
139119
lint.flake8-unused-arguments.ignore-variadic-names = true
140120

141121
[tool.pyproject-fmt]

0 commit comments

Comments
 (0)