|
1 | 1 | """Nox sessions.""" |
| 2 | +import contextlib |
| 3 | +import shutil |
2 | 4 | import tempfile |
3 | | -from typing import Any |
| 5 | +from pathlib import Path |
| 6 | +from typing import cast |
| 7 | +from typing import Iterator |
4 | 8 |
|
5 | 9 | import nox |
6 | 10 | from nox.sessions import Session |
7 | 11 |
|
| 12 | +package = "django_sorting_bootstrap" |
| 13 | +python_versions = ["3.8", "3.7"] |
8 | 14 | nox.options.sessions = "lint", "safety", "mypy" |
9 | 15 | locations = "src", "noxfile.py" |
10 | 16 |
|
11 | 17 |
|
12 | | -def install_with_constraints(session: Session, *args: str, **kwargs: Any) -> None: |
13 | | - """Install packages constrained by Poetry's lock file. |
14 | | - This function is a wrapper for nox.sessions.Session.install. It |
15 | | - invokes pip to install packages inside of the session's virtualenv. |
16 | | - Additionally, pip is passed a constraints file generated from |
17 | | - Poetry's lock file, to ensure that the packages are pinned to the |
18 | | - versions specified in poetry.lock. This allows you to manage the |
19 | | - packages as Poetry development dependencies. |
20 | | - Arguments: |
| 18 | +class Poetry: |
| 19 | + """Helper class for invoking Poetry inside a Nox session. |
| 20 | +
|
| 21 | + Attributes: |
21 | 22 | session: The Session object. |
22 | | - args: Command-line arguments for pip. |
23 | | - kwargs: Additional keyword arguments for Session.install. |
24 | 23 | """ |
25 | | - with tempfile.NamedTemporaryFile() as requirements: |
26 | | - session.run( |
27 | | - "poetry", |
28 | | - "export", |
29 | | - "--dev", |
30 | | - "--format=requirements.txt", |
31 | | - f"--output={requirements.name}", |
32 | | - external=True, |
| 24 | + |
| 25 | + def __init__(self, session: Session) -> None: |
| 26 | + """Constructor.""" |
| 27 | + self.session = session |
| 28 | + |
| 29 | + @contextlib.contextmanager |
| 30 | + def export(self, *args: str) -> Iterator[Path]: |
| 31 | + """Export the lock file to requirements format. |
| 32 | +
|
| 33 | + Args: |
| 34 | + args: Command-line arguments for ``poetry export``. |
| 35 | +
|
| 36 | + Yields: |
| 37 | + The path to the requirements file. |
| 38 | + """ |
| 39 | + with tempfile.TemporaryDirectory() as directory: |
| 40 | + requirements = Path(directory) / "requirements.txt" |
| 41 | + self.session.run( |
| 42 | + "poetry", |
| 43 | + "export", |
| 44 | + *args, |
| 45 | + "--format=requirements.txt", |
| 46 | + f"--output={requirements}", |
| 47 | + external=True, |
| 48 | + ) |
| 49 | + yield requirements |
| 50 | + |
| 51 | + def version(self) -> str: |
| 52 | + """Retrieve the package version. |
| 53 | +
|
| 54 | + Returns: |
| 55 | + The package version. |
| 56 | + """ |
| 57 | + output = self.session.run( |
| 58 | + "poetry", "version", external=True, silent=True, stderr=None |
33 | 59 | ) |
34 | | - session.install(f"--constraint={requirements.name}", *args, **kwargs) |
| 60 | + return cast(str, output).split()[1] |
35 | 61 |
|
| 62 | + def build(self, *args: str) -> None: |
| 63 | + """Build the package. |
36 | 64 |
|
37 | | -@nox.session(python="3.8") |
38 | | -def black(session: Session) -> None: |
39 | | - """Run black code formatter.""" |
40 | | - args = session.posargs or locations |
41 | | - install_with_constraints(session, "black") |
42 | | - session.run("black", *args) |
| 65 | + Args: |
| 66 | + args: Command-line arguments for ``poetry build``. |
| 67 | + """ |
| 68 | + self.session.run("poetry", "build", *args, external=True) |
43 | 69 |
|
44 | 70 |
|
45 | | -@nox.session(python=["3.8", "3.7"]) |
46 | | -def lint(session: Session) -> None: |
47 | | - """Lint using flake8.""" |
48 | | - args = session.posargs or locations |
49 | | - install_with_constraints( |
50 | | - session, "flake8", "flake8-bandit", "flake8-black", "flake8-bugbear", |
| 71 | +def install_package(session: Session) -> None: |
| 72 | + """Build and install the package. |
| 73 | +
|
| 74 | + Build a wheel from the package, and install it into the virtual environment |
| 75 | + of the specified Nox session. |
| 76 | +
|
| 77 | + The package requirements are installed using the versions specified in |
| 78 | + Poetry's lock file. |
| 79 | +
|
| 80 | + Args: |
| 81 | + session: The Session object. |
| 82 | + """ |
| 83 | + poetry = Poetry(session) |
| 84 | + |
| 85 | + with poetry.export() as requirements: |
| 86 | + session.install(f"--requirement={requirements}") |
| 87 | + |
| 88 | + poetry.build("--format=wheel") |
| 89 | + |
| 90 | + version = poetry.version() |
| 91 | + session.install( |
| 92 | + "--no-deps", "--force-reinstall", f"dist/{package}-{version}-py3-none-any.whl" |
51 | 93 | ) |
52 | | - session.run("flake8", *args) |
| 94 | + |
| 95 | + |
| 96 | +def install(session: Session, *args: str) -> None: |
| 97 | + """Install development dependencies into the session's virtual environment. |
| 98 | +
|
| 99 | + This function is a wrapper for nox.sessions.Session.install. |
| 100 | +
|
| 101 | + The packages must be managed as development dependencies in Poetry. |
| 102 | +
|
| 103 | + Args: |
| 104 | + session: The Session object. |
| 105 | + args: Command-line arguments for ``pip install``. |
| 106 | + """ |
| 107 | + poetry = Poetry(session) |
| 108 | + with poetry.export("--dev") as requirements: |
| 109 | + session.install(f"--constraint={requirements}", *args) |
| 110 | + |
| 111 | + |
| 112 | +@nox.session(name="pre-commit", python="3.8") |
| 113 | +def precommit(session: Session) -> None: |
| 114 | + """Lint using pre-commit.""" |
| 115 | + args = session.posargs or ["run", "--all-files", "--show-diff-on-failure"] |
| 116 | + install(session, "pre-commit") |
| 117 | + session.run("pre-commit", *args) |
53 | 118 |
|
54 | 119 |
|
55 | 120 | @nox.session(python="3.8") |
56 | 121 | def safety(session: Session) -> None: |
57 | 122 | """Scan dependencies for insecure packages.""" |
58 | | - with tempfile.NamedTemporaryFile() as requirements: |
59 | | - session.run( |
60 | | - "poetry", |
61 | | - "export", |
62 | | - "--dev", |
63 | | - "--format=requirements.txt", |
64 | | - "--without-hashes", |
65 | | - f"--output={requirements.name}", |
66 | | - external=True, |
67 | | - ) |
68 | | - install_with_constraints(session, "safety") |
69 | | - session.run("safety", "check", f"--file={requirements.name}", "--full-report") |
| 123 | + poetry = Poetry(session) |
| 124 | + with poetry.export("--dev", "--without-hashes") as requirements: |
| 125 | + install(session, "safety") |
| 126 | + session.run("safety", "check", f"--file={requirements}", "--bare") |
70 | 127 |
|
71 | 128 |
|
72 | | -@nox.session(python=["3.8", "3.7"]) |
| 129 | +@nox.session(python=python_versions) |
73 | 130 | def mypy(session: Session) -> None: |
74 | 131 | """Type-check using mypy.""" |
75 | 132 | args = session.posargs or locations |
76 | | - install_with_constraints(session, "mypy") |
| 133 | + install_package(session) |
| 134 | + install(session, "mypy") |
77 | 135 | session.run("mypy", *args) |
0 commit comments