"""Nox sessions.""" import os import shlex import shutil import sys from pathlib import Path from textwrap import dedent import nox import toml try: from nox_poetry import Session from nox_poetry import session except ImportError: message = f"""\ Nox failed to import the 'nox-poetry' package. Please install it using the following command: {sys.executable} -m pip install nox-poetry""" raise SystemExit(dedent(message)) from None package = "healthchecks_io" python_versions = ["3.10", "3.11", "3.9", "3.8", "3.7"] nox.needs_version = ">= 2021.6.6" nox.options.sessions = ( "pre-commit", "bandit", "safety", "mypy", "tests", # "typeguard", "xdoctest", "docs-build", ) mypy_type_packages = ("types-croniter", "types-pytz") test_requirements = ( "coverage[toml]", "pytest", "pygments", "respx", "pytest-asyncio", "pytest-lazy-fixture", ) mypy_type_packages = () pyproject = toml.load("pyproject.toml") test_requirements = pyproject["tool"]["poetry"]["group"]["dev"]["dependencies"].keys() 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. """ assert session.bin is not None # noqa: S101 # Only patch hooks containing a reference to this session's bindir. Support # quoting rules for Python and bash, but strip the outermost quotes so we # can detect paths within the bindir, like /python. bindirs = [ bindir[1:-1] if bindir[0] in "'\"" else bindir for bindir in (repr(session.bin), shlex.quote(session.bin)) ] virtualenv = session.env.get("VIRTUAL_ENV") if virtualenv is None: return headers = { # pre-commit < 2.16.0 "python": f"""\ import os os.environ["VIRTUAL_ENV"] = {virtualenv!r} os.environ["PATH"] = os.pathsep.join(( {session.bin!r}, os.environ.get("PATH", ""), )) """, # pre-commit >= 2.16.0 "bash": f"""\ VIRTUAL_ENV={shlex.quote(virtualenv)} PATH={shlex.quote(session.bin)}"{os.pathsep}$PATH" """, } 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 if not hook.read_bytes().startswith(b"#!"): continue text = hook.read_text() if not any(Path("A") == Path("a") and bindir.lower() in text.lower() or bindir in text for bindir in bindirs): continue lines = text.splitlines() for executable, header in headers.items(): if executable in lines[0].lower(): lines.insert(1, dedent(header)) hook.write_text("\n".join(lines)) break @session(name="pre-commit", python=python_versions[0]) def precommit(session: Session) -> None: """Lint using pre-commit.""" args = session.posargs or ["run", "--all-files", "--show-diff-on-failure"] session.install( "black", "darglint", "flake8", "flake8-bandit", "flake8-bugbear", "flake8-docstrings", "flake8-rst-docstrings", "pep8-naming", "pre-commit", "pre-commit-hooks", "pyupgrade", "reorder-python-imports", ) session.run("pre-commit", *args) if args and args[0] == "install": activate_virtualenv_in_precommit_hooks(session) @session(python=python_versions[0]) def safety(session: Session) -> None: """Scan dependencies for insecure packages.""" requirements = session.poetry.export_requirements() session.install("safety") # ignore https://github.com/pytest-dev/py/issues/287 # its an irresposnbily filed CVE causing nose session.run("safety", "check", "--full-report", f"--file={requirements}", "--ignore=51457") @session(python=python_versions) def mypy(session: Session) -> None: """Type-check using mypy.""" args = session.posargs or ["src"] session.install(".") session.install("mypy", "pytest") session.install(*mypy_type_packages) session.run("mypy", *args) @session(python=python_versions[0]) def bandit(session: Session) -> None: """Run bandit security tests""" args = session.posargs or ["-r", "./src"] session.run("bandit", *args) @session(python=python_versions) def tests(session: Session) -> None: """Run the test suite.""" session.install(".") session.install(*test_requirements) session.run("poetry", "run", "pytest", *session.posargs) @session(python=python_versions) def xdoctest(session: Session) -> None: """Run examples with xdoctest.""" if session.posargs: args = [package, *session.posargs] else: args = [f"--modname={package}", "--command=all"] if "FORCE_COLOR" in os.environ: args.append("--colored=1") session.install(".") session.install("xdoctest[colors]") session.run("python", "-m", "xdoctest", *args) @session(name="docs-build", python=python_versions[0]) def docs_build(session: Session) -> None: """Build the documentation.""" args = session.posargs or ["docs", "docs/_build"] if not session.posargs and "FORCE_COLOR" in os.environ: args.insert(0, "--color") session.install(".") session.install("sphinx", "sphinx-click", "furo") build_dir = Path("docs", "_build") if build_dir.exists(): shutil.rmtree(build_dir) session.run("sphinx-build", *args) @session(python=python_versions[0]) def docs(session: Session) -> None: """Build and serve the documentation with live reloading on file changes.""" args = session.posargs or ["--open-browser", "docs", "docs/_build"] session.install(".") session.install("sphinx", "sphinx-autobuild", "sphinx-click", "furo") build_dir = Path("docs", "_build") if build_dir.exists(): shutil.rmtree(build_dir) session.run("sphinx-autobuild", *args)