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

nox setup to run tests on python/pydantic matrix simliar to github actions #820

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
56 changes: 56 additions & 0 deletions docs/contributing.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,62 @@ $ bash scripts/test.sh

This command generates a directory `./htmlcov/`, if you open the file `./htmlcov/index.html` in your browser, you can explore interactively the regions of code that are covered by the tests, and notice if there is any region missing.

### Local testing with Nox

You can use `nox` to run the tests under multiple python and pydantic versions similar to the way the
github actions run tests for pull-requests. To setup your local environment, install `nox` and `nox-poetry`.
Example install using `pipx`:

```console
$ pip install pipx
$ pipx install nox
$ pipx inject nox nox-poetry
```
If you are using `pyenv` to manage your pythons, enable all versions that you want to run the
tests on:

```console
$ pyenv global 3.7 3.8 3.9 3.10 3.11 3.12
```
Run the tests:
```console
$ nox -rs tests
```

This will find all python versions and run the test suite with each python versions / pydantic combination.

### Other nox Sessions

#### Lint

Run mypy and other linting tests for all python / pydantic version combinations using the `lint` session:

```console
$ nox -r --session=lint
```

#### The pre-commit session

`pre-commit` is a multi-language linter framework and a Git hook manager.

Run pre-commit from Nox using the pre-commit session:

```console
$ nox --session=pre-commit
```

This session always runs with the current stable release of Python.

Use the separator -- to pass additional options to pre-commit. For example,
the following command installs the pre-commit hooks, so they run automatically
on every commit you make:

```console
$ nox --session=pre-commit -- install
```



## Thanks

Thanks for contributing! ☕
116 changes: 116 additions & 0 deletions noxfile.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
from pathlib import Path
from textwrap import dedent

from nox import parametrize
from nox_poetry import Session, session

pydantic1 = ">=1.10.0,<2.0.0"
pydantic2 = ">=2.0.2,<3.0.0"
pydantic_versions = {
pydantic1: "pyd1",
pydantic2: "pyd2",
}


@session(python=["3.12", "3.11", "3.10", "3.9", "3.8", "3.7"])
@parametrize("pydantic", list(pydantic_versions.keys()), ids=pydantic_versions.values())
def tests(session: Session, pydantic: str) -> None:
"""Run pytest with coverage."""
session.run_always("poetry", "install", external=True)
session.install(".")
session.run_always(
"pip", "install", "--upgrade", f"pydantic{pydantic}", external=True
)
my_env = {
# Need to set PYTHONPATH because some tests depend on docs_src
"PYTHONPATH": session.invoked_from,
"COVERAGE_FILE": f"coverage/.coverage.py{session.python}-{pydantic_versions[pydantic]}",
"CONTEXT": f"py{session.python}-{pydantic_versions[pydantic]}",
}
session.log(f"Using env: {my_env}")
session.run("bash", "scripts/test.sh", env=my_env, external=True)
session.notify("coverage")


@session(python=["3.8"])
def coverage(session: Session) -> None:
"""Gather coverage from test runs."""
session.install("coverage[toml]", ".")
session.run("coverage", "combine", "coverage")
session.run("coverage", "report")
session.run("coverage", "html", "--show-contexts", "--title", "Coverage")


@session
@parametrize("python", ["3.12", "3.11", "3.10", "3.9", "3.8", "3.7"])
@parametrize("pydantic", list(pydantic_versions.keys()), ids=pydantic_versions.values())
def lint(session: Session, python: str, pydantic: str) -> None:
"""Run lint checks."""
if python != "3.7" and pydantic == pydantic2:
session.run_always("poetry", "install", external=True)
session.install(".")
session.run_always(
"pip", "install", "--upgrade", f"pydantic{pydantic}", external=True
)
session.run("bash", "scripts/lint.sh", external=True)


def activate_virtualenv_in_precommit_hooks(session: Session) -> None:
"""Activate virtualenv in hooks installed by pre-commit.

This function patches git hooks installed by pre-commit to activate the
session's virtual environment. This allows pre-commit to locate hooks in
that environment when invoked from git.

Args:
session: The Session object.
"""
if session.bin is None:
return

virtualenv = session.env.get("VIRTUAL_ENV")
if virtualenv is None:
return

hookdir = Path(".git") / "hooks"
if not hookdir.is_dir():
return

for hook in hookdir.iterdir():
if hook.name.endswith(".sample") or not hook.is_file():
continue

text = hook.read_text()
bindir = repr(session.bin)[1:-1] # strip quotes
if not (
Path("A") == Path("a") and bindir.lower() in text.lower() or bindir in text
):
continue

lines = text.splitlines()
if not (lines[0].startswith("#!") and "python" in lines[0].lower()):
continue

header = dedent(
f"""\
import os
os.environ["VIRTUAL_ENV"] = {virtualenv!r}
os.environ["PATH"] = os.pathsep.join((
{session.bin!r},
os.environ.get("PATH", ""),
))
"""
)

lines.insert(1, header)
hook.write_text("\n".join(lines))


@session(name="pre-commit", python="3.12")
def precommit(session: Session) -> None:
"""Lint using pre-commit."""
args = session.posargs or ["run", "--all-files", "--show-diff-on-failure"]
session.install("ruff", "pre-commit", "pre-commit-hooks")
session.run("pre-commit", *args)
if args and args[0] == "install":
activate_virtualenv_in_precommit_hooks(session)