Skip to content

Commit

Permalink
feat: added venv_location option to specify virtualenv location
Browse files Browse the repository at this point in the history
  • Loading branch information
wpk committed Feb 27, 2024
1 parent ff259ce commit 8636ce3
Show file tree
Hide file tree
Showing 7 changed files with 80 additions and 31 deletions.
12 changes: 11 additions & 1 deletion docs/config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -175,14 +175,24 @@ You are not limited to virtualenv, there is a selection of backends you can choo
def tests(session):
pass
Finally, custom backend parameters are supported:
Custom backend parameters are supported:

.. code-block:: python
@nox.session(venv_params=['--no-download'])
def tests(session):
pass
Finally, you can specify the exact location of an environment:

.. code-block:: python
@nox.session(venv_location=".venv")
def dev(session):
pass
This places the environment in the folder ``./.venv`` instead of the default ``./.nox/dev``.


Passing arguments into sessions
-------------------------------
Expand Down
29 changes: 4 additions & 25 deletions docs/cookbook.rst
Original file line number Diff line number Diff line change
Expand Up @@ -47,33 +47,12 @@ Enter the ``dev`` nox session:
# so it's not run twice accidentally
nox.options.sessions = [...] # Sessions other than 'dev'
# this VENV_DIR constant specifies the name of the dir that the `dev`
# session will create, containing the virtualenv;
# the `resolve()` makes it portable
VENV_DIR = pathlib.Path('./.venv').resolve()
VENV_DIR = "./.venv"
@nox.session
@nox.session(venv_location=VENV_DIR)
def dev(session: nox.Session) -> None:
"""
Sets up a python development environment for the project.
This session will:
- Create a python virtualenv for the session
- Install the `virtualenv` cli tool into this environment
- Use `virtualenv` to create a global project virtual environment
- Invoke the python interpreter from the global project environment to install
the project and all it's development dependencies.
"""
session.install("virtualenv")
# the VENV_DIR constant is explained above
session.run("virtualenv", os.fsdecode(VENV_DIR), silent=True)
python = os.fsdecode(VENV_DIR.joinpath("bin/python"))
# Use the venv's interpreter to install the project along with
# all it's dev dependencies, this ensures it's installed in the right way
session.run(python, "-m", "pip", "install", "-e", ".[dev]", external=True)
"""Sets up a python development environment for the project."""
session.install("-e", ".[dev]")
With this, a user can simply run ``nox -s dev`` and have their entire environment set up automatically!

Expand Down
4 changes: 4 additions & 0 deletions nox/_decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ def __init__(
name: str | None = None,
venv_backend: Any = None,
venv_params: Any = None,
venv_location: str | None = None,
should_warn: Mapping[str, Any] | None = None,
tags: Sequence[str] | None = None,
) -> None:
Expand All @@ -76,6 +77,7 @@ def __init__(
self.name = name
self.venv_backend = venv_backend
self.venv_params = venv_params
self.venv_location = venv_location
self.should_warn = dict(should_warn or {})
self.tags = list(tags or [])

Expand All @@ -92,6 +94,7 @@ def copy(self, name: str | None = None) -> Func:
name,
self.venv_backend,
self.venv_params,
self.venv_location,
self.should_warn,
self.tags,
)
Expand Down Expand Up @@ -123,6 +126,7 @@ def __init__(self, func: Func, param_spec: Param) -> None:
None,
func.venv_backend,
func.venv_params,
func.venv_location,
func.should_warn,
func.tags,
)
Expand Down
12 changes: 11 additions & 1 deletion nox/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ def session_decorator(
name: str | None = ...,
venv_backend: Any | None = ...,
venv_params: Any | None = ...,
venv_location: str | None = ...,
tags: Sequence[str] | None = ...,
) -> Callable[[F], F]:
...
Expand All @@ -55,6 +56,7 @@ def session_decorator(
name: str | None = None,
venv_backend: Any | None = None,
venv_params: Any | None = None,
venv_location: str | None = None,
tags: Sequence[str] | None = None,
) -> F | Callable[[F], F]:
"""Designate the decorated function as a session."""
Expand All @@ -74,6 +76,7 @@ def session_decorator(
name=name,
venv_backend=venv_backend,
venv_params=venv_params,
venv_location=venv_location,
tags=tags,
)

Expand All @@ -88,7 +91,14 @@ def session_decorator(

final_name = name or func.__name__
fn = Func(
func, python, reuse_venv, final_name, venv_backend, venv_params, tags=tags
func,
python,
reuse_venv,
final_name,
venv_backend,
venv_params,
venv_location=venv_location,
tags=tags,
)
_REGISTRY[final_name] = fn
return fn
Expand Down
4 changes: 3 additions & 1 deletion nox/sessions.py
Original file line number Diff line number Diff line change
Expand Up @@ -754,7 +754,9 @@ def tags(self) -> list[str]:

@property
def envdir(self) -> str:
return _normalize_path(self.global_config.envdir, self.friendly_name)
return self.func.venv_location or _normalize_path(
self.global_config.envdir, self.friendly_name
)

def _create_venv(self) -> None:
backend = (
Expand Down
6 changes: 6 additions & 0 deletions noxfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,3 +192,9 @@ def github_actions_default_tests(session: nox.Session) -> None:
def github_actions_all_tests(session: nox.Session) -> None:
"""Check all versions installed by the nox GHA Action"""
_check_python_version(session)


@nox.session(venv_location=".venv")
def dev(session: nox.Session) -> None:
"""Create development environment `./.venv` using `nox -s dev`"""
session.install("-r", "requirements-dev.txt")
44 changes: 41 additions & 3 deletions tests/test_sessions.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,15 @@
from nox.logger import logger


@pytest.fixture()
def change_to_tmp_path(tmp_path):
old_cwd = Path.cwd()
os.chdir(tmp_path)
yield tmp_path
# Cleanup?
os.chdir(old_cwd)


def test__normalize_path():
envdir = "envdir"
normalize = nox.sessions._normalize_path
Expand Down Expand Up @@ -62,8 +71,8 @@ def test__normalize_path_give_up():


class TestSession:
def make_session_and_runner(self):
func = mock.Mock(spec=["python"], python="3.7")
def make_session_and_runner(self, venv_location=None):
func = mock.Mock(spec=["python"], python="3.7", venv_location=venv_location)
runner = nox.sessions.SessionRunner(
name="test",
signatures=["test"],
Expand Down Expand Up @@ -100,6 +109,23 @@ def test_create_tmp_twice(self):
assert session.env["TMPDIR"] == os.path.abspath(tmpdir)
assert tmpdir.startswith(root)

@pytest.mark.parametrize("pre_run", [0, 1])
def test_create_tmp_with_venv_location(self, change_to_tmp_path, pre_run):
session, runner = self.make_session_and_runner(venv_location="my-location")
# for testing, also set envdir
with tempfile.TemporaryDirectory() as root:
runner.global_config.envdir = root
for _ in range(pre_run):
session.create_tmp()

tmpdir = session.create_tmp()

assert tmpdir == os.path.join("my-location", "tmp")
assert os.path.abspath(tmpdir) == os.path.join(
change_to_tmp_path, "my-location", "tmp"
)
assert session.env["TMPDIR"] == os.path.abspath(tmpdir)

def test_properties(self):
session, runner = self.make_session_and_runner()
with tempfile.TemporaryDirectory() as root:
Expand Down Expand Up @@ -849,6 +875,7 @@ def make_runner(self):
func.python = None
func.venv_backend = None
func.reuse_venv = False
func.venv_location = None
runner = nox.sessions.SessionRunner(
name="test",
signatures=["test(1, 2)"],
Expand Down Expand Up @@ -930,6 +957,7 @@ def test__create_venv(self, create):
assert runner.venv.interpreter is None
assert runner.venv.reuse_existing is False

@pytest.mark.parametrize("venv_location", [None, "my-location"])
@pytest.mark.parametrize(
"create_method,venv_backend,expected_backend",
[
Expand All @@ -943,11 +971,14 @@ def test__create_venv(self, create):
("nox.virtualenv.CondaEnv.create", "conda", nox.virtualenv.CondaEnv),
],
)
def test__create_venv_options(self, create_method, venv_backend, expected_backend):
def test__create_venv_options(
self, venv_location, create_method, venv_backend, expected_backend
):
runner = self.make_runner()
runner.func.python = "coolpython"
runner.func.reuse_venv = True
runner.func.venv_backend = venv_backend
runner.func.venv_location = venv_location

with mock.patch(create_method, autospec=True) as create:
runner._create_venv()
Expand All @@ -957,6 +988,13 @@ def test__create_venv_options(self, create_method, venv_backend, expected_backen
assert runner.venv.interpreter == "coolpython"
assert runner.venv.reuse_existing is True

location_name = venv_location or nox.sessions._normalize_path(
runner.global_config.envdir, runner.friendly_name
)
assert runner.venv.location_name == location_name
assert runner.venv.location == os.path.abspath(location_name)
assert runner.envdir == location_name

def test__create_venv_unexpected_venv_backend(self):
runner = self.make_runner()
runner.func.venv_backend = "somenewenvtool"
Expand Down

0 comments on commit 8636ce3

Please sign in to comment.