From eaaf0d299240afb9e94ebd5a5271a95e6271ea19 Mon Sep 17 00:00:00 2001 From: DavHau Date: Thu, 20 Jul 2023 17:32:28 +0200 Subject: [PATCH] Merge pull request 'templates: add python-project' (#10) from python-template into main --- .gitignore | 9 +++ flake.nix | 1 + templates/python-project/.envrc | 1 + templates/python-project/default.nix | 57 +++++++++++++++++ templates/python-project/flake-module.nix | 10 +++ templates/python-project/my_lib/__init__.py | 5 ++ templates/python-project/my_tool/__init__.py | 17 ++++++ templates/python-project/pyproject.toml | 61 +++++++++++++++++++ templates/python-project/shell.nix | 45 ++++++++++++++ templates/python-project/tests/conftest.py | 10 +++ templates/python-project/tests/test_cli.py | 16 +++++ .../tests/test_detect_git_repo.py | 16 +++++ 12 files changed, 248 insertions(+) create mode 100644 templates/python-project/.envrc create mode 100644 templates/python-project/default.nix create mode 100644 templates/python-project/flake-module.nix create mode 100644 templates/python-project/my_lib/__init__.py create mode 100644 templates/python-project/my_tool/__init__.py create mode 100644 templates/python-project/pyproject.toml create mode 100644 templates/python-project/shell.nix create mode 100644 templates/python-project/tests/conftest.py create mode 100644 templates/python-project/tests/test_cli.py create mode 100644 templates/python-project/tests/test_detect_git_repo.py diff --git a/.gitignore b/.gitignore index fcfc4a1b..7733d7db 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,10 @@ +.direnv result* + +# python +__pycache__ +.coverage +.mypy_cache +.pytest_cache +.pythonenv +.ruff_cache diff --git a/flake.nix b/flake.nix index 606548ad..79ac0cb3 100644 --- a/flake.nix +++ b/flake.nix @@ -22,6 +22,7 @@ imports = [ ./flake-parts/packages.nix ./flake-parts/formatting.nix + ./templates/python-project/flake-module.nix ]; flake = { nixosConfigurations.installer = lib.nixosSystem { diff --git a/templates/python-project/.envrc b/templates/python-project/.envrc new file mode 100644 index 00000000..1d953f4b --- /dev/null +++ b/templates/python-project/.envrc @@ -0,0 +1 @@ +use nix diff --git a/templates/python-project/default.nix b/templates/python-project/default.nix new file mode 100644 index 00000000..ddf3e165 --- /dev/null +++ b/templates/python-project/default.nix @@ -0,0 +1,57 @@ +{ + pkgs ? import {}, + lib ? pkgs.lib, + python3 ? pkgs.python3, +}: let + pyproject = builtins.fromTOML (builtins.readFile ./pyproject.toml); + name = pyproject.project.name; + + src = lib.cleanSource ./.; + + dependencies = lib.attrValues { + # inherit (python3.pkgs) + # some-package + # ; + }; + + devDependencies = lib.attrValues { + inherit (pkgs) ruff; + inherit (python3.pkgs) + black + mypy + pytest + pytest-cov + ; + }; + + package = python3.pkgs.buildPythonPackage { + inherit name src; + format = "pyproject"; + nativeBuildInputs = [ + python3.pkgs.setuptools + ]; + propagatedBuildInputs = + dependencies + ++ []; + passthru.tests = {inherit check;}; + passthru.devDependencies = devDependencies; + }; + + checkPython = python3.withPackages (ps: devDependencies); + + check = pkgs.runCommand "${name}-check" {} '' + cp -r ${src} ./src + chmod +w -R ./src + cd src + export PYTHONPATH=. + echo -e "\x1b[32m## run ruff\x1b[0m" + ${pkgs.ruff}/bin/ruff check . + echo -e "\x1b[32m## run mypy\x1b[0m" + ${checkPython}/bin/mypy . + echo -e "\x1b[32m## run pytest\x1b[0m" + ${checkPython}/bin/pytest + touch $out + ''; + +in + package diff --git a/templates/python-project/flake-module.nix b/templates/python-project/flake-module.nix new file mode 100644 index 00000000..38813ea7 --- /dev/null +++ b/templates/python-project/flake-module.nix @@ -0,0 +1,10 @@ +{ + perSystem = {pkgs, ...}: let + pyproject = builtins.fromTOML (builtins.readFile ./src/pyproject.toml); + name = pyproject.project.name; + package = pkgs.callPackage ./default.nix {}; + in { + # packages.${name} = package; + checks.python-template = package.tests.check; + }; +} diff --git a/templates/python-project/my_lib/__init__.py b/templates/python-project/my_lib/__init__.py new file mode 100644 index 00000000..d87e45fe --- /dev/null +++ b/templates/python-project/my_lib/__init__.py @@ -0,0 +1,5 @@ +import os + + +def detect_git_repo(path: str) -> bool: + return os.path.exists(f"{path}/.git") diff --git a/templates/python-project/my_tool/__init__.py b/templates/python-project/my_tool/__init__.py new file mode 100644 index 00000000..4b0d9553 --- /dev/null +++ b/templates/python-project/my_tool/__init__.py @@ -0,0 +1,17 @@ +import argparse + +# statement that doesn't need testing +__version__ = "1.0.0" # pragma: no cover + + +# this will be an entrypoint under /bin/my_cli (see pyproject.toml config) +def my_cli() -> None: + parser = argparse.ArgumentParser(description="my-tool") + parser.add_argument( + "-v", "--version", help="Show the version of this program", action="store_true" + ) + args = parser.parse_args() + if args.version: + print(f"Version: {__version__}") + else: + parser.print_help() diff --git a/templates/python-project/pyproject.toml b/templates/python-project/pyproject.toml new file mode 100644 index 00000000..a3ee5c69 --- /dev/null +++ b/templates/python-project/pyproject.toml @@ -0,0 +1,61 @@ +[build-system] +requires = ["setuptools"] +build-backend = "setuptools.build_meta" + +[tool.setuptools.packages.find] +include = ["my_tool*"] + +[project] +name = "my_tool" +description = "internal tooling of cLAN" +dynamic = ["version"] +scripts = {my-tool = "my_tool:my_cli"} + +[tool.pytest.ini_options] +addopts = "--cov . --cov-report term --cov-fail-under=100 --no-cov-on-fail" + +[tool.mypy] +python_version = "3.10" +warn_redundant_casts = true +disallow_untyped_calls = true +disallow_untyped_defs = true +no_implicit_optional = true +exclude = [ + "tests" +] + +[[tool.mypy.overrides]] +module = "setuptools.*" +ignore_missing_imports = true + +[[tool.mypy.overrides]] +module = "pytest.*" +ignore_missing_imports = true + +[tool.ruff] +line-length = 88 + +select = ["E", "F", "I"] +ignore = [ "E501" ] + +[tool.black] +line-length = 88 +target-version = ['py310'] +include = '\.pyi?$' +exclude = ''' +/( + \.git + | \.hg + | \.mypy_cache + | \.tox + | \.venv + | _build + | buck-out + | build + | dist + # The following are specific to Black, you probably don't want those. + | blib2to3 + | tests/data + | profiling +)/ +''' diff --git a/templates/python-project/shell.nix b/templates/python-project/shell.nix new file mode 100644 index 00000000..7432f8b8 --- /dev/null +++ b/templates/python-project/shell.nix @@ -0,0 +1,45 @@ +{ + pkgs ? import {}, + system ? builtins.currentSystem, +}: let + lib = pkgs.lib; + python3 = pkgs.python3; + package = import ./default.nix { + inherit lib python3; + }; + pythonWithDeps = python3.withPackages ( + ps: + package.propagatedBuildInputs + ++ package.devDependencies + ++ [ + ps.pip + ] + ); + devShell = pkgs.mkShell { + packages = [ + pkgs.ruff + pythonWithDeps + ]; + # sets up an editable install and add enty points to $PATH + shellHook = '' + tmp_path=$(realpath ./.pythonenv) + repo_root=$(realpath .) + if ! cmp -s pyproject.toml $tmp_path/pyproject.toml; then + rm -rf $tmp_path + mkdir -p "$tmp_path/${pythonWithDeps.sitePackages}" + + ${pythonWithDeps.interpreter} -m pip install \ + --quiet \ + --disable-pip-version-check \ + --no-index \ + --no-build-isolation \ + --prefix "$tmp_path" \ + --editable $repo_root && \ + cp -a pyproject.toml $tmp_path/pyproject.toml + fi + export PATH="$tmp_path/bin:$PATH" + export PYTHONPATH="$repo_root:$tmp_path/${pythonWithDeps.sitePackages}" + ''; + }; +in + devShell diff --git a/templates/python-project/tests/conftest.py b/templates/python-project/tests/conftest.py new file mode 100644 index 00000000..55b2e9bd --- /dev/null +++ b/templates/python-project/tests/conftest.py @@ -0,0 +1,10 @@ +import subprocess + +import pytest + + +# returns a temporary directory with a fake git repo +@pytest.fixture() +def git_repo_path(tmp_path): + subprocess.run(["mkdir", ".git"], cwd=tmp_path) + return tmp_path diff --git a/templates/python-project/tests/test_cli.py b/templates/python-project/tests/test_cli.py new file mode 100644 index 00000000..5311f2bd --- /dev/null +++ b/templates/python-project/tests/test_cli.py @@ -0,0 +1,16 @@ +import sys + +import my_tool + + +def test_no_args(capsys): + my_tool.my_cli() + captured = capsys.readouterr() + assert captured.out.startswith("usage:") + + +def test_version(capsys, monkeypatch): + monkeypatch.setattr(sys, "argv", ["", "--version"]) + my_tool.my_cli() + captured = capsys.readouterr() + assert captured.out.startswith("Version:") diff --git a/templates/python-project/tests/test_detect_git_repo.py b/templates/python-project/tests/test_detect_git_repo.py new file mode 100644 index 00000000..529105c1 --- /dev/null +++ b/templates/python-project/tests/test_detect_git_repo.py @@ -0,0 +1,16 @@ +import tempfile + +import my_lib + + +# using the fixture from conftest.py +def test_is_git_repo(git_repo_path: str): + result = my_lib.detect_git_repo(git_repo_path) + assert result is True + + +# using the fixture from conftest.py +def test_is_not_git_repo(): + with tempfile.TemporaryDirectory() as tempdir: + result = my_lib.detect_git_repo(tempdir) + assert result is False