Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use Public API #4

Merged
merged 6 commits into from
Jan 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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