diff --git a/.copier-answers.yml b/.copier-answers.yml
new file mode 100644
index 0000000..262d972
--- /dev/null
+++ b/.copier-answers.yml
@@ -0,0 +1,14 @@
+# Changes here will be overwritten by Copier
+_commit: HEAD
+_src_path: ../python-copier-template
+author_email: tom.cobb@diamond.ac.uk
+author_name: Tom Cobb
+component_owner: group:default/sscc
+description: An expanded python-copier-template with all the options
+distribution_name: dls-python-copier-template-example
+docker: true
+docs_type: sphinx
+git_platform: github.com
+github_org: DiamondLightSource
+package_name: python_copier_template_example
+repo_name: python-copier-template-example
diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
new file mode 100644
index 0000000..44de8d3
--- /dev/null
+++ b/.devcontainer/devcontainer.json
@@ -0,0 +1,54 @@
+// For format details, see https://containers.dev/implementors/json_reference/
+{
+ "name": "Python 3 Developer Container",
+ "build": {
+ "dockerfile": "../Dockerfile",
+ "target": "build",
+ // Only upgrade pip, we will install the project below
+ "args": {
+ "PIP_OPTIONS": "--upgrade pip"
+ }
+ },
+ "remoteEnv": {
+ "DISPLAY": "${localEnv:DISPLAY}"
+ },
+ // Add the URLs of features you want added when the container is built.
+ "features": {
+ "ghcr.io/devcontainers/features/common-utils:1": {
+ "username": "none",
+ "upgradePackages": false
+ }
+ },
+ // Set *default* container specific settings.json values on container create.
+ "settings": {
+ "python.defaultInterpreterPath": "/venv/bin/python"
+ },
+ "customizations": {
+ "vscode": {
+ // Add the IDs of extensions you want installed when the container is created.
+ "extensions": [
+ "ms-python.python",
+ "tamasfe.even-better-toml",
+ "redhat.vscode-yaml",
+ "ryanluker.vscode-coverage-gutters"
+ ]
+ }
+ },
+ // Make sure the files we are mapping into the container exist on the host
+ "initializeCommand": "bash -c 'for i in $HOME/.inputrc; do [ -f $i ] || touch $i; done'",
+ "runArgs": [
+ "--net=host",
+ "--security-opt=label=type:container_runtime_t"
+ ],
+ "mounts": [
+ "source=${localEnv:HOME}/.ssh,target=/root/.ssh,type=bind",
+ "source=${localEnv:HOME}/.inputrc,target=/root/.inputrc,type=bind",
+ // map in home directory - not strictly necessary but useful
+ "source=${localEnv:HOME},target=${localEnv:HOME},type=bind,consistency=cached"
+ ],
+ // make the workspace folder the same inside and outside of the container
+ "workspaceMount": "source=${localWorkspaceFolder},target=${localWorkspaceFolder},type=bind",
+ "workspaceFolder": "${localWorkspaceFolder}",
+ // After the container is created, install the python project in editable form
+ "postCreateCommand": "pip install -e '.[dev]'"
+}
diff --git a/.github/CONTRIBUTING.rst b/.github/CONTRIBUTING.rst
new file mode 100644
index 0000000..e615df0
--- /dev/null
+++ b/.github/CONTRIBUTING.rst
@@ -0,0 +1,35 @@
+Contributing to the project
+===========================
+
+Contributions and issues are most welcome! All issues and pull requests are
+handled through GitHub_. Also, please check for any existing issues before
+filing a new one. If you have a great idea but it involves big changes, please
+file a ticket before making a pull request! We want to make sure you don't spend
+your time coding something that might not fit the scope of the project.
+
+.. _GitHub: https://github.com/DiamondLightSource/python-copier-template-example/issues
+
+Issue or Discussion?
+--------------------
+
+Github also offers discussions_ as a place to ask questions and share ideas. If
+your issue is open ended and it is not obvious when it can be "closed", please
+raise it as a discussion instead.
+
+.. _discussions: https://github.com/DiamondLightSource/python-copier-template-example/discussions
+
+Code coverage
+-------------
+
+While 100% code coverage does not make a library bug-free, it significantly
+reduces the number of easily caught bugs! Please make sure coverage remains the
+same or is improved by a pull request!
+
+Developer guide
+---------------
+
+The `Developer Guide`_ contains information on setting up a development
+environment, running the tests and what standards the code and documentation
+should follow.
+
+.. _Developer Guide: https://DiamondLightSource.github.io/python-copier-template-example/main/developer/how-to/contribute.html
diff --git a/.github/actions/install_requirements/action.yml b/.github/actions/install_requirements/action.yml
new file mode 100644
index 0000000..79d1a71
--- /dev/null
+++ b/.github/actions/install_requirements/action.yml
@@ -0,0 +1,60 @@
+name: Install requirements
+description: Run pip install with requirements and upload resulting requirements
+inputs:
+ requirements_file:
+ description: Name of requirements file to use and upload
+ required: true
+ install_options:
+ description: Parameters to pass to pip install
+ required: true
+ artifact_name:
+ description: A user friendly name to give the produced artifacts
+ required: true
+ python_version:
+ description: Python version to install
+ default: "3.x"
+
+runs:
+ using: composite
+
+ steps:
+ - name: Setup python
+ uses: actions/setup-python@v5
+ with:
+ python-version: ${{ inputs.python_version }}
+
+ - name: Pip install
+ run: |
+ touch ${{ inputs.requirements_file }}
+ # -c uses requirements.txt as constraints, see 'Validate requirements file'
+ pip install -c ${{ inputs.requirements_file }} ${{ inputs.install_options }}
+ shell: bash
+
+ - name: Create lockfile
+ run: |
+ mkdir -p lockfiles
+ pip freeze --exclude-editable > lockfiles/${{ inputs.requirements_file }}
+ # delete the self referencing line and make sure it isn't blank
+ sed -i'' -e '/file:/d' lockfiles/${{ inputs.requirements_file }}
+ shell: bash
+
+ - name: Upload lockfiles
+ uses: actions/upload-artifact@v4.0.0
+ with:
+ name: lockfiles-${{ inputs.python_version }}-${{ inputs.artifact_name }}-${{ github.sha }}
+ path: lockfiles
+
+ # This eliminates the class of problems where the requirements being given no
+ # longer match what the packages themselves dictate. E.g. In the rare instance
+ # where I install some-package which used to depend on vulnerable-dependency
+ # but now uses good-dependency (despite being nominally the same version)
+ # pip will install both if given a requirements file with -r
+ - name: If requirements file exists, check it matches pip installed packages
+ run: |
+ if [ -s ${{ inputs.requirements_file }} ]; then
+ if ! diff -u ${{ inputs.requirements_file }} lockfiles/${{ inputs.requirements_file }}; then
+ echo "Error: ${{ inputs.requirements_file }} need the above changes to be exhaustive"
+ exit 1
+ fi
+ fi
+ shell: bash
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..2d1af87
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,20 @@
+# To get started with Dependabot version updates, you'll need to specify which
+# package ecosystems to update and where the package manifests are located.
+# Please see the documentation for all configuration options:
+# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
+
+version: 2
+updates:
+ - package-ecosystem: "github-actions"
+ directory: "/"
+ schedule:
+ interval: "weekly"
+ groups:
+ github-artifacts:
+ patterns:
+ - actions/*-artifact
+
+ - package-ecosystem: "pip"
+ directory: "/"
+ schedule:
+ interval: "weekly"
diff --git a/.github/pages/index.html b/.github/pages/index.html
new file mode 100644
index 0000000..c495f39
--- /dev/null
+++ b/.github/pages/index.html
@@ -0,0 +1,11 @@
+
+
+
+
+ Redirecting to main branch
+
+
+
+
+
+
diff --git a/.github/pages/make_switcher.py b/.github/pages/make_switcher.py
new file mode 100755
index 0000000..ae227ab
--- /dev/null
+++ b/.github/pages/make_switcher.py
@@ -0,0 +1,99 @@
+import json
+import logging
+from argparse import ArgumentParser
+from pathlib import Path
+from subprocess import CalledProcessError, check_output
+from typing import List, Optional
+
+
+def report_output(stdout: bytes, label: str) -> List[str]:
+ ret = stdout.decode().strip().split("\n")
+ print(f"{label}: {ret}")
+ return ret
+
+
+def get_branch_contents(ref: str) -> List[str]:
+ """Get the list of directories in a branch."""
+ stdout = check_output(["git", "ls-tree", "-d", "--name-only", ref])
+ return report_output(stdout, "Branch contents")
+
+
+def get_sorted_tags_list() -> List[str]:
+ """Get a list of sorted tags in descending order from the repository."""
+ stdout = check_output(["git", "tag", "-l", "--sort=-v:refname"])
+ return report_output(stdout, "Tags list")
+
+
+def get_versions(ref: str, add: Optional[str], remove: Optional[str]) -> List[str]:
+ """Generate the file containing the list of all GitHub Pages builds."""
+ # Get the directories (i.e. builds) from the GitHub Pages branch
+ try:
+ builds = set(get_branch_contents(ref))
+ except CalledProcessError:
+ builds = set()
+ logging.warning(f"Cannot get {ref} contents")
+
+ # Add and remove from the list of builds
+ if add:
+ builds.add(add)
+ if remove:
+ assert remove in builds, f"Build '{remove}' not in {sorted(builds)}"
+ builds.remove(remove)
+
+ # Get a sorted list of tags
+ tags = get_sorted_tags_list()
+
+ # Make the sorted versions list from main branches and tags
+ versions: List[str] = []
+ for version in ["master", "main"] + tags:
+ if version in builds:
+ versions.append(version)
+ builds.remove(version)
+
+ # Add in anything that is left to the bottom
+ versions += sorted(builds)
+ print(f"Sorted versions: {versions}")
+ return versions
+
+
+def write_json(path: Path, repository: str, versions: str):
+ org, repo_name = repository.split("/")
+ struct = [
+ {"version": version, "url": f"https://{org}.github.io/{repo_name}/{version}/"}
+ for version in versions
+ ]
+ text = json.dumps(struct, indent=2)
+ print(f"JSON switcher:\n{text}")
+ path.write_text(text, encoding="utf-8")
+
+
+def main(args=None):
+ parser = ArgumentParser(
+ description="Make a versions.txt file from gh-pages directories"
+ )
+ parser.add_argument(
+ "--add",
+ help="Add this directory to the list of existing directories",
+ )
+ parser.add_argument(
+ "--remove",
+ help="Remove this directory from the list of existing directories",
+ )
+ parser.add_argument(
+ "repository",
+ help="The GitHub org and repository name: ORG/REPO",
+ )
+ parser.add_argument(
+ "output",
+ type=Path,
+ help="Path of write switcher.json to",
+ )
+ args = parser.parse_args(args)
+
+ # Write the versions file
+ versions = get_versions("origin/gh-pages", args.add, args.remove)
+ write_json(args.output, args.repository, versions)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/.github/workflows/code.yml b/.github/workflows/code.yml
new file mode 100644
index 0000000..8e3a270
--- /dev/null
+++ b/.github/workflows/code.yml
@@ -0,0 +1,243 @@
+name: Code CI
+
+on:
+ push:
+ pull_request:
+env:
+ # The target python version, which must match the Dockerfile version
+ CONTAINER_PYTHON: "3.11"
+ DIST_WHEEL_PATH: dist-${{ github.sha }}
+
+jobs:
+ lint:
+ # pull requests are a duplicate of a branch push if within the same repo.
+ if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Install python packages
+ uses: ./.github/actions/install_requirements
+ with:
+ requirements_file: requirements-dev-3.x.txt
+ install_options: -e .[dev]
+ artifact_name: lint
+
+ - name: Lint
+ run: tox -e pre-commit,mypy
+
+ test:
+ if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository
+ strategy:
+ fail-fast: false
+ matrix:
+ os: ["ubuntu-latest"] # can add windows-latest, macos-latest
+ python: ["3.8", "3.9", "3.10", "3.11"]
+ install: ["-e .[dev]"]
+ # Make one version be non-editable to test both paths of version code
+ include:
+ - os: "ubuntu-latest"
+ python: "3.7"
+ install: ".[dev]"
+
+ runs-on: ${{ matrix.os }}
+ env:
+ # https://github.com/pytest-dev/pytest/issues/2042
+ PY_IGNORE_IMPORTMISMATCH: "1"
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ # Need this to get version number from last tag
+ fetch-depth: 0
+
+ - name: Install python packages
+ uses: ./.github/actions/install_requirements
+ with:
+ python_version: ${{ matrix.python }}
+ requirements_file: requirements-test-${{ matrix.os }}-${{ matrix.python }}.txt
+ install_options: ${{ matrix.install }}
+ artifact_name: tests
+
+ - name: List dependency tree
+ run: pipdeptree
+
+ - name: Run tests
+ run: tox -e pytest
+
+ - name: Upload coverage to Codecov
+ uses: codecov/codecov-action@v3
+ with:
+ name: ${{ matrix.python }}/${{ matrix.os }}
+ files: cov.xml
+
+ dist:
+ if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository
+ runs-on: "ubuntu-latest"
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ # Need this to get version number from last tag
+ fetch-depth: 0
+
+ - name: Build sdist and wheel
+ run: |
+ export SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct) && \
+ pipx run build
+
+ - name: Upload sdist and wheel as artifacts
+ uses: actions/upload-artifact@v4.0.0
+ with:
+ name: ${{ env.DIST_WHEEL_PATH }}
+ path: dist
+
+ - name: Check for packaging errors
+ run: pipx run twine check --strict dist/*
+
+ - name: Install python packages
+ uses: ./.github/actions/install_requirements
+ with:
+ python_version: ${{env.CONTAINER_PYTHON}}
+ requirements_file: requirements.txt
+ install_options: dist/*.whl
+ artifact_name: dist
+
+ - name: Test module --version works using the installed wheel
+ # If more than one module in src/ replace with module name to test
+ run: python -m $(ls src | head -1) --version
+
+ container:
+ needs: [lint, dist, test]
+ runs-on: ubuntu-latest
+
+ permissions:
+ contents: read
+ packages: write
+
+ env:
+ TEST_TAG: "testing"
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ # image names must be all lower case
+ - name: Generate image repo name
+ run: echo IMAGE_REPOSITORY=ghcr.io/$(tr '[:upper:]' '[:lower:]' <<< "${{ github.repository }}") >> $GITHUB_ENV
+
+ - name: Set lockfile location in environment
+ run: |
+ echo "DIST_LOCKFILE_PATH=lockfiles-${{ env.CONTAINER_PYTHON }}-dist-${{ github.sha }}" >> $GITHUB_ENV
+
+ - name: Download wheel and lockfiles
+ uses: actions/download-artifact@v4.1.0
+ with:
+ path: artifacts/
+ pattern: "*dist*"
+
+ - name: Log in to GitHub Docker Registry
+ if: github.event_name != 'pull_request'
+ uses: docker/login-action@v3
+ with:
+ registry: ghcr.io
+ username: ${{ github.actor }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Set up Docker Buildx
+ id: buildx
+ uses: docker/setup-buildx-action@v3
+
+ - name: Build and export to Docker local cache
+ uses: docker/build-push-action@v5
+ with:
+ # Note build-args, context, file, and target must all match between this
+ # step and the later build-push-action, otherwise the second build-push-action
+ # will attempt to build the image again
+ build-args: |
+ PIP_OPTIONS=-r ${{ env.DIST_LOCKFILE_PATH }}/requirements.txt ${{ env.DIST_WHEEL_PATH }}/*.whl
+ context: artifacts/
+ file: ./Dockerfile
+ target: runtime
+ load: true
+ tags: ${{ env.TEST_TAG }}
+ # If you have a long docker build (2+ minutes), uncomment the
+ # following to turn on caching. For short build times this
+ # makes it a little slower
+ #cache-from: type=gha
+ #cache-to: type=gha,mode=max
+
+ - name: Test cli works in cached runtime image
+ run: docker run docker.io/library/${{ env.TEST_TAG }} --version
+
+ - name: Create tags for publishing image
+ id: meta
+ uses: docker/metadata-action@v5
+ with:
+ images: ${{ env.IMAGE_REPOSITORY }}
+ tags: |
+ type=ref,event=tag
+ type=raw,value=latest, enable=${{ github.ref_type == 'tag' }}
+ # type=edge,branch=main
+ # Add line above to generate image for every commit to given branch,
+ # and uncomment the end of if clause in next step
+
+ - name: Push cached image to container registry
+ if: github.ref_type == 'tag' # || github.ref_name == 'main'
+ uses: docker/build-push-action@v5
+ # This does not build the image again, it will find the image in the
+ # Docker cache and publish it
+ with:
+ # Note build-args, context, file, and target must all match between this
+ # step and the previous build-push-action, otherwise this step will
+ # attempt to build the image again
+ build-args: |
+ PIP_OPTIONS=-r ${{ env.DIST_LOCKFILE_PATH }}/requirements.txt ${{ env.DIST_WHEEL_PATH }}/*.whl
+ context: artifacts/
+ file: ./Dockerfile
+ target: runtime
+ push: true
+ tags: ${{ steps.meta.outputs.tags }}
+ labels: ${{ steps.meta.outputs.labels }}
+
+ release:
+ # upload to PyPI and make a release on every tag
+ needs: [lint, dist, test]
+ if: ${{ github.event_name == 'push' && github.ref_type == 'tag' }}
+ runs-on: ubuntu-latest
+ env:
+ HAS_PYPI_TOKEN: ${{ secrets.PYPI_TOKEN != '' }}
+
+ steps:
+ - name: Download wheel and lockfiles
+ uses: actions/download-artifact@v4.1.0
+ with:
+ path: artifacts/
+ pattern: "*dist*"
+
+ - name: Fixup blank lockfiles
+ # Github release artifacts can't be blank
+ run: for f in ${{ env.DIST_LOCKFILE_PATH }}/*; do [ -s $f ] || echo '# No requirements' >> $f; done
+
+ - name: Github Release
+ # We pin to the SHA, not the tag, for security reasons.
+ # https://docs.github.com/en/actions/learn-github-actions/security-hardening-for-github-actions#using-third-party-actions
+ uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v0.1.15
+ with:
+ prerelease: ${{ contains(github.ref_name, 'a') || contains(github.ref_name, 'b') || contains(github.ref_name, 'rc') }}
+ files: |
+ ${{ env.DIST_WHEEL_PATH }}/*
+ ${{ env.DIST_LOCKFILE_PATH }}/*
+ generate_release_notes: true
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Publish to PyPI
+ if: ${{ env.HAS_PYPI_TOKEN }}
+ uses: pypa/gh-action-pypi-publish@release/v1
+ with:
+ password: ${{ secrets.PYPI_TOKEN }}
diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
new file mode 100644
index 0000000..3c29ff9
--- /dev/null
+++ b/.github/workflows/docs.yml
@@ -0,0 +1,54 @@
+name: Docs CI
+
+on:
+ push:
+ pull_request:
+
+jobs:
+ docs:
+ if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Avoid git conflicts when tag and branch pushed at same time
+ if: startsWith(github.ref, 'refs/tags')
+ run: sleep 60
+
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ # Need this to get version number from last tag
+ fetch-depth: 0
+
+ - name: Install system packages
+ # Can delete this if you don't use graphviz in your docs
+ run: sudo apt-get install graphviz
+
+ - name: Install python packages
+ uses: ./.github/actions/install_requirements
+ with:
+ requirements_file: requirements-dev-3.x.txt
+ install_options: -e .[dev]
+ artifact_name: docs
+
+ - name: Build docs
+ run: tox -e docs
+
+ - name: Sanitize ref name for docs version
+ run: echo "DOCS_VERSION=${GITHUB_REF_NAME//[^A-Za-z0-9._-]/_}" >> $GITHUB_ENV
+
+ - name: Move to versioned directory
+ run: mv build/html .github/pages/$DOCS_VERSION
+
+ - name: Write switcher.json
+ run: python .github/pages/make_switcher.py --add $DOCS_VERSION ${{ github.repository }} .github/pages/switcher.json
+
+ - name: Publish Docs to gh-pages
+ if: github.event_name == 'push' && github.actor != 'dependabot[bot]'
+ # We pin to the SHA, not the tag, for security reasons.
+ # https://docs.github.com/en/actions/learn-github-actions/security-hardening-for-github-actions#using-third-party-actions
+ uses: peaceiris/actions-gh-pages@64b46b4226a4a12da2239ba3ea5aa73e3163c75b # v3.9.1
+ with:
+ github_token: ${{ secrets.GITHUB_TOKEN }}
+ publish_dir: .github/pages
+ keep_files: true
diff --git a/.github/workflows/docs_clean.yml b/.github/workflows/docs_clean.yml
new file mode 100644
index 0000000..e324640
--- /dev/null
+++ b/.github/workflows/docs_clean.yml
@@ -0,0 +1,43 @@
+name: Docs Cleanup CI
+
+# delete branch documentation when a branch is deleted
+# also allow manually deleting a documentation version
+on:
+ delete:
+ workflow_dispatch:
+ inputs:
+ version:
+ description: "documentation version to DELETE"
+ required: true
+ type: string
+
+jobs:
+ remove:
+ if: github.event.ref_type == 'branch' || github.event_name == 'workflow_dispatch'
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ ref: gh-pages
+
+ - name: removing documentation for branch ${{ github.event.ref }}
+ if: ${{ github.event_name != 'workflow_dispatch' }}
+ run: echo "REF_NAME=${{ github.event.ref }}" >> $GITHUB_ENV
+
+ - name: manually removing documentation version ${{ github.event.inputs.version }}
+ if: ${{ github.event_name == 'workflow_dispatch' }}
+ run: echo "REF_NAME=${{ github.event.inputs.version }}" >> $GITHUB_ENV
+
+ - name: Sanitize ref name for docs version
+ run: echo "DOCS_VERSION=${REF_NAME//[^A-Za-z0-9._-]/_}" >> $GITHUB_ENV
+
+ - name: update index and push changes
+ run: |
+ rm -r $DOCS_VERSION
+ python make_switcher.py --remove $DOCS_VERSION ${{ github.repository }} switcher.json
+ git config --global user.name 'GitHub Actions Docs Cleanup CI'
+ git config --global user.email 'GithubActionsCleanup@noreply.github.com'
+ git commit -am "Removing redundant docs version $DOCS_VERSION"
+ git push
diff --git a/.github/workflows/linkcheck.yml b/.github/workflows/linkcheck.yml
new file mode 100644
index 0000000..7f651a2
--- /dev/null
+++ b/.github/workflows/linkcheck.yml
@@ -0,0 +1,28 @@
+name: Link Check
+
+on:
+ workflow_dispatch:
+ schedule:
+ # Run weekly to check URL links still resolve
+ - cron: "0 8 * * WED"
+
+jobs:
+ docs:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Install python packages
+ uses: ./.github/actions/install_requirements
+ with:
+ requirements_file: requirements-dev-3.x.txt
+ install_options: -e .[dev]
+ artifact_name: link_check
+
+ - name: Check links
+ run: tox -e docs build -- -b linkcheck
+
+ - name: Keepalive Workflow
+ uses: gautamkrishnar/keepalive-workflow@v1
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..a37be99
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,71 @@
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+env/
+.venv
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+*.egg-info/
+.installed.cfg
+*.egg
+**/_version.py
+
+# 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/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+cov.xml
+.pytest_cache/
+.mypy_cache/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+target/
+
+# likely venv names
+.venv*
+venv*
+
+# further build artifacts
+lockfiles/
+
+# ruff cache
+.ruff_cache/
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644
index 0000000..5bc9f00
--- /dev/null
+++ b/.pre-commit-config.yaml
@@ -0,0 +1,23 @@
+repos:
+ - repo: https://github.com/pre-commit/pre-commit-hooks
+ rev: v4.4.0
+ hooks:
+ - id: check-added-large-files
+ - id: check-yaml
+ - id: check-merge-conflict
+
+ - repo: local
+ hooks:
+ - id: black
+ name: Run black
+ stages: [commit]
+ language: system
+ entry: black --check --diff
+ types: [python]
+
+ - id: ruff
+ name: Run ruff
+ stages: [commit]
+ language: system
+ entry: ruff
+ types: [python]
diff --git a/.vscode/extensions.json b/.vscode/extensions.json
new file mode 100644
index 0000000..a1227b3
--- /dev/null
+++ b/.vscode/extensions.json
@@ -0,0 +1,10 @@
+{
+ "recommendations": [
+ "ms-vscode-remote.remote-containers",
+ "ms-python.python",
+ "tamasfe.even-better-toml",
+ "redhat.vscode-yaml",
+ "ryanluker.vscode-coverage-gutters",
+ "charliermarsh.Ruff"
+ ]
+}
diff --git a/.vscode/launch.json b/.vscode/launch.json
new file mode 100644
index 0000000..3cda743
--- /dev/null
+++ b/.vscode/launch.json
@@ -0,0 +1,25 @@
+{
+ // Use IntelliSense to learn about possible attributes.
+ // Hover to view descriptions of existing attributes.
+ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "name": "Debug Unit Test",
+ "type": "python",
+ "request": "launch",
+ "justMyCode": false,
+ "program": "${file}",
+ "purpose": [
+ "debug-test"
+ ],
+ "console": "integratedTerminal",
+ "env": {
+ // The default config in pyproject.toml's "[tool.pytest.ini_options]" adds coverage.
+ // Cannot have coverage and debugging at the same time.
+ // https://github.com/microsoft/vscode-python/issues/693
+ "PYTEST_ADDOPTS": "--no-cov"
+ },
+ }
+ ]
+}
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..47fbecf
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,22 @@
+{
+ "python.linting.pylintEnabled": false,
+ "python.linting.flake8Enabled": false,
+ "python.linting.mypyEnabled": true,
+ "python.linting.enabled": true,
+ "python.testing.pytestArgs": [
+ "--cov=python_copier_template_example",
+ "--cov-report",
+ "xml:cov.xml"
+ ],
+ "python.testing.unittestEnabled": false,
+ "python.testing.pytestEnabled": true,
+ "python.formatting.provider": "black",
+ "python.languageServer": "Pylance",
+ "editor.formatOnSave": true,
+ "[python]": {
+ "editor.codeActionsOnSave": {
+ "source.fixAll.ruff": false,
+ "source.organizeImports.ruff": true
+ }
+ }
+}
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
new file mode 100644
index 0000000..c999e86
--- /dev/null
+++ b/.vscode/tasks.json
@@ -0,0 +1,16 @@
+// See https://go.microsoft.com/fwlink/?LinkId=733558
+// for the documentation about the tasks.json format
+{
+ "version": "2.0.0",
+ "tasks": [
+ {
+ "type": "shell",
+ "label": "Tests, lint and docs",
+ "command": "tox -p",
+ "options": {
+ "cwd": "${workspaceRoot}"
+ },
+ "problemMatcher": [],
+ }
+ ]
+}
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..96981c6
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,37 @@
+# This file is for use as a devcontainer and a runtime container
+#
+# The devcontainer should use the build target and run as root with podman
+# or docker with user namespaces.
+#
+FROM python:3.11 as build
+
+ARG PIP_OPTIONS=.
+
+# Add any system dependencies for the developer/build environment here e.g.
+# RUN apt-get update && apt-get upgrade -y && \
+# apt-get install -y --no-install-recommends \
+# desired-packages \
+# && rm -rf /var/lib/apt/lists/*
+
+# set up a virtual environment and put it in PATH
+RUN python -m venv /venv
+ENV PATH=/venv/bin:$PATH
+
+# Copy any required context for the pip install over
+COPY . /context
+WORKDIR /context
+
+# install python package into /venv
+RUN pip install ${PIP_OPTIONS}
+
+FROM python:3.11-slim as runtime
+
+# Add apt-get system dependecies for runtime here if needed
+
+# copy the virtual environment from the build stage and put it in PATH
+COPY --from=build /venv/ /venv/
+ENV PATH=/venv/bin:$PATH
+
+# change this entrypoint if it is not the same as the repo
+ENTRYPOINT ["python-copier-template-example"]
+CMD ["--version"]
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..8dada3e
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright {yyyy} {name of copyright owner}
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/README.rst b/README.rst
new file mode 100644
index 0000000..35d85b0
--- /dev/null
+++ b/README.rst
@@ -0,0 +1,55 @@
+python_copier_template_example
+===========================
+
+|code_ci| |docs_ci| |coverage| |pypi_version| |license|
+
+This is where you should write a short paragraph that describes what your module does,
+how it does it, and why people should use it.
+
+============== ==============================================================
+PyPI ``pip install dls-python-copier-template-example``
+Source code https://github.com/DiamondLightSource/python-copier-template-example
+Documentation https://DiamondLightSource.github.io/python-copier-template-example
+Releases https://github.com/DiamondLightSource/python-copier-template-example/releases
+============== ==============================================================
+
+This is where you should put some images or code snippets that illustrate
+some relevant examples. If it is a library then you might put some
+introductory code here:
+
+.. code-block:: python
+
+ from python_copier_template_example import __version__
+
+ print(f"Hello python_copier_template_example {__version__}")
+
+Or if it is a commandline tool then you might put some example commands here::
+
+ $ python -m python_copier_template_example --version
+
+.. |code_ci| image:: https://github.com/DiamondLightSource/python-copier-template-example/actions/workflows/code.yml/badge.svg?branch=main
+ :target: https://github.com/DiamondLightSource/python-copier-template-example/actions/workflows/code.yml
+ :alt: Code CI
+
+.. |docs_ci| image:: https://github.com/DiamondLightSource/python-copier-template-example/actions/workflows/docs.yml/badge.svg?branch=main
+ :target: https://github.com/DiamondLightSource/python-copier-template-example/actions/workflows/docs.yml
+ :alt: Docs CI
+
+.. |coverage| image:: https://codecov.io/gh/DiamondLightSource/python-copier-template-example/branch/main/graph/badge.svg
+ :target: https://codecov.io/gh/DiamondLightSource/python-copier-template-example
+ :alt: Test Coverage
+
+.. |pypi_version| image:: https://img.shields.io/pypi/v/dls-python-copier-template-example.svg
+ :target: https://pypi.org/project/dls-python-copier-template-example
+ :alt: Latest PyPI version
+
+.. |license| image:: https://img.shields.io/badge/License-Apache%202.0-blue.svg
+ :target: https://opensource.org/licenses/Apache-2.0
+ :alt: Apache License
+
+
+..
+ Anything below this line is used when viewing README.rst and will be replaced
+ when included in index.rst
+
+See https://DiamondLightSource.github.io/python-copier-template-example for more detailed documentation.
diff --git a/catalog-info.yaml b/catalog-info.yaml
new file mode 100644
index 0000000..d0bc3e2
--- /dev/null
+++ b/catalog-info.yaml
@@ -0,0 +1,10 @@
+apiVersion: backstage.io/v1alpha1
+kind: Component
+metadata:
+ name: dls-python-copier-template-example
+ title: python-copier-template-example
+ description: An expanded python-copier-template with all the options
+spec:
+ type: documentation
+ lifecycle: experimental
+ owner: group:default/sscc
\ No newline at end of file
diff --git a/docs/conf.py b/docs/conf.py
new file mode 100644
index 0000000..b9b2e96
--- /dev/null
+++ b/docs/conf.py
@@ -0,0 +1,195 @@
+# Configuration file for the Sphinx documentation builder.
+#
+# This file only contains a selection of the most common options. For a full
+# list see the documentation:
+# https://www.sphinx-doc.org/en/master/usage/configuration.html
+
+import sys
+from pathlib import Path
+from subprocess import check_output
+
+import requests
+
+import python_copier_template_example
+
+# -- General configuration ------------------------------------------------
+
+# General information about the project.
+project = "python-copier-template-example"
+
+# The full version, including alpha/beta/rc tags.
+release = python_copier_template_example.__version__
+
+# The short X.Y version.
+if "+" in release:
+ # Not on a tag, use branch name
+ root = Path(__file__).absolute().parent.parent
+ git_branch = check_output("git branch --show-current".split(), cwd=root)
+ version = git_branch.decode().strip()
+else:
+ version = release
+
+extensions = [
+ # Use this for generating API docs
+ "sphinx.ext.autodoc",
+ # 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",
+ # Adds the inheritance-diagram generation directive
+ "sphinx.ext.inheritance_diagram",
+ # Add a copy button to each code block
+ "sphinx_copybutton",
+ # For the card element
+ "sphinx_design",
+]
+
+# If true, Sphinx will warn about all references where the target cannot
+# be found.
+nitpicky = True
+
+# A list of (type, target) tuples (by default empty) that should be ignored when
+# generating warnings in "nitpicky mode". Note that type should include the
+# domain name if present. Example entries would be ('py:func', 'int') or
+# ('envvar', 'LD_LIBRARY_PATH').
+nitpick_ignore = [
+ ("py:class", "NoneType"),
+ ("py:class", "'str'"),
+ ("py:class", "'float'"),
+ ("py:class", "'int'"),
+ ("py:class", "'bool'"),
+ ("py:class", "'object'"),
+ ("py:class", "'id'"),
+ ("py:class", "typing_extensions.Literal"),
+]
+
+# Both the class’ and the __init__ method’s docstring are concatenated and
+# inserted into the main body of the autoclass directive
+autoclass_content = "both"
+
+# Order the members by the order they appear in the source code
+autodoc_member_order = "bysource"
+
+# Don't inherit docstrings from baseclasses
+autodoc_inherit_docstrings = False
+
+# Output graphviz directive produced images in a scalable format
+graphviz_output_format = "svg"
+
+# The name of a reST role (builtin or Sphinx extension) to use as the default
+# role, that is, for text marked up `like this`
+default_role = "any"
+
+# The suffix of source filenames.
+source_suffix = ".rst"
+
+# The master toctree document.
+master_doc = "index"
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+# These patterns also affect html_static_path and html_extra_path
+exclude_patterns = ["_build"]
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = "sphinx"
+
+# This means you can link things like `str` and `asyncio` to the relevant
+# docs in the python documentation.
+intersphinx_mapping = {"python": ("https://docs.python.org/3/", None)}
+
+# A dictionary of graphviz graph attributes for inheritance diagrams.
+inheritance_graph_attrs = {"rankdir": "TB"}
+
+# Common links that should be available on every page
+rst_epilog = """
+.. _Diamond Light Source: http://www.diamond.ac.uk
+.. _black: https://github.com/psf/black
+.. _ruff: https://beta.ruff.rs/docs/
+.. _mypy: http://mypy-lang.org/
+.. _pre-commit: https://pre-commit.com/
+"""
+
+# Ignore localhost links for periodic check that links in docs are valid
+linkcheck_ignore = [r"http://localhost:\d+/"]
+
+# Set copy-button to ignore python and bash prompts
+# https://sphinx-copybutton.readthedocs.io/en/latest/use.html#using-regexp-prompt-identifiers
+copybutton_prompt_text = r">>> |\.\.\. |\$ |In \[\d*\]: | {2,5}\.\.\.: | {5,8}: "
+copybutton_prompt_is_regexp = True
+
+# -- Options for HTML output -------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages. See the documentation for
+# a list of builtin themes.
+#
+html_theme = "pydata_sphinx_theme"
+github_repo = "python-copier-template-example"
+github_user = "DiamondLightSource"
+switcher_json = f"https://{github_user}.github.io/{github_repo}/switcher.json"
+switcher_exists = requests.get(switcher_json).ok
+if not switcher_exists:
+ print(
+ "*** Can't read version switcher, is GitHub pages enabled? \n"
+ " Once Docs CI job has successfully run once, set the "
+ "Github pages source branch to be 'gh-pages' at:\n"
+ f" https://github.com/{github_user}/{github_repo}/settings/pages",
+ file=sys.stderr,
+ )
+
+# Theme options for pydata_sphinx_theme
+# We don't check switcher because there are 3 possible states for a repo:
+# 1. New project, docs are not published so there is no switcher
+# 2. Existing project with latest skeleton, switcher exists and works
+# 3. Existing project with old skeleton that makes broken switcher,
+# switcher exists but is broken
+# Point 3 makes checking switcher difficult, because the updated skeleton
+# will fix the switcher at the end of the docs workflow, but never gets a chance
+# to complete as the docs build warns and fails.
+html_theme_options = {
+ "logo": {
+ "text": project,
+ },
+ "use_edit_page_button": True,
+ "github_url": f"https://github.com/{github_user}/{github_repo}",
+ "icon_links": [
+ {
+ "name": "PyPI",
+ "url": f"https://pypi.org/project/{project}",
+ "icon": "fas fa-cube",
+ }
+ ],
+ "switcher": {
+ "json_url": switcher_json,
+ "version_match": version,
+ },
+ "check_switcher": False,
+ "navbar_end": ["theme-switcher", "icon-links", "version-switcher"],
+ "external_links": [
+ {
+ "name": "Release Notes",
+ "url": f"https://github.com/{github_user}/{github_repo}/releases",
+ }
+ ],
+ "navigation_with_keys": False,
+}
+
+# A dictionary of values to pass into the template engine’s context for all pages
+html_context = {
+ "github_user": github_user,
+ "github_repo": project,
+ "github_version": version,
+ "doc_path": "docs",
+}
+
+# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
+html_show_sphinx = False
+
+# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
+html_show_copyright = False
+
+# Logo
+html_logo = "images/dls-logo.svg"
+html_favicon = "images/dls-favicon.ico"
diff --git a/docs/developer/explanations/decisions.rst b/docs/developer/explanations/decisions.rst
new file mode 100644
index 0000000..5841e6e
--- /dev/null
+++ b/docs/developer/explanations/decisions.rst
@@ -0,0 +1,17 @@
+.. This Source Code Form is subject to the terms of the Mozilla Public
+.. License, v. 2.0. If a copy of the MPL was not distributed with this
+.. file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+Architectural Decision Records
+==============================
+
+We record major architectural decisions in Architecture Decision Records (ADRs),
+as `described by Michael Nygard
+`_.
+Below is the list of our current ADRs.
+
+.. toctree::
+ :maxdepth: 1
+ :glob:
+
+ decisions/*
\ No newline at end of file
diff --git a/docs/developer/explanations/decisions/0001-record-architecture-decisions.rst b/docs/developer/explanations/decisions/0001-record-architecture-decisions.rst
new file mode 100644
index 0000000..b2d3d0f
--- /dev/null
+++ b/docs/developer/explanations/decisions/0001-record-architecture-decisions.rst
@@ -0,0 +1,26 @@
+1. Record architecture decisions
+================================
+
+Date: 2022-02-18
+
+Status
+------
+
+Accepted
+
+Context
+-------
+
+We need to record the architectural decisions made on this project.
+
+Decision
+--------
+
+We will use Architecture Decision Records, as `described by Michael Nygard
+`_.
+
+Consequences
+------------
+
+See Michael Nygard's article, linked above. To create new ADRs we will copy and
+paste from existing ones.
diff --git a/docs/developer/explanations/decisions/0002-switched-to-pip-skeleton.rst b/docs/developer/explanations/decisions/0002-switched-to-pip-skeleton.rst
new file mode 100644
index 0000000..33d5698
--- /dev/null
+++ b/docs/developer/explanations/decisions/0002-switched-to-pip-skeleton.rst
@@ -0,0 +1,35 @@
+2. Adopt python_copier_template for project structure
+=====================================================
+
+Date: 2022-02-18
+
+Status
+------
+
+Accepted
+
+Context
+-------
+
+We should use the following `python_copier_template `_.
+The template will ensure consistency in developer
+environments and package management.
+
+Decision
+--------
+
+We have switched to using the skeleton.
+
+Consequences
+------------
+
+This module will use a fixed set of tools as developed in python_copier_template
+and can pull from this template to update the packaging to the latest techniques.
+
+As such, the developer environment may have changed, the following could be
+different:
+
+- linting
+- formatting
+- pip venv setup
+- CI/CD
diff --git a/docs/developer/how-to/build-docs.rst b/docs/developer/how-to/build-docs.rst
new file mode 100644
index 0000000..11a5e63
--- /dev/null
+++ b/docs/developer/how-to/build-docs.rst
@@ -0,0 +1,38 @@
+Build the docs using sphinx
+===========================
+
+You can build the `sphinx`_ based docs from the project directory by running::
+
+ $ tox -e docs
+
+This will build the static docs on the ``docs`` directory, which includes API
+docs that pull in docstrings from the code.
+
+.. seealso::
+
+ `documentation_standards`
+
+The docs will be built into the ``build/html`` directory, and can be opened
+locally with a web browser::
+
+ $ firefox build/html/index.html
+
+Autobuild
+---------
+
+You can also run an autobuild process, which will watch your ``docs``
+directory for changes and rebuild whenever it sees changes, reloading any
+browsers watching the pages::
+
+ $ tox -e docs autobuild
+
+You can view the pages at localhost::
+
+ $ firefox http://localhost:8000
+
+If you are making changes to source code too, you can tell it to watch
+changes in this directory too::
+
+ $ tox -e docs autobuild -- --watch src
+
+.. _sphinx: https://www.sphinx-doc.org/
diff --git a/docs/developer/how-to/contribute.rst b/docs/developer/how-to/contribute.rst
new file mode 100644
index 0000000..65b992f
--- /dev/null
+++ b/docs/developer/how-to/contribute.rst
@@ -0,0 +1 @@
+.. include:: ../../../.github/CONTRIBUTING.rst
diff --git a/docs/developer/how-to/lint.rst b/docs/developer/how-to/lint.rst
new file mode 100644
index 0000000..2df258d
--- /dev/null
+++ b/docs/developer/how-to/lint.rst
@@ -0,0 +1,39 @@
+Run linting using pre-commit
+============================
+
+Code linting is handled by black_ and ruff_ run under pre-commit_.
+
+Running pre-commit
+------------------
+
+You can run the above checks on all files with this command::
+
+ $ tox -e pre-commit
+
+Or you can install a pre-commit hook that will run each time you do a ``git
+commit`` on just the files that have changed::
+
+ $ pre-commit install
+
+It is also possible to `automatically enable pre-commit on cloned repositories `_.
+This will result in pre-commits being enabled on every repo your user clones from now on.
+
+Fixing issues
+-------------
+
+If black reports an issue you can tell it to reformat all the files in the
+repository::
+
+ $ black .
+
+Likewise with ruff::
+
+ $ ruff --fix .
+
+Ruff may not be able to automatically fix all issues; in this case, you will have to fix those manually.
+
+VSCode support
+--------------
+
+The ``.vscode/settings.json`` will run black formatting as well as
+ruff checking on save. Issues will be highlighted in the editor window.
diff --git a/docs/developer/how-to/make-release.rst b/docs/developer/how-to/make-release.rst
new file mode 100644
index 0000000..cab5792
--- /dev/null
+++ b/docs/developer/how-to/make-release.rst
@@ -0,0 +1,16 @@
+Make a release
+==============
+
+To make a new release, please follow this checklist:
+
+- Choose a new PEP440 compliant release number (see https://peps.python.org/pep-0440/)
+- Go to the GitHub release_ page
+- Choose ``Draft New Release``
+- Click ``Choose Tag`` and supply the new tag you chose (click create new tag)
+- Click ``Generate release notes``, review and edit these notes
+- Choose a title and click ``Publish Release``
+
+Note that tagging and pushing to the main branch has the same effect except that
+you will not get the option to edit the release notes.
+
+.. _release: https://github.com/DiamondLightSource/python-copier-template-example/releases
diff --git a/docs/developer/how-to/pin-requirements.rst b/docs/developer/how-to/pin-requirements.rst
new file mode 100644
index 0000000..8963962
--- /dev/null
+++ b/docs/developer/how-to/pin-requirements.rst
@@ -0,0 +1,74 @@
+Pinning Requirements
+====================
+
+Introduction
+------------
+
+By design this project only defines dependencies in one place, i.e. in
+the ``requires`` table in ``pyproject.toml``.
+
+In the ``requires`` table it is possible to pin versions of some dependencies
+as needed. For library projects it is best to leave pinning to a minimum so
+that your library can be used by the widest range of applications.
+
+When CI builds the project it will use the latest compatible set of
+dependencies available (after applying your pins and any dependencies' pins).
+
+This approach means that there is a possibility that a future build may
+break because an updated release of a dependency has made a breaking change.
+
+The correct way to fix such an issue is to work out the minimum pinning in
+``requires`` that will resolve the problem. However this can be quite hard to
+do and may be time consuming when simply trying to release a minor update.
+
+For this reason we provide a mechanism for locking all dependencies to
+the same version as a previous successful release. This is a quick fix that
+should guarantee a successful CI build.
+
+Finding the lock files
+----------------------
+
+Every release of the project will have a set of requirements files published
+as release assets.
+
+For example take a look at the release page for python3-pip-skeleton-cli here:
+https://github.com/DiamondLightSource/python3-pip-skeleton-cli/releases/tag/3.3.0
+
+There is a list of requirements*.txt files showing as assets on the release.
+
+There is one file for each time the CI installed the project into a virtual
+environment. There are multiple of these as the CI creates a number of
+different environments.
+
+The files are created using ``pip freeze`` and will contain a full list
+of the dependencies and sub-dependencies with pinned versions.
+
+You can download any of these files by clicking on them. It is best to use
+the one that ran with the lowest Python version as this is more likely to
+be compatible with all the versions of Python in the test matrix.
+i.e. ``requirements-test-ubuntu-latest-3.8.txt`` in this example.
+
+Applying the lock file
+----------------------
+
+To apply a lockfile:
+
+- copy the requirements file you have downloaded to the root of your
+ repository
+- rename it to requirements.txt
+- commit it into the repo
+- push the changes
+
+The CI looks for a requirements.txt in the root and will pass it to pip
+when installing each of the test environments. pip will then install exactly
+the same set of packages as the previous release.
+
+Removing dependency locking from CI
+-----------------------------------
+
+Once the reasons for locking the build have been resolved it is a good idea
+to go back to an unlocked build. This is because you get an early indication
+of any incoming problems.
+
+To restore unlocked builds in CI simply remove requirements.txt from the root
+of the repo and push.
diff --git a/docs/developer/how-to/run-tests.rst b/docs/developer/how-to/run-tests.rst
new file mode 100644
index 0000000..d2e0364
--- /dev/null
+++ b/docs/developer/how-to/run-tests.rst
@@ -0,0 +1,12 @@
+Run the tests using pytest
+==========================
+
+Testing is done with pytest_. It will find functions in the project that `look
+like tests`_, and run them to check for errors. You can run it with::
+
+ $ tox -e pytest
+
+It will also report coverage to the commandline and to ``cov.xml``.
+
+.. _pytest: https://pytest.org/
+.. _look like tests: https://docs.pytest.org/explanation/goodpractices.html#test-discovery
diff --git a/docs/developer/how-to/static-analysis.rst b/docs/developer/how-to/static-analysis.rst
new file mode 100644
index 0000000..065920e
--- /dev/null
+++ b/docs/developer/how-to/static-analysis.rst
@@ -0,0 +1,8 @@
+Run static analysis using mypy
+==============================
+
+Static type analysis is done with mypy_. It checks type definition in source
+files without running them, and highlights potential issues where types do not
+match. You can run it with::
+
+ $ tox -e mypy
diff --git a/docs/developer/how-to/test-container.rst b/docs/developer/how-to/test-container.rst
new file mode 100644
index 0000000..a4a43a6
--- /dev/null
+++ b/docs/developer/how-to/test-container.rst
@@ -0,0 +1,25 @@
+Container Local Build and Test
+==============================
+
+CI builds a runtime container for the project. The local tests
+checks available via ``tox -p`` do not verify this because not
+all developers will have docker installed locally.
+
+If CI is failing to build the container, then it is best to fix and
+test the problem locally. This would require that you have docker
+or podman installed on your local workstation.
+
+In the following examples the command ``docker`` is interchangeable with
+``podman`` depending on which container cli you have installed.
+
+To build the container and call it ``test``::
+
+ cd
+ docker build -t test .
+
+To verify that the container runs::
+
+ docker run -it test --help
+
+You can pass any other command line parameters to your application
+instead of --help.
diff --git a/docs/developer/how-to/update-tools.rst b/docs/developer/how-to/update-tools.rst
new file mode 100644
index 0000000..c1075ee
--- /dev/null
+++ b/docs/developer/how-to/update-tools.rst
@@ -0,0 +1,16 @@
+Update the tools
+================
+
+This module is merged with the python3-pip-skeleton_. This is a generic
+Python project structure which provides a means to keep tools and
+techniques in sync between multiple Python projects. To update to the
+latest version of the skeleton, run::
+
+ $ git pull --rebase=false https://github.com/DiamondLightSource/python3-pip-skeleton
+
+Any merge conflicts will indicate an area where something has changed that
+conflicts with the setup of the current module. Check the `closed pull requests
+`_
+of the skeleton module for more details.
+
+.. _python3-pip-skeleton: https://DiamondLightSource.github.io/python3-pip-skeleton
diff --git a/docs/developer/index.rst b/docs/developer/index.rst
new file mode 100644
index 0000000..8a6369b
--- /dev/null
+++ b/docs/developer/index.rst
@@ -0,0 +1,64 @@
+Developer Guide
+===============
+
+Documentation is split into four categories, also accessible from links in the
+side-bar.
+
+.. grid:: 2
+ :gutter: 4
+
+ .. grid-item-card:: :material-regular:`directions_run;3em`
+
+ .. toctree::
+ :caption: Tutorials
+ :maxdepth: 1
+
+ tutorials/dev-install
+
+ +++
+
+ Tutorials for getting up and running as a developer.
+
+ .. grid-item-card:: :material-regular:`task;3em`
+
+ .. toctree::
+ :caption: How-to Guides
+ :maxdepth: 1
+
+ how-to/contribute
+ how-to/build-docs
+ how-to/run-tests
+ how-to/static-analysis
+ how-to/lint
+ how-to/update-tools
+ how-to/make-release
+ how-to/pin-requirements
+ how-to/test-container
+
+ +++
+
+ Practical step-by-step guides for day-to-day dev tasks.
+
+ .. grid-item-card:: :material-regular:`apartment;3em`
+
+ .. toctree::
+ :caption: Explanations
+ :maxdepth: 1
+
+ explanations/decisions
+
+ +++
+
+ Explanations of how and why the architecture is why it is.
+
+ .. grid-item-card:: :material-regular:`description;3em`
+
+ .. toctree::
+ :caption: Reference
+ :maxdepth: 1
+
+ reference/standards
+
+ +++
+
+ Technical reference material on standards in use.
diff --git a/docs/developer/reference/standards.rst b/docs/developer/reference/standards.rst
new file mode 100644
index 0000000..5a1fd47
--- /dev/null
+++ b/docs/developer/reference/standards.rst
@@ -0,0 +1,63 @@
+Standards
+=========
+
+This document defines the code and documentation standards used in this
+repository.
+
+Code Standards
+--------------
+
+The code in this repository conforms to standards set by the following tools:
+
+- black_ for code formatting
+- ruff_ for style checks
+- mypy_ for static type checking
+
+.. seealso::
+
+ How-to guides `../how-to/lint` and `../how-to/static-analysis`
+
+.. _documentation_standards:
+
+Documentation Standards
+-----------------------
+
+Docstrings are pre-processed using the Sphinx Napoleon extension. As such,
+google-style_ is considered as standard for this repository. Please use type
+hints in the function signature for types. For example:
+
+.. code:: python
+
+ def func(arg1: str, arg2: int) -> bool:
+ """Summary line.
+
+ Extended description of function.
+
+ Args:
+ arg1: Description of arg1
+ arg2: Description of arg2
+
+ Returns:
+ Description of return value
+ """
+ return True
+
+.. _google-style: https://sphinxcontrib-napoleon.readthedocs.io/en/latest/index.html#google-vs-numpy
+
+Documentation is contained in the ``docs`` directory and extracted from
+docstrings of the API.
+
+Docs follow the underlining convention::
+
+ Headling 1 (page title)
+ =======================
+
+ Heading 2
+ ---------
+
+ Heading 3
+ ~~~~~~~~~
+
+.. seealso::
+
+ How-to guide `../how-to/build-docs`
diff --git a/docs/developer/tutorials/dev-install.rst b/docs/developer/tutorials/dev-install.rst
new file mode 100644
index 0000000..4284c05
--- /dev/null
+++ b/docs/developer/tutorials/dev-install.rst
@@ -0,0 +1,68 @@
+Developer install
+=================
+
+These instructions will take you through the minimal steps required to get a dev
+environment setup, so you can run the tests locally.
+
+Clone the repository
+--------------------
+
+First clone the repository locally using `Git
+`_::
+
+ $ git clone git://github.com/DiamondLightSource/python-copier-template-example.git
+
+Install dependencies
+--------------------
+
+You can choose to either develop on the host machine using a `venv` (which
+requires python 3.8 or later) or to run in a container under `VSCode
+`_
+
+.. tab-set::
+
+ .. tab-item:: Local virtualenv
+
+ .. code::
+
+ $ cd python-copier-template-example
+ $ python3 -m venv venv
+ $ source venv/bin/activate
+ $ pip install -e '.[dev]'
+
+ .. tab-item:: VSCode devcontainer
+
+ .. code::
+
+ $ code python-copier-template-example
+ # Click on 'Reopen in Container' when prompted
+ # Open a new terminal
+
+ .. note::
+
+ See the epics-containers_ documentation for more complex
+ use cases, such as integration with podman.
+
+See what was installed
+----------------------
+
+To see a graph of the python package dependency tree type::
+
+ $ pipdeptree
+
+Build and test
+--------------
+
+Now you have a development environment you can run the tests in a terminal::
+
+ $ tox -p
+
+This will run in parallel the following checks:
+
+- `../how-to/build-docs`
+- `../how-to/run-tests`
+- `../how-to/static-analysis`
+- `../how-to/lint`
+
+
+.. _epics-containers: https://epics-containers.github.io/main/user/tutorials/devcontainer.html
diff --git a/docs/genindex.rst b/docs/genindex.rst
new file mode 100644
index 0000000..93eb8b2
--- /dev/null
+++ b/docs/genindex.rst
@@ -0,0 +1,5 @@
+API Index
+=========
+
+..
+ https://stackoverflow.com/a/42310803
diff --git a/docs/images/dls-favicon.ico b/docs/images/dls-favicon.ico
new file mode 100644
index 0000000..9a11f50
Binary files /dev/null and b/docs/images/dls-favicon.ico differ
diff --git a/docs/images/dls-logo.svg b/docs/images/dls-logo.svg
new file mode 100644
index 0000000..0af1a17
--- /dev/null
+++ b/docs/images/dls-logo.svg
@@ -0,0 +1,11 @@
+
+
\ No newline at end of file
diff --git a/docs/index.rst b/docs/index.rst
new file mode 100644
index 0000000..1b2528a
--- /dev/null
+++ b/docs/index.rst
@@ -0,0 +1,29 @@
+:html_theme.sidebar_secondary.remove:
+
+.. include:: ../README.rst
+ :end-before: when included in index.rst
+
+How the documentation is structured
+-----------------------------------
+
+The documentation is split into 2 sections:
+
+.. grid:: 2
+
+ .. grid-item-card:: :material-regular:`person;4em`
+ :link: user/index
+ :link-type: doc
+
+ The User Guide contains documentation on how to install and use python-copier-template-example.
+
+ .. grid-item-card:: :material-regular:`code;4em`
+ :link: developer/index
+ :link-type: doc
+
+ The Developer Guide contains documentation on how to develop and contribute changes back to python-copier-template-example.
+
+.. toctree::
+ :hidden:
+
+ user/index
+ developer/index
diff --git a/docs/user/explanations/docs-structure.rst b/docs/user/explanations/docs-structure.rst
new file mode 100644
index 0000000..f25a09b
--- /dev/null
+++ b/docs/user/explanations/docs-structure.rst
@@ -0,0 +1,18 @@
+About the documentation
+-----------------------
+
+ :material-regular:`format_quote;2em`
+
+ The Grand Unified Theory of Documentation
+
+ -- David Laing
+
+There is a secret that needs to be understood in order to write good software
+documentation: there isn't one thing called *documentation*, there are four.
+
+They are: *tutorials*, *how-to guides*, *technical reference* and *explanation*.
+They represent four different purposes or functions, and require four different
+approaches to their creation. Understanding the implications of this will help
+improve most documentation - often immensely.
+
+`More information on this topic. `_
diff --git a/docs/user/how-to/run-container.rst b/docs/user/how-to/run-container.rst
new file mode 100644
index 0000000..5910118
--- /dev/null
+++ b/docs/user/how-to/run-container.rst
@@ -0,0 +1,15 @@
+Run in a container
+==================
+
+Pre-built containers with python-copier-template-example and its dependencies already
+installed are available on `Github Container Registry
+`_.
+
+Starting the container
+----------------------
+
+To pull the container from github container registry and run::
+
+ $ docker run ghcr.io/DiamondLightSource/python-copier-template-example:main --version
+
+To get a released version, use a numbered release instead of ``main``.
diff --git a/docs/user/index.rst b/docs/user/index.rst
new file mode 100644
index 0000000..2c94a0c
--- /dev/null
+++ b/docs/user/index.rst
@@ -0,0 +1,57 @@
+User Guide
+==========
+
+Documentation is split into four categories, also accessible from links in the
+side-bar.
+
+.. grid:: 2
+ :gutter: 4
+
+ .. grid-item-card:: :material-regular:`directions_walk;3em`
+
+ .. toctree::
+ :caption: Tutorials
+ :maxdepth: 1
+
+ tutorials/installation
+
+ +++
+
+ Tutorials for installation and typical usage. New users start here.
+
+ .. grid-item-card:: :material-regular:`directions;3em`
+
+ .. toctree::
+ :caption: How-to Guides
+ :maxdepth: 1
+
+ how-to/run-container
+
+ +++
+
+ Practical step-by-step guides for the more experienced user.
+
+ .. grid-item-card:: :material-regular:`info;3em`
+
+ .. toctree::
+ :caption: Explanations
+ :maxdepth: 1
+
+ explanations/docs-structure
+
+ +++
+
+ Explanations of how the library works and why it works that way.
+
+ .. grid-item-card:: :material-regular:`menu_book;3em`
+
+ .. toctree::
+ :caption: Reference
+ :maxdepth: 1
+
+ reference/api
+ ../genindex
+
+ +++
+
+ Technical reference material including APIs and release notes.
diff --git a/docs/user/reference/api.rst b/docs/user/reference/api.rst
new file mode 100644
index 0000000..e821325
--- /dev/null
+++ b/docs/user/reference/api.rst
@@ -0,0 +1,14 @@
+API
+===
+
+.. automodule:: python_copier_template_example
+
+ ``python_copier_template_example``
+ -----------------------------------
+
+This is the internal API reference for python_copier_template_example
+
+.. data:: python_copier_template_example.__version__
+ :type: str
+
+ Version number as calculated by https://github.com/pypa/setuptools_scm
diff --git a/docs/user/tutorials/installation.rst b/docs/user/tutorials/installation.rst
new file mode 100644
index 0000000..074e195
--- /dev/null
+++ b/docs/user/tutorials/installation.rst
@@ -0,0 +1,38 @@
+Installation
+============
+
+Check your version of python
+----------------------------
+
+You will need python 3.8 or later. You can check your version of python by
+typing into a terminal::
+
+ $ python3 --version
+
+
+Create a virtual environment
+----------------------------
+
+It is recommended that you install into a “virtual environment” so this
+installation will not interfere with any existing Python software::
+
+ $ python3 -m venv /path/to/venv
+ $ source /path/to/venv/bin/activate
+
+
+Installing the library
+----------------------
+
+You can now use ``pip`` to install the library and its dependencies::
+
+ $ python3 -m pip install dls-python-copier-template-example
+
+If you require a feature that is not currently released you can also install
+from github::
+
+ $ python3 -m pip install git+https://github.com/DiamondLightSource/python-copier-template-example.git
+
+The library should now be installed and the commandline interface on your path.
+You can check the version that has been installed by typing::
+
+ $ python-copier-template-example --version
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..41546bb
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,110 @@
+[build-system]
+requires = ["setuptools>=64", "setuptools_scm[toml]>=6.2", "wheel"]
+build-backend = "setuptools.build_meta"
+
+[project]
+name = "dls-python-copier-template-example"
+classifiers = [
+ "Development Status :: 3 - Alpha",
+ "License :: OSI Approved :: Apache Software License",
+ "Programming Language :: Python :: 3.7",
+ "Programming Language :: Python :: 3.8",
+ "Programming Language :: Python :: 3.9",
+ "Programming Language :: Python :: 3.10",
+ "Programming Language :: Python :: 3.11",
+]
+description = "An expanded python-copier-template with all the options"
+dependencies = [
+ "typing-extensions;python_version<'3.8'",
+] # Add project dependencies here, e.g. ["click", "numpy"]
+dynamic = ["version"]
+license.file = "LICENSE"
+readme = "README.rst"
+requires-python = ">=3.7"
+
+[project.optional-dependencies]
+dev = [
+ "black",
+ "mypy",
+ "pipdeptree",
+ "pre-commit",
+ "pydata-sphinx-theme>=0.12",
+ "pytest",
+ "pytest-cov",
+ "ruff",
+ "sphinx-autobuild",
+ "sphinx-copybutton",
+ "sphinx-design",
+ "tox-direct",
+ "types-mock",
+]
+
+[project.scripts]
+python-copier-template-example = "python_copier_template_example.__main__:main"
+
+[project.urls]
+GitHub = "https://github.com/DiamondLightSource/python-copier-template-example"
+
+[[project.authors]] # Further authors may be added by duplicating this section
+email = "tom.cobb@diamond.ac.uk"
+name = "Tom Cobb"
+
+
+[tool.setuptools_scm]
+write_to = "src/python_copier_template_example/_version.py"
+
+[tool.mypy]
+ignore_missing_imports = true # Ignore missing stubs in imported modules
+
+[tool.pytest.ini_options]
+# Run pytest with all our checkers, and don't spam us with massive tracebacks on error
+addopts = """
+ --tb=native -vv --doctest-modules --doctest-glob="*.rst"
+ """
+# https://iscinumpy.gitlab.io/post/bound-version-constraints/#watch-for-warnings
+filterwarnings = "error"
+# Doctest python code in docs, python code in src docstrings, test functions in tests
+testpaths = "docs src tests"
+
+[tool.coverage.run]
+data_file = "/tmp/python_copier_template_example.coverage"
+
+[tool.coverage.paths]
+# Tests are run from installed location, map back to the src directory
+source = ["src", "**/site-packages/"]
+
+# tox must currently be configured via an embedded ini string
+# See: https://github.com/tox-dev/tox/issues/999
+[tool.tox]
+legacy_tox_ini = """
+[tox]
+skipsdist=True
+
+[testenv:{pre-commit,mypy,pytest,docs}]
+# Don't create a virtualenv for the command, requires tox-direct plugin
+direct = True
+passenv = *
+allowlist_externals =
+ pytest
+ pre-commit
+ mypy
+ sphinx-build
+ sphinx-autobuild
+commands =
+ pytest: pytest --cov=python_copier_template_example --cov-report term --cov-report xml:cov.xml {posargs}
+ mypy: mypy src tests {posargs}
+ pre-commit: pre-commit run --all-files {posargs}
+ docs: sphinx-{posargs:build -EW --keep-going} -T docs build/html
+"""
+
+
+[tool.ruff]
+src = ["src", "tests"]
+line-length = 88
+select = [
+ "C4", # flake8-comprehensions - https://beta.ruff.rs/docs/rules/#flake8-comprehensions-c4
+ "E", # pycodestyle errors - https://beta.ruff.rs/docs/rules/#error-e
+ "F", # pyflakes rules - https://beta.ruff.rs/docs/rules/#pyflakes-f
+ "W", # pycodestyle warnings - https://beta.ruff.rs/docs/rules/#warning-w
+ "I001", # isort
+]
diff --git a/src/python_copier_template_example/__init__.py b/src/python_copier_template_example/__init__.py
new file mode 100644
index 0000000..b69c468
--- /dev/null
+++ b/src/python_copier_template_example/__init__.py
@@ -0,0 +1,11 @@
+import sys
+
+if sys.version_info < (3, 8):
+ from importlib_metadata import version # noqa
+else:
+ from importlib.metadata import version # noqa
+
+__version__ = version("dls-python-copier-template-example")
+del version
+
+__all__ = ["__version__"]
diff --git a/src/python_copier_template_example/__main__.py b/src/python_copier_template_example/__main__.py
new file mode 100644
index 0000000..fb2afb0
--- /dev/null
+++ b/src/python_copier_template_example/__main__.py
@@ -0,0 +1,16 @@
+from argparse import ArgumentParser
+
+from . import __version__
+
+__all__ = ["main"]
+
+
+def main(args=None):
+ parser = ArgumentParser()
+ parser.add_argument("-v", "--version", action="version", version=__version__)
+ args = parser.parse_args(args)
+
+
+# test with: python -m python_copier_template_example
+if __name__ == "__main__":
+ main()
diff --git a/tests/test_cli.py b/tests/test_cli.py
new file mode 100644
index 0000000..cd97017
--- /dev/null
+++ b/tests/test_cli.py
@@ -0,0 +1,9 @@
+import subprocess
+import sys
+
+from python_copier_template_example import __version__
+
+
+def test_cli_version():
+ cmd = [sys.executable, "-m", "python_copier_template_example", "--version"]
+ assert subprocess.check_output(cmd).decode().strip() == __version__