From 6754f04fcd70a05b8f48fb62f44d65a92c8959f4 Mon Sep 17 00:00:00 2001 From: Alexander Hess Date: Mon, 3 Aug 2020 21:24:06 +0200 Subject: [PATCH] Add nox as the task runner - set up skeletons for all planned nox sessions - provide a base configuration for all nox sessions - create two utility functions: + _begin() => run commands common to all sessions + _install_packages() => install dependencies in nox sessions respecting the versions pinned in poetry.lock - add the following file: + noxfile.py --- .gitignore | 1 + noxfile.py | 114 +++++++++++++++++++++++++++++++++++++ poetry.lock | 148 ++++++++++++++++++++++++++++++++++++++++++++++++- pyproject.toml | 2 + 4 files changed, 263 insertions(+), 2 deletions(-) create mode 100644 noxfile.py diff --git a/.gitignore b/.gitignore index 47d1330..2e72e0b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.cache/ *.egg-info/ .python-version .venv/ diff --git a/noxfile.py b/noxfile.py new file mode 100644 index 0000000..14b844b --- /dev/null +++ b/noxfile.py @@ -0,0 +1,114 @@ +"""Configure nox for the isolated test and lint environments.""" + +import os +import tempfile +from typing import Any + +import nox +from nox.sessions import Session + + +MAIN_PYTHON = '3.8' + +# Keep the project is forward compatible. +NEXT_PYTHON = '3.9' + +# Paths with *.py files. +SRC_LOCATIONS = 'noxfile.py', 'src/' + + +# Use a unified .cache/ folder for all tools. +nox.options.envdir = '.cache/nox' + +# All tools except git and poetry are project dependencies. +# Avoid accidental successes if the environment is not set up properly. +nox.options.error_on_external_run = True + +# Run only CI related checks by default. +nox.options.sessions = ( + 'format', + 'lint', + f'test-{MAIN_PYTHON}', + f'test-{NEXT_PYTHON}', +) + + +@nox.session(name='format', python=MAIN_PYTHON) +def format_(session: Session): + """Format source files.""" + _begin(session) + + +@nox.session(python=MAIN_PYTHON) +def lint(session: Session): + """Lint source files.""" + _begin(session) + + +@nox.session(python=[MAIN_PYTHON, NEXT_PYTHON]) +def test(session: Session): + """Test the code base.""" + _begin(session) + + +@nox.session(name='pre-commit', python=MAIN_PYTHON, venv_backend='none') +def pre_commit(session: Session): + """Source files must be well-formed before they enter git.""" + _begin(session) + session.notify('format') + session.notify('lint') + + +@nox.session(name='pre-merge', python=MAIN_PYTHON, venv_backend='none') +def pre_merge(session: Session): + """The test suite must pass before merges are made.""" + _begin(session) + session.notify('test') + + +def _begin(session: Session) -> None: + """Show generic info about a session.""" + if session.posargs: + print('extra arguments:', *session.posargs) + + session.run('python', '--version') + + # Fake GNU's pwd. + session.log('pwd') + print(os.getcwd()) + + +def _install_packages( + session: Session, *packages_or_pip_args: str, **kwargs: Any +) -> None: + """Install packages respecting the poetry.lock file. + + This function wraps nox.sessions.Session.install() such that it installs + packages respecting the pinnned versions specified in poetry's lock file. + This makes nox sessions even more deterministic. + + Args: + session: the Session object + *packages_or_pip_args: the packages to be installed or pip options + **kwargs: passed on to nox.sessions.Session.install() + """ + if session.virtualenv.reuse_existing: + session.log( + 'No dependencies are installed as an existing environment is re-used', + ) + return + + session.log('Dependencies are installed respecting the poetry.lock file') + + with tempfile.NamedTemporaryFile() as requirements_txt: + session.run( + 'poetry', + 'export', + '--dev', + '--format=requirements.txt', + f'--output={requirements_txt.name}', + external=True, + ) + session.install( + f'--constraint={requirements_txt.name}', *packages_or_pip_args, **kwargs, + ) diff --git a/poetry.lock b/poetry.lock index b9e1c09..d1f77fe 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,8 +1,152 @@ -package = [] +[[package]] +category = "dev" +description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +name = "appdirs" +optional = false +python-versions = "*" +version = "1.4.4" + +[[package]] +category = "dev" +description = "Bash tab completion for argparse" +name = "argcomplete" +optional = false +python-versions = "*" +version = "1.12.0" + +[package.extras] +test = ["coverage", "flake8", "pexpect", "wheel"] + +[[package]] +category = "dev" +description = "Cross-platform colored terminal text." +marker = "sys_platform == \"win32\"" +name = "colorama" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "0.4.3" + +[[package]] +category = "dev" +description = "Log formatting with colors!" +name = "colorlog" +optional = false +python-versions = "*" +version = "4.2.1" + +[package.dependencies] +colorama = "*" + +[[package]] +category = "dev" +description = "Distribution utilities" +name = "distlib" +optional = false +python-versions = "*" +version = "0.3.1" + +[[package]] +category = "dev" +description = "A platform independent file lock." +name = "filelock" +optional = false +python-versions = "*" +version = "3.0.12" + +[[package]] +category = "dev" +description = "Flexible test automation." +name = "nox" +optional = false +python-versions = ">=3.5" +version = "2020.5.24" + +[package.dependencies] +argcomplete = ">=1.9.4,<2.0" +colorlog = ">=2.6.1,<5.0.0" +py = ">=1.4.0,<2.0.0" +virtualenv = ">=14.0.0" + +[package.extras] +tox_to_nox = ["jinja2", "tox"] + +[[package]] +category = "dev" +description = "library with cross-python path, ini-parsing, io, code, log facilities" +name = "py" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "1.9.0" + +[[package]] +category = "dev" +description = "Python 2 and 3 compatibility utilities" +name = "six" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +version = "1.15.0" + +[[package]] +category = "dev" +description = "Virtual Python Environment builder" +name = "virtualenv" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" +version = "20.0.29" + +[package.dependencies] +appdirs = ">=1.4.3,<2" +distlib = ">=0.3.1,<1" +filelock = ">=3.0.0,<4" +six = ">=1.9.0,<2" + +[package.extras] +docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=19.9.0rc1)"] +testing = ["coverage (>=5)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)", "pytest-xdist (>=1.31.0)", "packaging (>=20.0)", "xonsh (>=0.9.16)"] [metadata] -content-hash = "fafb334cb038533f851c23d0b63254223abf72ce4f02987e7064b0c95566699a" +content-hash = "4ff12f695975572453949608224e06fb36641151ee69668f8603f7bc3435a51f" lock-version = "1.0" python-versions = "^3.8" [metadata.files] +appdirs = [ + {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, + {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, +] +argcomplete = [ + {file = "argcomplete-1.12.0-py2.py3-none-any.whl", hash = "sha256:91dc7f9c7f6281d5a0dce5e73d2e33283aaef083495c13974a7dd197a1cdc949"}, + {file = "argcomplete-1.12.0.tar.gz", hash = "sha256:2fbe5ed09fd2c1d727d4199feca96569a5b50d44c71b16da9c742201f7cc295c"}, +] +colorama = [ + {file = "colorama-0.4.3-py2.py3-none-any.whl", hash = "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff"}, + {file = "colorama-0.4.3.tar.gz", hash = "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"}, +] +colorlog = [ + {file = "colorlog-4.2.1-py2.py3-none-any.whl", hash = "sha256:43597fd822ce705190fc997519342fdaaf44b9b47f896ece7aa153ed4b909c74"}, + {file = "colorlog-4.2.1.tar.gz", hash = "sha256:75e55822c3a3387d721579241e776de2cf089c9ef9528b1f09e8b04d403ad118"}, +] +distlib = [ + {file = "distlib-0.3.1-py2.py3-none-any.whl", hash = "sha256:8c09de2c67b3e7deef7184574fc060ab8a793e7adbb183d942c389c8b13c52fb"}, + {file = "distlib-0.3.1.zip", hash = "sha256:edf6116872c863e1aa9d5bb7cb5e05a022c519a4594dc703843343a9ddd9bff1"}, +] +filelock = [ + {file = "filelock-3.0.12-py3-none-any.whl", hash = "sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836"}, + {file = "filelock-3.0.12.tar.gz", hash = "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59"}, +] +nox = [ + {file = "nox-2020.5.24-py3-none-any.whl", hash = "sha256:c4509621fead99473a1401870e680b0aadadce5c88440f0532863595176d64c1"}, + {file = "nox-2020.5.24.tar.gz", hash = "sha256:61a55705736a1a73efbd18d5b262a43d55a1176546e0eb28b29064cfcffe26c0"}, +] +py = [ + {file = "py-1.9.0-py2.py3-none-any.whl", hash = "sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2"}, + {file = "py-1.9.0.tar.gz", hash = "sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342"}, +] +six = [ + {file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"}, + {file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"}, +] +virtualenv = [ + {file = "virtualenv-20.0.29-py2.py3-none-any.whl", hash = "sha256:8aa9c37b082664dbce2236fa420759c02d64109d8e6013593ad13914718a30fd"}, + {file = "virtualenv-20.0.29.tar.gz", hash = "sha256:f14a0a98ea4397f0d926cff950361766b6a73cd5975ae7eb259d12919f819a25"}, +] diff --git a/pyproject.toml b/pyproject.toml index eb24353..158fb5d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,3 +23,5 @@ repository = "https://github.com/webartifex/urban-meal-delivery" python = "^3.8" [tool.poetry.dev-dependencies] +# Task Runners +nox = "^2020.5.24"