Skip to content

Commit

Permalink
Merge pull request #4 from masenf/public-api
Browse files Browse the repository at this point in the history
Use Public API
  • Loading branch information
masenf committed Jan 15, 2023
2 parents ee40a14 + 2682f02 commit 683e4d9
Show file tree
Hide file tree
Showing 6 changed files with 202 additions and 104 deletions.
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2022 Masen Furer
Copyright (c) 2023 Masen Furer

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
47 changes: 41 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,30 +9,53 @@ Reuse virtualenvs with multiple `tox` test environments.

If two environments have compatible specifications (basically, same `deps`) and
use the same `env_dir`, installing this plugin and setting
`ignore_env_name_mismatch = true` will allow tox to use the same underlying
`runner = ignore_env_name_mismatch` will allow tox to use the same underlying
virtualenv for each test environment.

## Usage

1. Install `tox-ignore-env-name-mismatch` in the same environment as `tox`.
2. Set `ignore_env_name_mismatch = true` to opt-out of recreating the virtualenv when the cached name differs from the current env name.
* To always use this plugin, specify `requires = tox-ignore-env-name-mismatch` in the `[tox]` section
of `tox.ini`
2. Set `runner = ignore_env_name_mismatch` in a testenv to opt-out of recreating the virtualenv when the env name changes.

### To always use this plugin:

#### Install/provision

* Specify `requires = tox-ignore-env-name-mismatch~=0.2.0` in the `[tox]`
section of `tox.ini`

This will cause `tox` to provision a new virtualenv for `tox` itself and other
dependencies named in the
[`requires`](https://tox.wiki/en/latest/config.html#requires) key if the current
environment does not meet the specification.

Pinning the plugin to a minor version is _highly recommended_ to avoid breaking
changes.

#### Vendor

* copy `src/tox_ignore_env_name_mismatch.py` to the root of you project
directory as `toxfile.py`

This uses the tox4's new ["inline
plugin"](https://tox.wiki/en/latest/plugins.html#module-tox.plugin) approach
instead of relying on the provisioning system (which [can be disabled via
CLI](https://tox.wiki/en/latest/cli_interface.html#tox---no-provision)).

## Example

```
[tox]
envlist = py39,py310,py311,lint,format,types
requires = tox-ignore-env-name-mismatch
requires = tox-ignore-env-name-mismatch~=0.2.0
[testenv]
deps = pytest
commands = pytest {posargs}
[testenv:{lint,format,types}]
env_dir = {toxworkdir}{/}static
ignore_env_name_mismatch = true
runner = ignore_env_name_mismatch
deps =
black
flake8
Expand Down Expand Up @@ -74,3 +97,15 @@ _fine_, that's what plugins are for).
* [tox multiple tests, re-using tox environment](https://stackoverflow.com/questions/57222212/tox-multiple-tests-re-using-tox-environment) [2019, StackOverflow]
* [[tox-dev/tox#425] Ability to share tox environments within a project](https://github.com/tox-dev/tox/issues/425) [2016, Github]
* [Tox tricks and patterns#Environment reuse](https://blog.ionelmc.ro/2015/04/14/tox-tricks-and-patterns/#environment-reuse) [2015, Blog]

## Changelog

### v0.2 - 2023-01-15

**[BREAKING]** [#3](https://github.com/masenf/tox-ignore-env-name-mismatch/issues/3) Rewrite plugin to use Public API

To upgrade to v0.2, change `ignore_env_name_mismatch = true` to `runner = ignore_env_name_mismatch`.

### v0.1 - 2023-01-14

Initial Release
2 changes: 1 addition & 1 deletion examples/tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ commands = pytest {posargs}

[testenv:{lint,format,types}]
env_dir = {toxworkdir}{/}static
ignore_env_name_mismatch = true
runner = ignore_env_name_mismatch
deps =
black
flake8
Expand Down
84 changes: 58 additions & 26 deletions src/tox_ignore_env_name_mismatch.py
Original file line number Diff line number Diff line change
@@ -1,45 +1,77 @@
from tox.config.sets import EnvConfigSet
"""
https://github.com/masenf/tox-ignore-env-name-mismatch
MIT License
Copyright (c) 2023 Masen Furer
"""
from contextlib import contextmanager
from typing import Any, Iterator, Optional, Sequence, Tuple

from tox.plugin import impl
from tox.tox_env.api import ToxEnv
from tox.tox_env.info import Info
from tox.tox_env.python.virtual_env.runner import VirtualEnvRunner
from tox.tox_env.register import ToxEnvRegister


IGNORE_ENV_NAME_MISMATCH_KEY = "ignore_env_name_mismatch"
IGNORE_ENV_NAME_MISMATCH_KEY_ALT = "ignore_envname_mismatch"
class FilteredInfo(Info):
"""Subclass of Info that optionally filters specific keys during compare()."""

def __init__(
self,
*args: Any,
filter_keys: Optional[Sequence[str]] = None,
filter_section: Optional[str] = None,
**kwargs: Any,
):
"""
:param filter_keys: key names to pop from value
:param filter_section: if specified, only pop filter_keys when the compared section matches
All other args and kwargs are passed to super().__init__
"""
self.filter_keys = filter_keys
self.filter_section = filter_section
super().__init__(*args, **kwargs)

@contextmanager
def compare(
self,
value: Any,
section: str,
sub_section: Optional[str] = None,
) -> Iterator[Tuple[bool, Optional[Any]]]:
"""Perform comparison and update cached info after filtering `value`."""
if self.filter_section is None or section == self.filter_section:
try:
value = value.copy()
except AttributeError: # pragma: no cover
pass
else:
for fkey in self.filter_keys or []:
value.pop(fkey, None)
with super().compare(value, section, sub_section) as rv:
yield rv

class ReusableVirtualEnvRunner(VirtualEnvRunner):
"""EnvRunner that optionall ignores name mismatch."""

class IgnoreEnvNameMismatchVirtualEnvRunner(VirtualEnvRunner):
"""EnvRunner that does NOT save the env name as part of the cached info."""

@staticmethod
def id() -> str:
return "virtualenv-reusable"
return "ignore_env_name_mismatch"

@property
def cache(self) -> Info:
"""Ignore changes in the "name" if env has `ignore_env_name_mismatch = true`."""
info = super().cache
toxenv_info = info._content.get(ToxEnv.__name__, {})
if self.conf[IGNORE_ENV_NAME_MISMATCH_KEY] and toxenv_info:
toxenv_info["name"] = self.conf.name
return info


@impl
def tox_add_env_config(env_conf: EnvConfigSet) -> None:
"""tox4 entry point: add ignore_env_name_config env config."""
env_conf.add_config(
keys=[IGNORE_ENV_NAME_MISMATCH_KEY, IGNORE_ENV_NAME_MISMATCH_KEY_ALT],
default=False,
of_type=bool,
desc="Do not recreate venv when the testenv name differs.",
)
"""Return a modified Info class that does NOT pass "name" key to `Info.compare`."""
return FilteredInfo(
self.env_dir,
filter_keys=["name"],
filter_section=ToxEnv.__name__,
)


@impl
def tox_register_tox_env(register: ToxEnvRegister) -> None:
"""tox4 entry point: set ReuseVirtualEnvRunner as default_env_runner."""
register.add_run_env(ReusableVirtualEnvRunner)
register.default_env_runner = ReusableVirtualEnvRunner.id()
"""tox4 entry point: add IgnoreEnvNameMismatchVirtualEnvRunner to registry."""
register.add_run_env(IgnoreEnvNameMismatchVirtualEnvRunner)
19 changes: 5 additions & 14 deletions tests/integration/test_end_to_end.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@

import pytest

import tox_ignore_env_name_mismatch


pytest_plugins = ["pytester"]

Expand All @@ -18,20 +16,12 @@
@pytest.mark.parametrize(
"ignore_env_name_mismatch_spec, exp_reuse",
[
[f"{tox_ignore_env_name_mismatch.IGNORE_ENV_NAME_MISMATCH_KEY} = true", True],
[
f"{tox_ignore_env_name_mismatch.IGNORE_ENV_NAME_MISMATCH_KEY_ALT} = true",
True,
],
[f"{tox_ignore_env_name_mismatch.IGNORE_ENV_NAME_MISMATCH_KEY} = false", False],
[
f"{tox_ignore_env_name_mismatch.IGNORE_ENV_NAME_MISMATCH_KEY_ALT} = false",
False,
],
["runner = ignore_env_name_mismatch", True],
["", False],
],
)
def test_testenv_reuse(pytester, monkeypatch, ignore_env_name_mismatch_spec, exp_reuse):
"""Environment should not be recreated if ignore_env_name_mismatch is true."""
"""Environment should not be recreated if runner is ignore_env_name_mismatch."""
envlist = ["foo", "bar", "baz"]
monkeypatch.delenv(
"TOX_WORK_DIR", raising=False
Expand Down Expand Up @@ -69,9 +59,10 @@ def test_testenv_no_reuse(pytester, monkeypatch):
"""
[tox]
envlist = foo, bar
[testenv]
env_dir = {toxworkdir}{/}shared
ignore_env_name_mismatch = true
runner = ignore_env_name_mismatch
deps = wheel
commands = %s
Expand Down
Loading

0 comments on commit 683e4d9

Please sign in to comment.