diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..ebb44c2 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,32 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/python +{ + "name": "Cookiecutter Poetry", + // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile + "image": "mcr.microsoft.com/devcontainers/python:1-3.11-bullseye", + "features": { + "ghcr.io/devcontainers-contrib/features/cookiecutter:2": {}, + "ghcr.io/devcontainers-contrib/features/poetry:2": {} + }, + + // Use 'postCreateCommand' to run commands after the container is created. + "postCreateCommand": "./.devcontainer/postCreateCommand.sh", + + // Configure tool-specific properties. + "customizations": { + "vscode": { + "extensions": [ + "ms-python.python" + ], + "settings": { + "python.testing.pytestArgs": [ + "tests" + ], + "python.testing.unittestEnabled": false, + "python.testing.pytestEnabled": true, + "python.defaultInterpreterPath": "/workspaces/cookiecutter-poetry/.venv/bin/python", + "python.testing.pytestPath": "/workspaces/cookiecutter-poetry/.venv/bin/pytest" + } + } + } +} diff --git a/.devcontainer/postCreateCommand.sh b/.devcontainer/postCreateCommand.sh new file mode 100755 index 0000000..38dca89 --- /dev/null +++ b/.devcontainer/postCreateCommand.sh @@ -0,0 +1,7 @@ +#! /usr/bin/env bash + +# Install Dependencies +poetry install --with dev + +# Install pre-commit hooks +poetry run pre-commit install --install-hooks diff --git a/.gitignore b/.gitignore index 5584f4b..386d985 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,7 @@ cookiecutter-poetry-example # From https://raw.githubusercontent.com/github/gitignore/main/Python.gitignore - Byte-compiled / optimized / DLL files +# Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class @@ -165,7 +165,7 @@ cython_debug/ #.idea/ # Exclude vscode config files -.vscode +.vscode/ # Exclude .DS_Store files from being added .DS_Store diff --git a/cookiecutter.json b/cookiecutter.json index 7f62645..dccfeb8 100644 --- a/cookiecutter.json +++ b/cookiecutter.json @@ -11,5 +11,6 @@ "mkdocs": ["y", "n"], "codecov" : ["y","n"], "dockerfile" : ["y","n"], + "devcontainer" : ["y","n"], "open_source_license": ["MIT license", "BSD license", "ISC license", "Apache Software License 2.0", "GNU General Public License v3", "Not open source"] } diff --git a/docs/features/devcontainer.md b/docs/features/devcontainer.md new file mode 100644 index 0000000..98f253d --- /dev/null +++ b/docs/features/devcontainer.md @@ -0,0 +1,9 @@ +# Reproducible development environments with VSCode devcontainers + +If `devcontainer` is set to `"y"` project uses the VSCode [devcontainer](https://code.visualstudio.com/docs/devcontainers/containers) +specification to create a reproducible development environment. The devcontainer +is defined in the `.devcontainer` directory and pre-installs all dependencies +from poetry required to develop, test and build the project. + +The devcontainer also installs the pre-commit hooks and configures the VSCode python +extension to use the appropriate python interpretor and pytest paths. diff --git a/docs/index.md b/docs/index.md index 304d1cd..d528b24 100644 --- a/docs/index.md +++ b/docs/index.md @@ -28,6 +28,7 @@ This is a modern Cookiecutter template that can be used to initiate a Python pro - Documentation with [MkDocs](https://www.mkdocs.org/) - Compatibility testing for multiple versions of Python with [Tox](https://tox.wiki/en/latest/) - Containerization with [Docker](https://www.docker.com/) +- Development environment with [VSCode devcontainers](https://code.visualstudio.com/docs/remote/containers) An example of a repository generated with this package can be found [here](https://github.com/fpgmaas/cookiecutter-poetry-example). diff --git a/docs/prompt_arguments.md b/docs/prompt_arguments.md index 50d49f4..949e006 100644 --- a/docs/prompt_arguments.md +++ b/docs/prompt_arguments.md @@ -69,6 +69,10 @@ will be deployed to the `gh-pages` branch. `"y"` or `"n"`. Adds a simple [Dockerfile](https://docker.com). +**devcontainer** + +`"y"` or `"n"`. Adds a [devcontainer](https://code.visualstudio.com/docs/devcontainers/containers) specification to the project along with pre-installed pre-commit hooks and VSCode python extension configuration. + **open_source_license** Choose a [license](https://choosealicense.com/). Options: diff --git a/hooks/post_gen_project.py b/hooks/post_gen_project.py index a1616a0..cd87c96 100644 --- a/hooks/post_gen_project.py +++ b/hooks/post_gen_project.py @@ -31,3 +31,6 @@ def remove_dir(filepath: str) -> None: remove_file("codecov.yaml") if "{{cookiecutter.include_github_actions}}" == "y": remove_file(".github/workflows/validate-codecov-config.yml") + + if "{{cookiecutter.devcontainer}}" != "y": + remove_dir(".devcontainer") diff --git a/mkdocs.yml b/mkdocs.yml index a81a6cc..08fc672 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -20,6 +20,7 @@ nav: - Documentation with MkDocs: features/mkdocs.md - Compatibility testing with Tox: features/tox.md - Containerization with Docker: features/docker.md + - Devcontainer with VSCode: features/devcontainer.md - Tutorial: tutorial.md - Prompt Arguments: prompt_arguments.md plugins: diff --git a/pyproject.toml b/pyproject.toml index 579a772..64caf3f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,6 +46,9 @@ preview = true [tool.poetry.scripts] ccp = 'cookiecutter_poetry.cli:main' +[tool.pytest.ini_options] +testpaths = ["tests"] + [tool.coverage.report] skip_empty = true diff --git a/tests/test_cookiecutter.py b/tests/test_cookiecutter.py index cbe15b4..d3ecc72 100644 --- a/tests/test_cookiecutter.py +++ b/tests/test_cookiecutter.py @@ -44,6 +44,24 @@ def test_using_pytest(cookies, tmp_path): assert subprocess.check_call(shlex.split("poetry run make test")) == 0 +def test_devcontainer(cookies, tmp_path): + """Test that the devcontainer files are created when devcontainer=y""" + with run_within_dir(tmp_path): + result = cookies.bake(extra_context={"devcontainer": "y"}) + assert result.exit_code == 0 + assert os.path.isfile(f"{result.project_path}/.devcontainer/devcontainer.json") + assert os.path.isfile(f"{result.project_path}/.devcontainer/postCreateCommand.sh") + + +def test_not_devcontainer(cookies, tmp_path): + """Test that the devcontainer files are not created when devcontainer=n""" + with run_within_dir(tmp_path): + result = cookies.bake(extra_context={"devcontainer": "n"}) + assert result.exit_code == 0 + assert not os.path.isfile(f"{result.project_path}/.devcontainer/devcontainer.json") + assert not os.path.isfile(f"{result.project_path}/.devcontainer/postCreateCommand.sh") + + def test_cicd_contains_artifactory_secrets(cookies, tmp_path): with run_within_dir(tmp_path): result = cookies.bake(extra_context={"publish_to": "artifactory"}) diff --git a/{{cookiecutter.project_name}}/.devcontainer/devcontainer.json b/{{cookiecutter.project_name}}/.devcontainer/devcontainer.json new file mode 100644 index 0000000..c016aa9 --- /dev/null +++ b/{{cookiecutter.project_name}}/.devcontainer/devcontainer.json @@ -0,0 +1,31 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/python +{ + "name": "{{cookiecutter.project_name}}", + // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile + "image": "mcr.microsoft.com/devcontainers/python:1-3.11-bullseye", + "features": { + "ghcr.io/devcontainers-contrib/features/poetry:2": {} + }, + + // Use 'postCreateCommand' to run commands after the container is created. + "postCreateCommand": "./.devcontainer/postCreateCommand.sh", + + // Configure tool-specific properties. + "customizations": { + "vscode": { + "extensions": [ + "ms-python.python" + ], + "settings": { + "python.testing.pytestArgs": [ + "tests" + ], + "python.testing.unittestEnabled": false, + "python.testing.pytestEnabled": true, + "python.defaultInterpreterPath": "/workspaces/{{cookiecutter.project_name}}/.venv/bin/python", + "python.testing.pytestPath": "/workspaces/{{cookiecutter.project_name}}/.venv/bin/pytest" + } + } + } +} diff --git a/{{cookiecutter.project_name}}/.devcontainer/postCreateCommand.sh b/{{cookiecutter.project_name}}/.devcontainer/postCreateCommand.sh new file mode 100755 index 0000000..38dca89 --- /dev/null +++ b/{{cookiecutter.project_name}}/.devcontainer/postCreateCommand.sh @@ -0,0 +1,7 @@ +#! /usr/bin/env bash + +# Install Dependencies +poetry install --with dev + +# Install pre-commit hooks +poetry run pre-commit install --install-hooks diff --git a/{{cookiecutter.project_name}}/.gitignore b/{{cookiecutter.project_name}}/.gitignore index 530641f..f64e26a 100644 --- a/{{cookiecutter.project_name}}/.gitignore +++ b/{{cookiecutter.project_name}}/.gitignore @@ -2,7 +2,7 @@ docs/source # From https://raw.githubusercontent.com/github/gitignore/main/Python.gitignore - Byte-compiled / optimized / DLL files +# Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class @@ -156,6 +156,9 @@ dmypy.json # Cython debug symbols cython_debug/ +# Vscode config files +.vscode/ + # PyCharm # JetBrains specific template is maintained in a separate JetBrains.gitignore that can # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore diff --git a/{{cookiecutter.project_name}}/pyproject.toml b/{{cookiecutter.project_name}}/pyproject.toml index 50cd64c..7a09b34 100644 --- a/{{cookiecutter.project_name}}/pyproject.toml +++ b/{{cookiecutter.project_name}}/pyproject.toml @@ -51,6 +51,9 @@ warn_return_any = "True" warn_unused_ignores = "True" show_error_codes = "True" +[tool.pytest.ini_options] +testpaths = ["tests"] + [tool.ruff] target-version = "py37" line-length = 120