diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 88a2b1a2..3240b7d6 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -41,3 +41,10 @@ repos: --config, .vscode/cspell.json, ] + - repo: https://github.com/bjd2385/pre-commit-gitlabci-lint + rev: v1.3.0 + hooks: + - id: gitlabci-lint + args: ["-p", "46207681"] # project id is required, but any will do + entry: env GITLAB_TOKEN=w8i5Q1CaEpF57cqxezvG gitlabci-lint + files: "^\\.gitlab-ci\\.yml$" diff --git a/.vscode/terms.txt b/.vscode/terms.txt index 97b077da..5c223833 100644 --- a/.vscode/terms.txt +++ b/.vscode/terms.txt @@ -1,3 +1,6 @@ +asprunner +autoapi +autodoc automodule BUILDDIR bumpversion @@ -6,16 +9,20 @@ cyclomatic docstrings Docutils endfor +fraunhofer Furo gitfile gitlab +gitlabci gitmoji glab includex +Jannis jannismain jimustafa jinja magiclink +Mainczyk mkdocs mkdocstrings myst diff --git a/CHANGELOG.md b/CHANGELOG.md index 891d01c5..51faf801 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [unreleased] +### Added + +- automatically assign `asprunner` tag in GitLab CI configuration intended for `gitlab-fhg` remote. +- GitLab CI artifacts are set to expire after 1 week to reduce overall storage usage (most recent artifact is kept) +- pre-commit hook that lints `.gitlab-ci.yml` and prevents committing a faulty config + - available for projects with gitlab remote using mkdocs + +### Changed + +- replace sphinx-autodoc2 with sphinx-autoapi for better google-style docstring support + +### Fixed + +- Gitlab projects using mkdocs didn't install doc requirements during CI +- docstring argument section not parsed correctly + ## [0.0.5] - 2023-09-25 ### Fixed diff --git a/DEPENDENCIES b/DEPENDENCIES new file mode 100644 index 00000000..c74aceda --- /dev/null +++ b/DEPENDENCIES @@ -0,0 +1,19 @@ +Copyright (c) 2016 The Python Packaging Authority (PyPA) + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..5bf17a11 --- /dev/null +++ b/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) +Copyright (c) 2023 Jannis Mainczyk + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE +OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/Makefile b/Makefile index 086231fa..b8557609 100644 --- a/Makefile +++ b/Makefile @@ -18,7 +18,7 @@ build/examples/gitlab_fhg: INIT_PYTHON_PROJECT_ARGS+=--remote=gitlab-fhg --remot build/examples/gitlab_iis: INIT_PYTHON_PROJECT_ARGS+=--remote=gitlab-iis --remote-url=git@git01.iis.fhg.de:mkj/sample-project.git build/examples/gitlab_iis_sphinx: INIT_PYTHON_PROJECT_ARGS+=--remote=gitlab-iis --remote-url=git@git01.iis.fhg.de:mkj/sample-project-sphinx.git --docs=sphinx -$(PUBLISHED_EXAMPLES): uncopy-template copy-template +$(PUBLISHED_EXAMPLES): uncopy-template link-template @echo "Recreating '$@'..." @rm -rf "$@" && mkdir -p "$@" init-python-project "$@" ${INIT_PYTHON_PROJECT_ARGS} --defaults --yes --verbose @@ -124,6 +124,10 @@ copy-template: @cp copier.yaml ${PKGDIR}/. uncopy-template: @rm -rf ${TEMPLATE_DEST} ${PKGDIR}/copier.yaml +link-template: + @cd ${PKGDIR} &&\ + ln -s ../../${TEMPLATE_SRC} template &&\ + ln -s ../../copier.yaml copier.yaml build-clean: uncopy-template ## remove build artifacts @rm -rf ${BUILDDIR} diff --git a/docs/developer-guide/release.md b/docs/developer-guide/release.md new file mode 100644 index 00000000..0ffdc051 --- /dev/null +++ b/docs/developer-guide/release.md @@ -0,0 +1,32 @@ +# Releasing a new version + +As this repository is hosted on three different remotes to reach different target audiences ([Fraunhofer IIS internal](https://git01.iis.fhg.de), [FHG internal](https://gitlab.cc-asp.fraunhofer.de) and [public](https://github.com/jannismain/python-project-template)), it is convenient to have their respective main branches all available under different names in your local repository: + +``` +git remote add origin git@git01.iis.fhg.de:mkj/project-template.git +git checkout main +git remote add fhg git@gitlab.cc-asp.fraunhofer.de:mkj/project-template.git +git remote add github git@github.com:jannismain/python-project-template.git +git branch --set-upstream-to=fhg/main main-fhg +git branch --set-upstream-to=github/main main-github +``` + +Each of those remotes host a version of the project template with links updated to point to that remote. Therefore, updates cannot be simply pushed to those remotes but need to be merged into their main branches, so that the platform-specific changes remain intact. + +With those preparations in place, a new release can be created like this: + +1. Commit everything that should be part of the release to be `main` branch of the IIS-internal version of the repository. That includes updating the CHANGELOG and bumping the version number. +2. Merge those changes into the `main` branches of the fhg and public remotes. + + ``` + git co main-fhg + git merge main --ff-only --ff + git co main-github + git merge main --ff-only --ff + ``` + +3. Trigger the release process on the public `main` branch + + ``` + make release + ``` diff --git a/docs/developer-guide/template-in-package.md b/docs/developer-guide/template-in-package.md new file mode 100644 index 00000000..498e29fb --- /dev/null +++ b/docs/developer-guide/template-in-package.md @@ -0,0 +1,15 @@ +# Template in Package + +## Summary + +- use `make copy-template` before building a package. +- use `make link-template` during development with an in-place installation. + +## Explanation + +Copier templates require a `copier.yaml` in the root of the git project in order to be discovered during `copier copy gh:jannismain/python-project-template`. + +However, for distribution of the template as a Python package, the template files need to be part of the Python package in `src/init_python_project/`. +So when building a package using `make build`, the template files are copied into the package using `make copy-template`. + +During development, a copy is impractical, as changes to the template are not picked up by `init-python-project`, which still uses the (now out-of-date) copy inside the package. For this reason, it is useful to *link* the template into the package using `make link-template`. Those symbolic links are not supported when building a Python package, so they are replaced the next time a package is created. diff --git a/pyproject.toml b/pyproject.toml index d7dbe85e..93b8d987 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,6 +11,7 @@ authors = [{ name = "Jannis Mainczyk", email = "jmainczyk@gmail.com" }] maintainers = [{ name = "Jannis Mainczyk", email = "jmainczyk@gmail.com" }] dependencies = ["copier", "typer[all]"] dynamic = ["version"] +license = { file = "LICENSE", type = "MIT" } [tool.setuptools_scm] fallback_version = "0.0.0" diff --git a/template/{% if docs == 'sphinx' and docs_template=='none' %}docs{% endif %}/conf.py.jinja b/template/{% if docs == 'sphinx' and docs_template=='none' %}docs{% endif %}/conf.py.jinja index 949edaf2..89f150de 100644 --- a/template/{% if docs == 'sphinx' and docs_template=='none' %}docs{% endif %}/conf.py.jinja +++ b/template/{% if docs == 'sphinx' and docs_template=='none' %}docs{% endif %}/conf.py.jinja @@ -38,25 +38,15 @@ myst_enable_extensions = [ "colon_fence", # https://myst-parser.readthedocs.io/en/latest/syntax/optional.html#markdown-figures ] -# -- Options for sphinx-autodoc2 --------------------------------------------- -# https://sphinx-autodoc2.readthedocs.io/en/latest/config.html +# -- Options for sphinx-autoapi ---------------------------------------------- +# https://sphinx-autoapi.readthedocs.io/en/latest/reference/config.html -extensions += ["autodoc2"] # https://sphinx-autodoc2.readthedocs.io/ - -autodoc2_output_dir = "apidocs" -autodoc2_packages = [ - "../src/{{package_name}}", +extensions += [ + "sphinx.ext.napoleon", # required to parse google-style docstrings + "sphinx.ext.autodoc", # required to parse typehints + "autoapi.extension", ] -autodoc2_render_plugin = "md" -{% raw %} -autodoc2_index_template = """ -API Documentation -================= - -.. toctree:: - :titlesonly: -{% for package in top_level %} - {{ package }} -{%- endfor %} -""" -{% endraw -%} +autoapi_dirs = ["../src/{{package_name}}"] + +# https://sphinx-autoapi.readthedocs.io/en/latest/how_to.html#how-to-include-type-annotations-as-types-in-rendered-docstrings +autodoc_typehints = "description" diff --git a/template/{% if docs == 'sphinx' and docs_template=='none' %}docs{% endif %}/index.md b/template/{% if docs == 'sphinx' and docs_template=='none' %}docs{% endif %}/index.md index ec0fab19..7c83c926 100644 --- a/template/{% if docs == 'sphinx' and docs_template=='none' %}docs{% endif %}/index.md +++ b/template/{% if docs == 'sphinx' and docs_template=='none' %}docs{% endif %}/index.md @@ -11,5 +11,4 @@ hide-toc: true :hidden: changelog -apidocs/index ``` diff --git a/template/{% if docs == 'sphinx' and docs_template=='none' %}docs{% endif %}/requirements.txt b/template/{% if docs == 'sphinx' and docs_template=='none' %}docs{% endif %}/requirements.txt index 4df05720..06f7877e 100644 --- a/template/{% if docs == 'sphinx' and docs_template=='none' %}docs{% endif %}/requirements.txt +++ b/template/{% if docs == 'sphinx' and docs_template=='none' %}docs{% endif %}/requirements.txt @@ -1,5 +1,5 @@ sphinx furo myst_parser -sphinx-autodoc2 +sphinx-autoapi sphinx-autobuild diff --git a/template/{% if precommit %}.pre-commit-config.yaml{% endif %}.jinja b/template/{% if precommit %}.pre-commit-config.yaml{% endif %}.jinja index f1c504fb..b3dc0e0d 100644 --- a/template/{% if precommit %}.pre-commit-config.yaml{% endif %}.jinja +++ b/template/{% if precommit %}.pre-commit-config.yaml{% endif %}.jinja @@ -8,4 +8,11 @@ repos: - id: end-of-file-fixer exclude: .*\.dat - id: check-yaml - - id: check-added-large-files + - id: check-added-large-files{% if remote.startswith('gitlab') and docs != 'sphinx' %} + - repo: https://github.com/bjd2385/pre-commit-gitlabci-lint + rev: v1.3.0 + hooks: + - id: gitlabci-lint + args: ["-p", "46207681"] # project id is required, but any will do + entry: env GITLAB_TOKEN=w8i5Q1CaEpF57cqxezvG gitlabci-lint + files: "^\\.gitlab-ci\\.yml$"{% endif %} diff --git a/template/{% if remote.startswith('gitlab') %}.gitlab-ci.yml{% endif %}.jinja b/template/{% if remote.startswith('gitlab') %}.gitlab-ci.yml{% endif %}.jinja index f33e6eeb..d741a94c 100644 --- a/template/{% if remote.startswith('gitlab') %}.gitlab-ci.yml{% endif %}.jinja +++ b/template/{% if remote.startswith('gitlab') %}.gitlab-ci.yml{% endif %}.jinja @@ -1,4 +1,6 @@ -image: python:latest +default: + image: python:latest{% if remote=='gitlab-fhg' %} + tags: [asprunner]{% endif %} test: cache: # reuse venv in subsequent jobs @@ -35,14 +37,16 @@ test: - build/coverage - build/badges - env/ + expire_in: 1w # most recent artifact is always kept {% if docs == 'sphinx' -%} include: docs/.gitlab/docs.yml -{% elif docs == 'mkdocs' %} +{% elif docs == 'mkdocs' -%} docs: script: - mkdocs build --clean --site-dir build/docs + - pip install -r docs/requirements.txt + - mkdocs build --clean --site-dir build/docs artifacts: paths: - build/docs @@ -65,3 +69,4 @@ pages: artifacts: paths: - public + expire_in: 1w diff --git a/tests/test_template.py b/tests/test_template.py index 5f54bbe7..2bb0f42d 100644 --- a/tests/test_template.py +++ b/tests/test_template.py @@ -28,6 +28,11 @@ ) +def get_precommit_hooks(pre_commit_config_path: Path) -> list[str]: + pre_commit_config = yaml.safe_load(pre_commit_config_path.open()) + return [hook["id"] for repo in pre_commit_config["repos"] for hook in repo["hooks"]] + + @pytest.fixture def venv(tmp_path): """Create virtual environment in subdirectory of tmp_path.""" @@ -83,6 +88,14 @@ def test_template_generation( fp_precommit_config = tmp_path / ".pre-commit-config.yaml" assert fp_precommit_config.is_file() == precommit + if precommit: + # gitlabci-lint incompatible with sphinx, as ci job is imported from docs/.gitlab/docs.yml + # so it is currently only included for projects using mkdocs with gitlab + if remote.startswith("gitlab") and docs != "sphinx": + assert "gitlabci-lint" in get_precommit_hooks(fp_precommit_config) + else: + assert "gitlabci-lint" not in get_precommit_hooks(fp_precommit_config) + fp_git = tmp_path / ".git" assert fp_git.is_dir(), "new projects should be git repositories" @@ -170,12 +183,14 @@ def test_remote_option(tmp_path: Path, remote: str): readme_template_url = (tmp_path / "README.md").open().readlines()[-1].strip() - if remote == "gitlab": + if remote == "github": assert remote_url == f"git@github.com:{user_name}/wonderful-project.git" assert (tmp_path / ".github").is_dir() assert "github.com" in readme_template_url if remote.startswith("gitlab"): - assert (tmp_path / ".gitlab-ci.yml").is_file() + gitlab_ci_yml = tmp_path / ".gitlab-ci.yml" + assert gitlab_ci_yml.is_file() + check_call(["pre-commit", "run", "--all-files", "gitlabci-lint"], cwd=str(tmp_path)) if remote.endswith("iis"): assert remote_url == f"git@git01.iis.fhg.de:{user_name}/wonderful-project.git" assert "git01.iis.fhg.de" in readme_template_url @@ -206,8 +221,6 @@ def test_docs_option(venv: VirtualEnvironment, tmp_path: Path, docs: str): elif docs == "sphinx": fp_sphinx_makefile = root / "docs" / "Makefile" assert fp_sphinx_makefile.is_file(), "sphinx Makefile should exist" - fp_sphinx_requirements = root / "docs" / "requirements.txt" - assert fp_sphinx_requirements.is_file(), "sphinx requirements file should exist" fp_sphinx_ci_job = root / "docs" / ".gitlab" / "docs.yml" assert fp_sphinx_ci_job.is_file(), "sphinx ci job should exist"