1010
1111from __future__ import annotations
1212
13- import os
1413import pathlib
15- from itertools import chain
16- from typing import TYPE_CHECKING , Callable , Dict , List , Tuple , TypeVar
14+ from typing import Any , Dict , List , Sequence , Tuple
1715
1816import nox
1917
20- if TYPE_CHECKING :
21- from typing_extensions import Concatenate , ParamSpec
22-
23- P = ParamSpec ("P" )
24- T = TypeVar ("T" )
25-
26- NoxSessionFunc = Callable [Concatenate [nox .Session , P ], T ]
27-
2818nox .needs_version = ">=2025.5.1"
2919
30- # see https://pdm-project.org/latest/usage/advanced/#use-nox-as-the-runner
31- os .environ .update (
32- {
33- "PDM_IGNORE_SAVED_PYTHON" : "1" ,
34- },
35- )
3620
3721nox .options .error_on_external_run = True
38- nox .options .reuse_existing_virtualenvs = True
22+ nox .options .reuse_venv = "yes"
3923nox .options .sessions = [
4024 "lint" ,
4125 "check-manifest" ,
5034reset_coverage = True
5135
5236
37+ def install_deps (
38+ session : nox .Session ,
39+ * ,
40+ extras : Sequence [str ] = (),
41+ groups : Sequence [str ] = (),
42+ project : bool = True ,
43+ ) -> None :
44+ """Helper to install dependencies from a group."""
45+ if not project and extras :
46+ raise TypeError ("Cannot install extras without also installing the project" )
47+
48+ command : List [str ] = [
49+ "pdm" ,
50+ "sync" ,
51+ "--fail-fast" ,
52+ "--clean-unselected" ,
53+ ]
54+
55+ # see https://pdm-project.org/latest/usage/advanced/#use-nox-as-the-runner
56+ env : Dict [str , Any ] = {
57+ "PDM_IGNORE_SAVED_PYTHON" : "1" ,
58+ }
59+
60+ command .extend ([f"-G={ g } " for g in (* extras , * groups )])
61+
62+ if not groups :
63+ # if no dev groups requested, make sure we don't install any
64+ command .append ("--prod" )
65+
66+ if not project :
67+ command .append ("--no-self" )
68+
69+ session .run_install (
70+ * command ,
71+ env = env ,
72+ external = True ,
73+ )
74+
75+
5376@nox .session (python = "3.8" )
5477def docs (session : nox .Session ) -> None :
5578 """Build and generate the documentation.
5679
5780 If running locally, will build automatic reloading docs.
5881 If running in CI, will build a production version of the documentation.
5982 """
60- session . run_always ( "pdm" , "install" , "--prod" , "-G" , " docs", external = True )
83+ install_deps ( session , extras = [ " docs"] )
6184 with session .chdir ("docs" ):
6285 args = ["-b" , "html" , "-n" , "." , "_build/html" , * session .posargs ]
6386 if session .interactive :
@@ -86,23 +109,22 @@ def docs(session: nox.Session) -> None:
86109@nox .session
87110def lint (session : nox .Session ) -> None :
88111 """Check all files for linting errors"""
89- session . run_always ( "pdm" , "install" , "-G" , " tools", external = True )
112+ install_deps ( session , project = False , groups = [ " tools"] )
90113
91114 session .run ("pre-commit" , "run" , "--all-files" , * session .posargs )
92115
93116
94117@nox .session (name = "check-manifest" )
95118def check_manifest (session : nox .Session ) -> None :
96119 """Run check-manifest."""
97- # --no-self is provided here because check-manifest builds disnake. There's no reason to build twice, so we don't.
98- session .run_always ("pdm" , "install" , "--no-self" , "-dG" , "tools" , external = True )
120+ install_deps (session , project = False , groups = ["tools" ])
99121 session .run ("check-manifest" , "-v" )
100122
101123
102- @nox .session ()
124+ @nox .session
103125def slotscheck (session : nox .Session ) -> None :
104126 """Run slotscheck."""
105- session . run_always ( "pdm" , "install" , "-dG" , " tools", external = True )
127+ install_deps ( session , project = True , groups = [ " tools"] )
106128 session .run ("python" , "-m" , "slotscheck" , "--verbose" , "-m" , "disnake" )
107129
108130
@@ -113,7 +135,7 @@ def autotyping(session: nox.Session) -> None:
113135 Because of the nature of changes that autotyping makes, and the goal design of examples,
114136 this runs on each folder in the repository with specific settings.
115137 """
116- session . run_always ( "pdm" , "install" , "-dG" , " codemod", external = True )
138+ install_deps ( session , project = True , groups = [ " codemod"] )
117139
118140 base_command = ["python" , "-m" , "libcst.tool" , "codemod" , "autotyping.AutotypeCommand" ]
119141 if not session .interactive :
@@ -172,7 +194,7 @@ def autotyping(session: nox.Session) -> None:
172194@nox .session (name = "codemod" , python = "3.8" )
173195def codemod (session : nox .Session ) -> None :
174196 """Run libcst codemods."""
175- session . run_always ( "pdm" , "install" , "-dG" , " codemod", external = True )
197+ install_deps ( session , project = True , groups = [ " codemod"] )
176198
177199 base_command = ["python" , "-m" , "libcst.tool" ]
178200 base_command_codemod = [* base_command , "codemod" ]
@@ -196,10 +218,21 @@ def codemod(session: nox.Session) -> None:
196218 session .notify ("autotyping" , posargs = [])
197219
198220
199- @nox .session ()
221+ @nox .session
200222def pyright (session : nox .Session ) -> None :
201223 """Run pyright."""
202- session .run_always ("pdm" , "install" , "-d" , "-Gspeed" , "-Gdocs" , "-Gvoice" , external = True )
224+ install_deps (
225+ session ,
226+ project = True ,
227+ extras = ["speed" , "voice" ],
228+ groups = [
229+ "test" , # tests/
230+ "nox" , # noxfile.py
231+ "docs" , # docs/
232+ "codemod" , # scripts/
233+ "typing" , # pyright
234+ ],
235+ )
203236 env = {
204237 "PYRIGHT_PYTHON_IGNORE_WARNINGS" : "1" ,
205238 }
@@ -221,10 +254,7 @@ def pyright(session: nox.Session) -> None:
221254)
222255def test (session : nox .Session , extras : List [str ]) -> None :
223256 """Run tests."""
224- # shell splitting is not done by nox
225- extras = list (chain (* (["-G" , extra ] for extra in extras )))
226-
227- session .run_always ("pdm" , "install" , "-dG" , "test" , "-dG" , "typing" , * extras , external = True )
257+ install_deps (session , project = True , extras = extras , groups = ["test" ])
228258
229259 pytest_args = ["--cov" , "--cov-context=test" ]
230260 global reset_coverage # noqa: PLW0603
@@ -243,10 +273,11 @@ def test(session: nox.Session, extras: List[str]) -> None:
243273 )
244274
245275
246- @nox .session ()
276+ @nox .session
247277def coverage (session : nox .Session ) -> None :
248278 """Display coverage information from the tests."""
249- session .run_always ("pdm" , "install" , "-dG" , "test" , external = True )
279+ install_deps (session , project = False , groups = ["test" ])
280+
250281 posargs = session .posargs
251282 # special-case serve
252283 if "serve" in posargs :
0 commit comments