1010import nox
1111
1212package = "datadoc_editor"
13- python_versions = ["3.12" , "3.13" ]
13+ python_versions = ["3.12" , "3.13" , "3.14" ]
1414nox .needs_version = ">= 2021.6.6"
1515nox .options .default_venv_backend = "uv"
1616nox .options .sessions = (
2323)
2424
2525
26- def activate_virtualenv_in_precommit_hooks (session : nox .Session ) -> None :
27- """Activate virtualenv in hooks installed by pre-commit.
28-
29- This function patches git hooks installed by pre-commit to activate the
30- session's virtual environment. This allows pre-commit to locate hooks in
31- that environment when invoked from git.
32-
33- Args:
34- session: The Session object.
35- """
36- assert session .bin is not None # nosec
37-
38- # Only patch hooks containing a reference to this session's bindir. Support
39- # quoting rules for Python and bash, but strip the outermost quotes so we
40- # can detect paths within the bindir, like <bindir>/python.
41- bindirs = [
42- bindir [1 :- 1 ] if bindir [0 ] in "'\" " else bindir
43- for bindir in (repr (session .bin ), shlex .quote (session .bin ))
44- ]
45-
46- virtualenv = session .env .get ("VIRTUAL_ENV" )
47- if virtualenv is None :
48- return
49-
50- headers = {
51- # pre-commit < 2.16.0
52- "python" : f"""\
53- import os
54- os.environ["VIRTUAL_ENV"] = { virtualenv !r}
55- os.environ["PATH"] = os.pathsep.join((
56- { session .bin !r} ,
57- os.environ.get("PATH", ""),
58- ))
59- """ ,
60- # pre-commit >= 2.16.0
61- "bash" : f"""\
62- VIRTUAL_ENV={ shlex .quote (virtualenv )}
63- PATH={ shlex .quote (session .bin )} "{ os .pathsep } $PATH"
64- """ ,
65- # pre-commit >= 2.17.0 on Windows forces sh shebang
66- "/bin/sh" : f"""\
67- VIRTUAL_ENV={ shlex .quote (virtualenv )}
68- PATH={ shlex .quote (session .bin )} "{ os .pathsep } $PATH"
69- """ ,
70- }
71-
72- hookdir = Path (".git" ) / "hooks"
73- if not hookdir .is_dir ():
74- return
75-
76- for hook in hookdir .iterdir ():
77- if hook .name .endswith (".sample" ) or not hook .is_file ():
78- continue
79-
80- if not hook .read_bytes ().startswith (b"#!" ):
81- continue
82-
83- text = hook .read_text ()
84-
85- if not is_bindir_in_text (bindirs , text ):
86- continue
87-
88- lines = text .splitlines ()
89- hook .write_text (insert_header_in_hook (headers , lines ))
90-
91-
92- def is_bindir_in_text (bindirs : list [str ], text : str ) -> bool :
93- """Helper function to check if bindir is in text."""
94- return any (
95- Path ("A" ) == Path ("a" ) and bindir .lower () in text .lower () or bindir in text
96- for bindir in bindirs
26+ def install_with_uv (
27+ session : nox .Session ,
28+ * ,
29+ groups : list [str ] | None = None ,
30+ only_groups : list [str ] | None = None ,
31+ all_extras : bool = False ,
32+ locked : bool = True ,
33+ ) -> None :
34+ """Install packages using uv, pinned to uv.lock."""
35+ cmd = ["uv" , "sync" , "--no-default-groups" ]
36+ if locked :
37+ cmd .append ("--locked" )
38+ if groups :
39+ for group in groups :
40+ cmd .extend (["--group" , group ])
41+ if only_groups :
42+ for group in only_groups or []:
43+ cmd .extend (["--only-group" , group ])
44+ if all_extras :
45+ cmd .append ("--all-extras" )
46+ cmd .append (
47+ f"--python={ session .virtualenv .location } "
48+ ) # Target the nox venv's Python interpreter
49+ session .run_install (
50+ * cmd , env = {"UV_PROJECT_ENVIRONMENT" : session .virtualenv .location }
9751 )
9852
9953
100- def insert_header_in_hook (header : dict [str , str ], lines : list [str ]) -> str :
101- """Helper function to insert headers in hook's text."""
102- for executable , header_text in header .items ():
103- if executable in lines [0 ].lower ():
104- lines .insert (1 , dedent (header_text ))
105- return "\n " .join (lines )
106- return "\n " .join (lines )
107-
108-
10954@nox .session (name = "pre-commit" , python = python_versions [- 1 ])
11055def precommit (session : nox .Session ) -> None :
11156 """Lint using pre-commit."""
57+ install_with_uv (session , only_groups = ["dev" ])
11258 args = session .posargs or [
11359 "run" ,
11460 "--all-files" ,
11561 "--hook-stage=manual" ,
11662 "--show-diff-on-failure" ,
11763 ]
118- session .install (
119- "pre-commit" ,
120- "pre-commit-hooks" ,
121- )
12264 session .run ("pre-commit" , * args )
123- if args and args [0 ] == "install" :
124- activate_virtualenv_in_precommit_hooks (session )
12565
12666
127- @nox .session (python = python_versions [ - 2 :] )
67+ @nox .session (python = python_versions )
12868def mypy (session : nox .Session ) -> None :
12969 """Type-check using mypy."""
70+ install_with_uv (session , groups = ["type_check" , "test" ])
13071 args = session .posargs or ["src" , "tests" ]
131- session .install ("." )
132- session .install (
133- "mypy" ,
134- "pytest" ,
135- "types-setuptools" ,
136- "pandas-stubs" ,
137- "pyarrow-stubs" ,
138- "types-Pygments" ,
139- "types-colorama" ,
140- "types-beautifulsoup4" ,
141- "faker" ,
142- "tomli"
143- )
14472 session .run ("mypy" , * args )
14573 if not session .posargs :
14674 session .run ("mypy" , f"--python-executable={ sys .executable } " , "noxfile.py" )
14775
14876
149- @nox .session (python = python_versions [ - 2 :] )
77+ @nox .session (python = python_versions )
15078def tests (session : nox .Session ) -> None :
15179 """Run the test suite."""
152- session .install ("." )
153- session .install (
154- "coverage[toml]" , "pytest" , "pygments" , "pytest-mock" , "requests-mock" , "faker" , "tomli"
155- )
80+ install_with_uv (session , groups = ["test" ])
15681 try :
15782 session .run (
15883 "coverage" ,
@@ -172,10 +97,8 @@ def tests(session: nox.Session) -> None:
17297@nox .session (python = python_versions [- 1 ])
17398def coverage (session : nox .Session ) -> None :
17499 """Produce the coverage report."""
100+ install_with_uv (session , only_groups = ["test" ])
175101 args = session .posargs or ["report" , "--skip-empty" ]
176-
177- session .install ("coverage[toml]" )
178-
179102 if not session .posargs and any (Path ().glob (".coverage.*" )):
180103 session .run ("coverage" , "combine" )
181104
@@ -185,40 +108,30 @@ def coverage(session: nox.Session) -> None:
185108@nox .session (python = python_versions [- 1 ])
186109def typeguard (session : nox .Session ) -> None :
187110 """Runtime type checking using Typeguard."""
188- session .install ("." )
189- session .install (
190- "pytest" , "typeguard" , "pygments" , "pytest_mock" , "requests_mock" , "faker" , "tomli"
191- )
111+ install_with_uv (session , groups = ["test" ])
192112 session .run ("pytest" , f"--typeguard-packages={ package } " , * session .posargs )
193113
194114
195115@nox .session (python = python_versions [- 1 ])
196116def xdoctest (session : nox .Session ) -> None :
197117 """Run examples with xdoctest."""
118+ install_with_uv (session , groups = ["test" ])
198119 if session .posargs :
199120 args = [package , * session .posargs ]
200121 else :
201122 args = [f"--modname={ package } " , "--command=all" ]
202123 if "FORCE_COLOR" in os .environ :
203124 args .append ("--colored=1" )
204-
205- session .install ("." )
206- session .install ("xdoctest[colors]" )
207125 session .run ("python" , "-m" , "xdoctest" , * args )
208126
209127
210128@nox .session (name = "docs-build" , python = python_versions [- 1 ])
211129def docs_build (session : nox .Session ) -> None :
212130 """Build the documentation."""
131+ install_with_uv (session , groups = ["docs" ])
213132 args = session .posargs or ["docs" , "docs/_build" ]
214133 if not session .posargs and "FORCE_COLOR" in os .environ :
215134 args .insert (0 , "--color" )
216-
217- session .install ("." )
218- session .install (
219- "sphinx" , "sphinx-autodoc-typehints" , "sphinx-click" , "furo" , "myst-parser"
220- )
221-
222135 build_dir = Path ("docs" , "_build" )
223136 if build_dir .exists ():
224137 shutil .rmtree (build_dir )
@@ -229,17 +142,8 @@ def docs_build(session: nox.Session) -> None:
229142@nox .session (python = python_versions [- 1 ])
230143def docs (session : nox .Session ) -> None :
231144 """Build and serve the documentation with live reloading on file changes."""
145+ install_with_uv (session , groups = ["docs" ])
232146 args = session .posargs or ["--open-browser" , "docs" , "docs/_build" ]
233- session .install ("." )
234- session .install (
235- "sphinx" ,
236- "sphinx-autobuild" ,
237- "sphinx-autodoc-typehints" ,
238- "sphinx-click" ,
239- "furo" ,
240- "myst-parser" ,
241- )
242-
243147 build_dir = Path ("docs" , "_build" )
244148 if build_dir .exists ():
245149 shutil .rmtree (build_dir )
0 commit comments