diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..8ac6b8c --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "monthly" diff --git a/.github/workflows/dependabot-prs.yml b/.github/workflows/dependabot-prs.yml new file mode 100644 index 0000000..b87d115 --- /dev/null +++ b/.github/workflows/dependabot-prs.yml @@ -0,0 +1,19 @@ +name: Add dependabot PRs to flash reviews +on: + pull_request: + types: + - opened + - reopened + - labeled + +jobs: + add_flash_review: + name: Add dependabot PRs to flash reviews + runs-on: ubuntu-latest + steps: + - uses: actions/add-to-project@v2.0.0 + with: + project-url: https://github.com/orgs/ISISComputingGroup/projects/17 + github-token: ${{ secrets.PROJECT_TOKEN }} + labeled: dependencies + label-operator: OR \ No newline at end of file diff --git a/.github/workflows/test-template.yml b/.github/workflows/test-template.yml new file mode 100644 index 0000000..22012ef --- /dev/null +++ b/.github/workflows/test-template.yml @@ -0,0 +1,36 @@ +name: Test copier template +on: [push, workflow_call] + +jobs: + test-copier-template: + runs-on: ubuntu-latest + steps: + - uses: astral-sh/setup-uv@v7 + - uses: actions/checkout@v7 + with: + fetch-depth: 0 + - name: install copier + run: uv tool install copier + - name: expand template + run: copier copy --defaults --data-file test_project.yml --vcs-ref=HEAD . /tmp/expanded_template + - name: Git init in generated project + run: git init . + working-directory: /tmp/expanded_template + - name: Make venv in generated project + run: uv sync --extra dev + working-directory: /tmp/expanded_template + - name: Run pytest in generated project + run: uv run pytest + working-directory: /tmp/expanded_template + - name: Run ruff format check in generated project + run: uv run ruff format --check + working-directory: /tmp/expanded_template + - name: Run ruff check in generated project + run: uv run ruff check + working-directory: /tmp/expanded_template + - name: Run pyright in generated project + run: uv run pyright + working-directory: /tmp/expanded_template + - name: Build docs in generated project + run: uv run sphinx-build doc _build + working-directory: /tmp/expanded_template diff --git a/LICENSE b/LICENSE index 67ed796..b44c480 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -BSD 2-Clause License +BSD 3-Clause License Copyright (c) 2026, ISIS Experiment Controls Computing @@ -12,6 +12,10 @@ modification, are permitted provided that the following conditions are met: this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE diff --git a/README.md b/README.md new file mode 100644 index 0000000..866c268 --- /dev/null +++ b/README.md @@ -0,0 +1,28 @@ +# Python Copier template + +This is a template, for use by [copier](https://copier.readthedocs.io/en/stable/), which creates or updates python +repositories to our standard layout. + +See the [copier documentation](https://copier.readthedocs.io/en/stable/) for details of how to use this template. + +## Creating a repository + +Assume that a blank new repository, `my_project`, is checked out: +- Run `uv tool install copier` +- Run `copier copy https://github.com/ISISComputingGroup/copier_template.git ./my_project` + +Or, clone and use the template locally: +- Run `copier copy ./copier_template ./my_project` +- Answer the prompts +- `git commit` the resulting structure into `my_project` + +## Updating a generated repository + +- Run `copier update ./my_project` + +## Developing the template itself + +Run copier as normal pointed at the locally-checked out copy, but use the `--vcs-ref=HEAD` flag to force copier to use the most recent local commit. +rather than a tagged version. + +CI runs in *this* repository to check that copier can successfully instantiate the template in this repo. diff --git a/copier.yml b/copier.yml new file mode 100644 index 0000000..e2cc099 --- /dev/null +++ b/copier.yml @@ -0,0 +1,58 @@ +_subdirectory: template + +project_name: + type: str + help: What is your project name (user-friendly string)? +project_short_description: + type: str + help: A short one-sentence description of this project + validator: >- + {% if not project_short_description.strip() %} + Description must not be empty. + {% elif '\n' in project_short_description %} + Description must be a single line. + {% elif not project_short_description[0].isupper() %} + Description must start with a capital letter. + {% elif not project_short_description.endswith('.') %} + Description must end with a full stop. + {% endif %} +module_name: + type: str + help: What is your Python module name? + validator: >- + {% if not module_name | regex_search('^[a-z][a-z0-9_]+$') %} + Must be a valid Python module name + {% endif %} +github_org_name: + type: str + help: GitHub organisation name + default: ISISComputingGroup +github_repo_name: + type: str + help: GitHub repository name +publish_to_pypi: + type: bool + help: Should this project be set up to publish to (public) pypi? +doc_theme_colour: + type: str + help: Theme colour for documentation + validator: >- + {% if not doc_theme_colour | regex_search('^#[0-9A-Fa-f]{6}$') %} + Must be a 6-digit hex colour including the leading '#', e.g. #7D7D7D. + {% endif %} +ci_os_platforms: + type: yaml + help: Operating systems to test in CI + default: ["ubuntu-latest", "windows-latest"] +ci_python_versions: + type: yaml + help: Python versions to test in CI + default: ["3.12", "3.13", "3.14"] + +_message_after_copy: | + Your project "{{ project_name }}" has been created successfully! + + Remember to: + - git init . + - git push + - Create a new branch for your next feature diff --git a/template/.github/dependabot.yml b/template/.github/dependabot.yml new file mode 100644 index 0000000..b8b8480 --- /dev/null +++ b/template/.github/dependabot.yml @@ -0,0 +1,10 @@ +version: 2 +updates: + - package-ecosystem: "pip" + directory: "/" + schedule: + interval: "monthly" + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "monthly" diff --git a/template/.github/release.yml b/template/.github/release.yml new file mode 100644 index 0000000..6ea8209 --- /dev/null +++ b/template/.github/release.yml @@ -0,0 +1,18 @@ +changelog: + exclude: + labels: + - Semver-Ignore + categories: + - title: Breaking Changes + labels: + - Semver-Major + - title: New Features + labels: + - Semver-Minor + - title: Bug Fixes + labels: + - Semver-Patch + - title: Other Changes + labels: + - Semver-Docs + - "*" diff --git a/template/.github/workflows/Lint-and-test.yml.jinja b/template/.github/workflows/Lint-and-test.yml.jinja new file mode 100644 index 0000000..afb9a27 --- /dev/null +++ b/template/.github/workflows/Lint-and-test.yml.jinja @@ -0,0 +1,40 @@ +name: Lint-and-test +on: [push, workflow_call] +jobs: + tests: + runs-on: ${{ '{{' }} matrix.os {{ '}}' }} + strategy: + matrix: + os: {{ ci_os_platforms | tojson }} + version: {{ ci_python_versions | tojson }} + fail-fast: false + steps: + - uses: actions/checkout@v7 + with: + fetch-depth: 0 + - uses: astral-sh/setup-uv@v7 + with: + python-version: ${{ '{{' }} matrix.version {{ '}}' }} + - name: install requirements + run: uv sync --extra lint --extra test + - name: run ruff check + run: uv run ruff check + - name: run ruff format --check + run: uv run ruff format --check + - name: run pyright + run: uv run pyright + - name: run pytest + run: uv run pytest + results: + if: ${{ '{{' }} always() {{ '}}' }} + runs-on: ubuntu-latest + name: Final Results + needs: [tests] + steps: + - run: exit 1 + # see https://stackoverflow.com/a/67532120/4907315 + if: >- + ${{ '{{' }} + contains(needs.*.result, 'failure') + || contains(needs.*.result, 'cancelled') + {{ '}}' }} \ No newline at end of file diff --git a/template/.github/workflows/check_pr_has_label.yml b/template/.github/workflows/check_pr_has_label.yml new file mode 100644 index 0000000..4c7dfaf --- /dev/null +++ b/template/.github/workflows/check_pr_has_label.yml @@ -0,0 +1,24 @@ +name: Check PR has release labels +on: + pull_request: + types: + - opened + - reopened + - synchronize + - labeled + - unlabeled + +jobs: + has_label: + name: Check PR has release labels + runs-on: ubuntu-latest + steps: + - run: | + echo "PR does not have a release label." + exit 1 + if: | + !contains(github.event.pull_request.labels.*.name, 'Semver-Patch') && + !contains(github.event.pull_request.labels.*.name, 'Semver-Major') && + !contains(github.event.pull_request.labels.*.name, 'Semver-Minor') && + !contains(github.event.pull_request.labels.*.name, 'Semver-Docs') && + !contains(github.event.pull_request.labels.*.name, 'Semver-Ignore') \ No newline at end of file diff --git a/template/.github/workflows/dependabot-prs.yml b/template/.github/workflows/dependabot-prs.yml new file mode 100644 index 0000000..b87d115 --- /dev/null +++ b/template/.github/workflows/dependabot-prs.yml @@ -0,0 +1,19 @@ +name: Add dependabot PRs to flash reviews +on: + pull_request: + types: + - opened + - reopened + - labeled + +jobs: + add_flash_review: + name: Add dependabot PRs to flash reviews + runs-on: ubuntu-latest + steps: + - uses: actions/add-to-project@v2.0.0 + with: + project-url: https://github.com/orgs/ISISComputingGroup/projects/17 + github-token: ${{ secrets.PROJECT_TOKEN }} + labeled: dependencies + label-operator: OR \ No newline at end of file diff --git a/template/.github/workflows/documentation.yml b/template/.github/workflows/documentation.yml new file mode 100644 index 0000000..3a0b927 --- /dev/null +++ b/template/.github/workflows/documentation.yml @@ -0,0 +1,38 @@ +name: sphinx + +on: [push, workflow_call] + +jobs: + docs: + permissions: + contents: write + runs-on: windows-latest + steps: + - uses: actions/checkout@v7 + with: + fetch-depth: 0 + - uses: astral-sh/setup-uv@v7 + with: + python-version: "3.14" + - name: install requirements + run: uv sync --extra doc + - name: Sphinx build + run: uv run sphinx-build -E -a -W --keep-going doc _build + - name: run spellcheck + run: uv run sphinx-build -E -a -W --keep-going -b spelling doc _build + - name: Upload artifact + uses: actions/upload-artifact@v7 + with: + name: documentation + path: | + _build + if-no-files-found: error + retention-days: 7 + - name: Deploy to GitHub Pages + uses: peaceiris/actions-gh-pages@v3 + if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }} + with: + publish_branch: gh-pages + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: _build/ + force_orphan: true \ No newline at end of file diff --git a/template/.github/workflows/lint-and-test-nightly.yml b/template/.github/workflows/lint-and-test-nightly.yml new file mode 100644 index 0000000..cd110e7 --- /dev/null +++ b/template/.github/workflows/lint-and-test-nightly.yml @@ -0,0 +1,9 @@ +name: lint-and-test-nightly +on: + schedule: + - cron: "18 23 * * *" + workflow_dispatch: + +jobs: + lint-and-test-nightly: + uses: ./.github/workflows/Lint-and-test.yml \ No newline at end of file diff --git a/template/.github/workflows/release.yml.jinja b/template/.github/workflows/release.yml.jinja new file mode 100644 index 0000000..2bfcd86 --- /dev/null +++ b/template/.github/workflows/release.yml.jinja @@ -0,0 +1,92 @@ +name: Create release +on: push +jobs: + lint-and-test: + if: github.ref_type == 'tag' + name: Run linter and tests + uses: ./.github/workflows/Lint-and-test.yml + build: + needs: lint-and-test + if: github.ref_type == 'tag' + name: build distribution + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v7 + with: + fetch-depth: 0 + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: "3.14" + - name: Install pypa/build + run: >- + python3 -m + pip install + build + --user + - name: Build a binary wheel and a source tarball + run: python3 -m build + - name: Store the distribution packages + uses: actions/upload-artifact@v7 + with: + name: python-package-distributions + path: dist/ + {% if publish_to_pypi %} + publish-to-pypi: + name: >- + Publish Python distribution to PyPI + if: github.ref_type == 'tag' + needs: [ lint-and-test, build ] + runs-on: ubuntu-latest + environment: + name: release + url: https://pypi.org/p/{{ module_name }} + permissions: + id-token: write # IMPORTANT: mandatory for trusted publishing + steps: + - name: Download all the dists + uses: actions/download-artifact@v8 + with: + name: python-package-distributions + path: dist/ + - name: Publish distribution to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + {% endif %} + github-release: + name: >- + Sign the Python distribution with Sigstore + and upload them to GitHub Release + needs: [lint-and-test, build] + runs-on: ubuntu-latest + permissions: + contents: write # IMPORTANT: mandatory for making GitHub Releases + id-token: write # IMPORTANT: mandatory for sigstore + steps: + - name: Download all the dists + uses: actions/download-artifact@v8 + with: + name: python-package-distributions + path: dist/ + - name: Sign the dists with Sigstore + uses: sigstore/gh-action-sigstore-python@v3.5.0 + with: + inputs: >- + ./dist/*.tar.gz + ./dist/*.whl + - name: Create GitHub Release + uses: softprops/action-gh-release@v3 + with: + generate_release_notes: true + env: + GITHUB_TOKEN: ${{ '{{' }} github.token {{ '}}' }} + - name: Upload artifact signatures to GitHub Release + env: + GITHUB_TOKEN: ${{ '{{' }} github.token {{ '}}' }} + # Upload to GitHub Release using the `gh` CLI. + # `dist/` contains the built packages, and the + # sigstore-produced signatures and certificates. + run: >- + gh release upload + '${{ '{{' }} github.ref_name {{ '}}' }}' dist/** + --repo '${{ '{{' }} github.repository {{ '}}' }}' \ No newline at end of file diff --git a/template/.gitignore.jinja b/template/.gitignore.jinja new file mode 100644 index 0000000..ec59cb7 --- /dev/null +++ b/template/.gitignore.jinja @@ -0,0 +1,166 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/latest/usage/project/#working-with-version-control +.pdm.toml +.pdm-python +.pdm-build/ + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +.idea/ +.vscode/ + +coverage_html_report/ + +# Sphinx generated +doc/generated/* +_build/ + +src/{{ module_name }}/version.py diff --git a/template/LICENSE b/template/LICENSE new file mode 100644 index 0000000..b44c480 --- /dev/null +++ b/template/LICENSE @@ -0,0 +1,28 @@ +BSD 3-Clause License + +Copyright (c) 2026, ISIS Experiment Controls Computing + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/template/README.md.jinja b/template/README.md.jinja new file mode 100644 index 0000000..7f09ffa --- /dev/null +++ b/template/README.md.jinja @@ -0,0 +1,3 @@ +# {{ project_name }} + +{{ project_short_description }} diff --git a/template/doc/_api.rst.jinja b/template/doc/_api.rst.jinja new file mode 100644 index 0000000..39ed62d --- /dev/null +++ b/template/doc/_api.rst.jinja @@ -0,0 +1,11 @@ +:orphan: + +API +=== + +.. autosummary:: + :toctree: generated + :template: custom-module-template.rst + :recursive: + + {{ module_name }} diff --git a/template/doc/_static/css/custom.css.jinja b/template/doc/_static/css/custom.css.jinja new file mode 100644 index 0000000..3f91146 --- /dev/null +++ b/template/doc/_static/css/custom.css.jinja @@ -0,0 +1,16 @@ +a:hover { + color: {{ doc_theme_colour }}; +} + +.wy-menu-vertical p.caption { + color: {{ doc_theme_colour }}; +} + +.sphinx-codeautolink-a{ + border-bottom-color: {{ doc_theme_colour }}; + border-bottom-style: solid; + border-bottom-width: 1px; +} +.sphinx-codeautolink-a:hover{ + color: #888888; +} \ No newline at end of file diff --git a/template/doc/_templates/custom-module-template.rst b/template/doc/_templates/custom-module-template.rst new file mode 100644 index 0000000..f483c41 --- /dev/null +++ b/template/doc/_templates/custom-module-template.rst @@ -0,0 +1,40 @@ + + +{{ ('``' + fullname + '``') | underline }} + +{%- set filtered_members = [] %} +{%- for item in members %} + {%- if item in functions + classes + exceptions + attributes %} + {% set _ = filtered_members.append(item) %} + {%- endif %} +{%- endfor %} + +.. automodule:: {{ fullname }} + :members: + :show-inheritance: + + {% block modules %} + {% if modules %} + .. rubric:: Submodules + + .. autosummary:: + :toctree: + :template: custom-module-template.rst + :recursive: + {% for item in modules %} + {{ item }} + {%- endfor %} + {% endif %} + {% endblock %} + + {% block members %} + {% if filtered_members %} + .. rubric:: Members + + .. autosummary:: + :nosignatures: + {% for item in filtered_members %} + {{ item }} + {%- endfor %} + {% endif %} + {% endblock %} \ No newline at end of file diff --git a/template/doc/conf.py.jinja b/template/doc/conf.py.jinja new file mode 100644 index 0000000..f21292a --- /dev/null +++ b/template/doc/conf.py.jinja @@ -0,0 +1,91 @@ +# Configuration file for the Sphinx documentation builder. +# +# For the full list of built-in configuration values, see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Project information ----------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information + +import os +import sys + +sys.path.insert(0, os.path.abspath("../src")) + +from {{ module_name}}.version import version + +project = "{{ project_name }}" +copyright = "" +author = "ISIS Experiment Controls" +release = version + +# -- General configuration --------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration + +nitpicky = True +nitpick_ignore_regex = [ + ("py:class", r"^.*\.T$"), + ("py:obj", r"^.*\.T$"), + ("py:class", r"^.*\.T.*_co$"), + ("py:obj", r"^.*\.T.*_co$"), +] + +myst_enable_extensions = ["dollarmath", "strikethrough", "colon_fence", "attrs_block"] +suppress_warnings = ["myst.strikethrough"] + +extensions = [ + "myst_parser", + "sphinx.ext.autodoc", + # and making summary tables at the top of API docs + "sphinx.ext.autosummary", + # This can parse google style docstrings + "sphinx.ext.napoleon", + # For linking to external sphinx documentation + "sphinx.ext.intersphinx", + # Add links to source code in API docs + "sphinx.ext.viewcode", + # Mermaid diagrams + "sphinxcontrib.mermaid", + # Documentation links in code blocks + "sphinx_codeautolink", +] +mermaid_d3_zoom = True +napoleon_google_docstring = True +napoleon_numpy_docstring = False + +templates_path = ["_templates"] +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] + +# -- Options for HTML output ------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output + +html_context = { + "display_github": True, # Integrate GitHub + "github_user": "{{ github_org_name }}", # Username + "github_repo": "{{ github_repo_name }}", # Repo name + "github_version": "main", # Version + "conf_py_path": "/doc/", # Path in the checkout to the docs root +} + +html_theme = "sphinx_rtd_theme" +html_theme_options = { + "logo_only": False, + "style_nav_header_background": "#343131", +} +html_static_path = ["_static"] +html_css_files = [ + "css/custom.css", +] + +autoclass_content = "init" +myst_heading_anchors = 7 +autodoc_preserve_defaults = True + +spelling_lang = "en_GB" +spelling_filters = ["enchant.tokenize.MentionFilter"] +spelling_warning = True +spelling_show_suggestions = True +spelling_suggestion_limit = 3 + +intersphinx_mapping = { + "python": ("https://docs.python.org/3", None), +} diff --git a/template/doc/index.md.jinja b/template/doc/index.md.jinja new file mode 100644 index 0000000..d25298e --- /dev/null +++ b/template/doc/index.md.jinja @@ -0,0 +1,9 @@ +# {{ project_name }} + +```{toctree} +:titlesonly: +:caption: Reference +:glob: + +_api +``` \ No newline at end of file diff --git a/template/doc/spelling_wordlist.txt b/template/doc/spelling_wordlist.txt new file mode 100644 index 0000000..e69de29 diff --git a/template/pyproject.toml.jinja b/template/pyproject.toml.jinja new file mode 100644 index 0000000..fc401c3 --- /dev/null +++ b/template/pyproject.toml.jinja @@ -0,0 +1,99 @@ +[build-system] +requires = ["setuptools", "setuptools_scm>=8"] +build-backend = "setuptools.build_meta" + +[project] +name = "{{ module_name }}" # REQUIRED, is the only field that cannot be marked as dynamic. +dynamic = ["version"] +description = "{{ project_short_description }}" +readme = "README.md" +license-files = ["LICENSE"] + +authors = [ + {name = "ISIS Experiment Controls", email = "ISISExperimentControls@stfc.ac.uk" } +] +maintainers = [ + {name = "ISIS Experiment Controls", email = "ISISExperimentControls@stfc.ac.uk" } +] + +classifiers = [ + "Development Status :: 3 - Alpha", + "Intended Audience :: Developers", + + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3 :: Only", +] + +dependencies = [] + +[project.optional-dependencies] +doc = [ + "sphinx", + "sphinx_rtd_theme", + "myst_parser", + "sphinx-autobuild", + "sphinx-codeautolink", + "sphinxcontrib-mermaid", + "sphinxcontrib-spelling", +] +lint = [ + "pyright==1.1.414", + "ruff==0.16.9", +] +test = [ + "pytest", + "pytest-asyncio", + "pytest-cov", +] +dev = [ + "{{ module_name }}[doc,lint,test]", +] + +[project.urls] +"Homepage" = "https://github.com/{{ github_org_name }}/{{ github_repo_name }}" +"Bug Reports" = "https://github.com/{{ github_org_name }}/{{ github_repo_name }}/issues" +"Source" = "https://github.com/{{ github_org_name }}/{{ github_repo_name }}" + +[tool.pytest.ini_options] +testpaths = "tests" +asyncio_mode = "auto" +addopts = "--cov --cov-report=html -vv" + +[tool.coverage.run] +branch = true +source = ["src"] +omit = [ + "version.py", # Autogenerated +] + +[tool.coverage.report] +fail_under = 100 +exclude_lines = [ + "pragma: no cover", + "if TYPE_CHECKING:", + "if typing.TYPE_CHECKING:", + "@abstractmethod", +] + +[tool.coverage.html] +directory = "coverage_html_report" + +[tool.pyright] +include = ["src", "tests"] +reportConstantRedefinition = true +reportDeprecated = true +reportInconsistentConstructor = true +reportMissingParameterType = true +reportMissingTypeArgument = true +reportUnnecessaryCast = true +reportUnnecessaryComparison = true +reportUnnecessaryContains = true +reportUnnecessaryIsInstance = true +reportUntypedBaseClass = true +reportUntypedClassDecorator = true +reportUntypedFunctionDecorator = true + +[tool.setuptools_scm] +version_file = "src/{{ module_name }}/version.py" + +[tool.build_sphinx] \ No newline at end of file diff --git a/template/ruff.toml b/template/ruff.toml new file mode 100644 index 0000000..2ae77ac --- /dev/null +++ b/template/ruff.toml @@ -0,0 +1,50 @@ +line-length = 100 +indent-width = 4 + +[lint] +preview = true +extend-select = [ + "N", # pep8-naming + "D", # pydocstyle + "I", # isort (for imports) + "line-too-long", # Line too long ({width} > {limit}) + "E", # Pycodestyle errors + "W", # Pycodestyle warnings + "F", # Pyflakes + "PL", # Pylint + "B", # Flake8-bugbear + "PIE", # Flake8-pie + "ANN", # Annotations + "ASYNC", # Asyncio-specific checks + "NPY", # Numpy-specific rules + "RUF", # Ruff-specific checks, include some useful asyncio rules + "FURB", # Rules from refurb + "ERA", # Commented-out code + "PT", # Pytest-specific rules + "LOG", # Logging-specific rules + "G", # Logging-specific rules + "UP", # Pyupgrade + "SLF", # Private-member usage + "PERF", # Performance-related rules +] +ignore = [ + "missing-new-line-after-section-name", # Section name should end with a newline ("{name}") + "missing-dashed-underline-after-section", # Missing dashed underline after section ("{name}") + "multi-line-summary-second-line", # Incompatible with D212 + "incorrect-blank-line-before-class", # Incompatible with D211 + "no-self-use", # Too noisy +] +[lint.per-file-ignores] +"tests/**" = [ + "invalid-function-name", # Allow test names to be long / not pep8 + "D", # Don't require method documentation for test methods + "ANN", # Don't require tests to use type annotations + "magic-value-comparison", # Allow magic numbers in tests + "too-many-statements", # Allow complex tests + "too-many-locals", # Allow complex tests + "import-private-name", # Allow tests to import "private" things + "private-member-access", # Allow tests to use "private" things +] +"doc/conf.py" = [ + "undocumented-public-module" +] diff --git a/template/src/{{module_name}}/__init__.py.jinja b/template/src/{{module_name}}/__init__.py.jinja new file mode 100644 index 0000000..aba8a86 --- /dev/null +++ b/template/src/{{module_name}}/__init__.py.jinja @@ -0,0 +1 @@ +"""{{ project_short_description }}""" diff --git a/template/src/{{module_name}}/py.typed b/template/src/{{module_name}}/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/template/tests/__init__.py b/template/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/template/tests/test_version.py.jinja b/template/tests/test_version.py.jinja new file mode 100644 index 0000000..c1f4b20 --- /dev/null +++ b/template/tests/test_version.py.jinja @@ -0,0 +1,5 @@ +from {{ module_name }}.version import version + + +def test_version_is_importable(): + assert version is not None diff --git a/template/{{ _copier_conf.answers_file }}.jinja b/template/{{ _copier_conf.answers_file }}.jinja new file mode 100644 index 0000000..88acac8 --- /dev/null +++ b/template/{{ _copier_conf.answers_file }}.jinja @@ -0,0 +1,2 @@ +# Changes here will be overwritten by Copier; NEVER EDIT MANUALLY +{{ _copier_answers|to_nice_yaml -}} \ No newline at end of file diff --git a/test_project.yml b/test_project.yml new file mode 100644 index 0000000..8b9dca5 --- /dev/null +++ b/test_project.yml @@ -0,0 +1,9 @@ +project_name: "LibFOO" +project_short_description: "Implements the FOO process for twiddling the bits." +module_name: "libfoo" +github_org_name: "ISISComputingGroup" +github_repo_name: "libfoo" +publish_to_pypi: false +doc_theme_colour: "#7D7D7D" +ci_os_platforms: ["ubuntu-latest"] +ci_python_versions: ["3.14"]