diff --git a/.copier-answers.yml b/.copier-answers.yml new file mode 100644 index 00000000..d4625839 --- /dev/null +++ b/.copier-answers.yml @@ -0,0 +1,14 @@ +# Changes here will be overwritten by Copier +_commit: 1.0.2-87-g16b7b0e +_src_path: gh:DiamondLightSource/python-copier-template +author_email: giles.knap@diamond.ac.uk +author_name: Giles Knap +component_owner: group:default/sscc +description: A GUI for low level interaction with turbo pmacs and power pmacs +distribution_name: dls-pmac-control +docker: true +docs_type: sphinx +git_platform: github.com +github_org: DiamondLightSource +package_name: dls_pmac_control +repo_name: dls-pmac-control diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 00000000..79b85ff4 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,46 @@ +// For format details, see https://containers.dev/implementors/json_reference/ +{ + "name": "Python 3 Developer Container", + "build": { + "dockerfile": "../Dockerfile", + "target": "developer" + }, + "remoteEnv": { + // Allow X11 apps to run inside the container + "DISPLAY": "${localEnv:DISPLAY}" + }, + "customizations": { + "vscode": { + // Set *default* container specific settings.json values on container create. + "settings": { + "python.defaultInterpreterPath": "/venv/bin/python" + }, + // Add the IDs of extensions you want installed when the container is created. + "extensions": [ + "ms-python.python", + "github.vscode-github-actions", + "tamasfe.even-better-toml", + "redhat.vscode-yaml", + "ryanluker.vscode-coverage-gutters", + "charliermarsh.ruff", + "ms-azuretools.vscode-docker" + ] + } + }, + "features": { + // Some default things like git config + "ghcr.io/devcontainers/features/common-utils:2": { + "upgradePackages": false + } + }, + "runArgs": [ + // Allow the container to access the host X11 display and EPICS CA + "--net=host", + // Make sure SELinux does not disable with access to host filesystems like tmp + "--security-opt=label=disable" + ], + // Mount the parent as /workspaces so we can pip install peers as editable + "workspaceMount": "source=${localWorkspaceFolder}/..,target=/workspaces,type=bind", + // After the container is created, install the python project in editable form + "postCreateCommand": "pip install $([ -f dev-requirements.txt ] && echo '-c dev-requirements.txt') -e '.[dev]' && pre-commit install" +} \ No newline at end of file diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 00000000..892c71ee --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,27 @@ +# Contribute to the project + +Contributions and issues are most welcome! All issues and pull requests are +handled through [GitHub](https://github.com/DiamondLightSource/dls-pmac-control/issues). 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. + +## Issue or Discussion? + +Github also offers [discussions](https://github.com/DiamondLightSource/dls-pmac-control/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. + +## 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 Information + +It is recommended that developers use a [vscode devcontainer](https://code.visualstudio.com/docs/devcontainers/containers). This repository contains configuration to set up a containerized development environment that suits its own needs. + +This project was created using the [Diamond Light Source Copier Template](https://github.com/DiamondLightSource/python-copier-template) for Python projects. +The template's [Developer Guide](https://diamondlightsource.github.io/python-copier-template) contains detailed information on setting up a development environment, running the tests and what standards the code and documentation +should follow. diff --git a/.github/actions/install_requirements/action.yml b/.github/actions/install_requirements/action.yml new file mode 100644 index 00000000..d33e0805 --- /dev/null +++ b/.github/actions/install_requirements/action.yml @@ -0,0 +1,34 @@ +name: Install requirements +description: Install a version of python then call pip install and report what was installed +inputs: + python-version: + description: Python version to install, default is from Dockerfile + default: "dev" + pip-install: + description: Parameters to pass to pip install + default: "$([ -f dev-requirements.txt ] && echo '-c dev-requirements.txt') -e .[dev]" + +runs: + using: composite + steps: + - name: Get version of python + run: | + PYTHON_VERSION="${{ inputs.python-version }}" + if [ $PYTHON_VERSION == "dev" ]; then + PYTHON_VERSION=$(sed -n "s/ARG PYTHON_VERSION=//p" Dockerfile) + fi + echo "PYTHON_VERSION=$PYTHON_VERSION" >> "$GITHUB_ENV" + shell: bash + + - name: Setup python + uses: actions/setup-python@v5 + with: + python-version: ${{ env.PYTHON_VERSION }} + + - name: Install packages + run: pip install ${{ inputs.pip-install }} + shell: bash + + - name: Report what was installed + run: pip freeze + shell: bash diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..184ba363 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,24 @@ +# 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: + actions: + patterns: + - "*" + + - package-ecosystem: "pip" + directory: "/" + schedule: + interval: "weekly" + groups: + dev-dependencies: + patterns: + - "*" diff --git a/.github/pages/index.html b/.github/pages/index.html new file mode 100644 index 00000000..80f0a009 --- /dev/null +++ b/.github/pages/index.html @@ -0,0 +1,11 @@ + + + + + Redirecting to main branch + + + + + + \ No newline at end of file diff --git a/.github/pages/make_switcher.py b/.github/pages/make_switcher.py new file mode 100755 index 00000000..e2c8e6f6 --- /dev/null +++ b/.github/pages/make_switcher.py @@ -0,0 +1,92 @@ +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]) -> 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) + + # 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.json file from gh-pages directories" + ) + parser.add_argument( + "--add", + help="Add this directory to 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) + write_json(args.output, args.repository, versions) + + +if __name__ == "__main__": + main() diff --git a/.github/workflows/_check.yml b/.github/workflows/_check.yml new file mode 100644 index 00000000..a6139c19 --- /dev/null +++ b/.github/workflows/_check.yml @@ -0,0 +1,27 @@ +on: + workflow_call: + outputs: + branch-pr: + description: The PR number if the branch is in one + value: ${{ jobs.pr.outputs.branch-pr }} + +jobs: + pr: + runs-on: "ubuntu-latest" + outputs: + branch-pr: ${{ steps.script.outputs.result }} + steps: + - uses: actions/github-script@v7 + id: script + if: github.event_name == 'push' + with: + script: | + const prs = await github.rest.pulls.list({ + owner: context.repo.owner, + repo: context.repo.repo, + head: context.repo.owner + ':${{ github.ref_name }}' + }) + if (prs.data.length) { + console.log(`::notice ::Skipping CI on branch push as it is already run in PR #${prs.data[0]["number"]}`) + return prs.data[0]["number"] + } diff --git a/.github/workflows/_container.yml b/.github/workflows/_container.yml new file mode 100644 index 00000000..4857ee9e --- /dev/null +++ b/.github/workflows/_container.yml @@ -0,0 +1,56 @@ +on: + workflow_call: + +jobs: + build: + 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: Set up Docker Buildx + id: buildx + uses: docker/setup-buildx-action@v3 + + - 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: Build and export to Docker local cache + uses: docker/build-push-action@v5 + with: + context: . + # Need load and tags so we can test it below + load: true + tags: tag_for_testing + + - name: Test cli works in cached runtime image + run: docker run --rm tag_for_testing --version + + - name: Create tags for publishing image + id: meta + uses: docker/metadata-action@v5 + with: + images: ghcr.io/${{ github.repository }} + tags: | + type=ref,event=tag + type=raw,value=latest + + - name: Push cached image to container registry + if: github.ref_type == 'tag' + 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: + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} diff --git a/.github/workflows/_dist.yml b/.github/workflows/_dist.yml new file mode 100644 index 00000000..b1c4c93c --- /dev/null +++ b/.github/workflows/_dist.yml @@ -0,0 +1,36 @@ +on: + workflow_call: + +jobs: + build: + 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 + with: + name: dist + path: dist + + - name: Check for packaging errors + run: pipx run twine check --strict dist/* + + - name: Install produced wheel + uses: ./.github/actions/install_requirements + with: + pip-install: dist/*.whl + + - 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 --hide='*.egg-info' src | head -1) --version diff --git a/.github/workflows/_docs.yml b/.github/workflows/_docs.yml new file mode 100644 index 00000000..40446e33 --- /dev/null +++ b/.github/workflows/_docs.yml @@ -0,0 +1,54 @@ +on: + workflow_call: + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Avoid git conflicts when tag and branch pushed at same time + if: github.ref_type == 'tag' + 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 + run: sudo apt-get install graphviz + + - name: Install python packages + uses: ./.github/actions/install_requirements + + - name: Build docs + run: tox -e docs + + - name: Remove environment.pickle + run: rm build/html/.doctrees/environment.pickle + + - name: Upload built docs artifact + uses: actions/upload-artifact@v4 + with: + name: docs + path: build + + - 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.ref_type == 'tag' || github.ref_name == 'main' + # 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@373f7f263a76c20808c831209c920827a82a2847 # v3.9.3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: .github/pages + keep_files: true \ No newline at end of file diff --git a/.github/workflows/_pypi.yml b/.github/workflows/_pypi.yml new file mode 100644 index 00000000..f2ead1bc --- /dev/null +++ b/.github/workflows/_pypi.yml @@ -0,0 +1,22 @@ +on: + workflow_call: + secrets: + PYPI_TOKEN: + required: true + +jobs: + upload: + runs-on: ubuntu-latest + environment: release + + steps: + - name: Download dist artifact + uses: actions/download-artifact@v4 + with: + name: dist + path: dist + + - name: Publish to PyPI using trusted publishing + uses: pypa/gh-action-pypi-publish@release/v1 + with: + password: ${{ secrets.PYPI_TOKEN }} diff --git a/.github/workflows/_release.yml b/.github/workflows/_release.yml new file mode 100644 index 00000000..b49fa7dc --- /dev/null +++ b/.github/workflows/_release.yml @@ -0,0 +1,32 @@ +on: + workflow_call: + +jobs: + artifacts: + runs-on: ubuntu-latest + + steps: + - name: Download artifacts + uses: actions/download-artifact@v4 + with: + merge-multiple: true + + - name: Zip up docs + run: | + set -vxeuo pipefail + if [ -d html ]; then + mv html $GITHUB_REF_NAME + zip -r docs.zip $GITHUB_REF_NAME + rm -rf $GITHUB_REF_NAME + fi + + - name: Create 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: "*" + generate_release_notes: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/_test.yml b/.github/workflows/_test.yml new file mode 100644 index 00000000..9b1f21f2 --- /dev/null +++ b/.github/workflows/_test.yml @@ -0,0 +1,62 @@ +on: + workflow_call: + inputs: + python-version: + type: string + description: The version of python to install + required: true + runs-on: + type: string + description: The runner to run this job on + required: true + secrets: + CODECOV_TOKEN: + required: true + +env: + # https://github.com/pytest-dev/pytest/issues/2042 + PY_IGNORE_IMPORTMISMATCH: "1" + +jobs: + run: + runs-on: ${{ inputs.runs-on }} + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + # Need this to get version number from last tag + fetch-depth: 0 + + - if: inputs.python-version == 'dev' + name: Install dev versions of python packages + uses: ./.github/actions/install_requirements + + - if: inputs.python-version == 'dev' + name: Write the requirements as an artifact + run: pip freeze --exclude-editable > /tmp/dev-requirements.txt + + - if: inputs.python-version == 'dev' + name: Upload dev-requirements.txt + uses: actions/upload-artifact@v4 + with: + name: dev-requirements + path: /tmp/dev-requirements.txt + + - if: inputs.python-version != 'dev' + name: Install latest versions of python packages + uses: ./.github/actions/install_requirements + with: + python-version: ${{ inputs.python-version }} + pip-install: ".[dev]" + + - name: Run tests + run: tox -e pytest + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v4 + with: + name: ${{ inputs.python-version }}/${{ inputs.runs-on }} + files: cov.xml + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/_tox.yml b/.github/workflows/_tox.yml new file mode 100644 index 00000000..a13536d3 --- /dev/null +++ b/.github/workflows/_tox.yml @@ -0,0 +1,22 @@ +on: + workflow_call: + inputs: + tox: + type: string + description: What to run under tox + required: true + + +jobs: + run: + runs-on: "ubuntu-latest" + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install python packages + uses: ./.github/actions/install_requirements + + - name: Run tox + run: tox -e ${{ inputs.tox }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..c5610ddc --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,68 @@ +name: CI + +on: + push: + pull_request: + +jobs: + check: + uses: ./.github/workflows/_check.yml + + lint: + needs: check + if: needs.check.outputs.branch-pr == '' + uses: ./.github/workflows/_tox.yml + with: + tox: pre-commit,pyright + + test: + needs: check + if: needs.check.outputs.branch-pr == '' + strategy: + matrix: + runs-on: ["ubuntu-latest"] # can add windows-latest, macos-latest + python-version: ["3.8", "3.9", "3.10", "3.11"] + include: + # Include one that runs in the dev environment + - runs-on: "ubuntu-latest" + python-version: "dev" + fail-fast: false + uses: ./.github/workflows/_test.yml + with: + runs-on: ${{ matrix.runs-on }} + python-version: ${{ matrix.python-version }} + secrets: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + + container: + needs: check + if: needs.check.outputs.branch-pr == '' + uses: ./.github/workflows/_container.yml + permissions: + packages: write + + docs: + needs: check + if: needs.check.outputs.branch-pr == '' + uses: ./.github/workflows/_docs.yml + + dist: + needs: check + if: needs.check.outputs.branch-pr == '' + uses: ./.github/workflows/_dist.yml + + pypi: + if: github.ref_type == 'tag' + needs: dist + uses: ./.github/workflows/_pypi.yml + permissions: + id-token: write + secrets: + PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }} + + release: + if: github.ref_type == 'tag' + needs: [dist, docs] + uses: ./.github/workflows/_release.yml + permissions: + contents: write diff --git a/.github/workflows/periodic.yml b/.github/workflows/periodic.yml new file mode 100644 index 00000000..e2a0fd1b --- /dev/null +++ b/.github/workflows/periodic.yml @@ -0,0 +1,13 @@ +name: Periodic + +on: + workflow_dispatch: + schedule: + # Run weekly to check URL links still resolve + - cron: "0 8 * * WED" + +jobs: + linkcheck: + uses: ./.github/workflows/_tox.yml + with: + tox: docs build -- -b linkcheck diff --git a/.gitignore b/.gitignore index de048497..2593ec75 100644 --- a/.gitignore +++ b/.gitignore @@ -1,28 +1,70 @@ -*~ -.cproject -.project -.vscode -O.* -bin -include -lib -db -dbd -data -iocs/lab -iocs/gui -iocs/lab-ppmac -.idea -cmake-build-debug/ -*.pyc -deleteme -.pytest_cache +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +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 -*.out -*.o +nosetests.xml +coverage.xml +cov.xml +.pytest_cache/ +.mypy_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: *.log -#etc/PowerClipper/ -#etc/LabPowerPmac/ -#etc/LabPowerPmac_Backup/ -.nfs* -**/venv27 + +# 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 00000000..5a4cbf7b --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,23 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.5.0 + hooks: + - id: check-added-large-files + - id: check-yaml + - id: check-merge-conflict + + - repo: local + hooks: + - id: ruff + name: lint with ruff + language: system + entry: ruff check --force-exclude + types: [python] + require_serial: true + + - id: ruff-format + name: format with ruff + language: system + entry: ruff format --force-exclude + types: [python] + require_serial: true diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 00000000..66ad6324 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,5 @@ +{ + "recommendations": [ + "ms-vscode-remote.remote-containers", + ] +} \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..fe2b6dc3 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,19 @@ +{ + // 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", + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..c129d991 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,11 @@ +{ + "python.testing.unittestEnabled": false, + "python.testing.pytestEnabled": true, + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.organizeImports": "explicit" + }, + "[python]": { + "editor.defaultFormatter": "charliermarsh.ruff", + }, +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 00000000..946e69d4 --- /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": [], + } + ] +} \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..6c754acc --- /dev/null +++ b/Dockerfile @@ -0,0 +1,29 @@ +# The devcontainer should use the developer target and run as root with podman +# or docker with user namespaces. +ARG PYTHON_VERSION=3.11 +FROM python:${PYTHON_VERSION} as developer + +# Add any system dependencies for the developer/build environment here +RUN apt-get update && apt-get install -y --no-install-recommends \ + graphviz \ + && 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 + +# The build stage installs the context into the venv +FROM developer as build +COPY . /context +WORKDIR /context +RUN pip install . + +# The runtime stage copies the built venv into a slim runtime container +FROM python:${PYTHON_VERSION}-slim as runtime +# Add apt-get system dependecies for runtime here if needed +COPY --from=build /venv/ /venv/ +ENV PATH=/venv/bin:$PATH + +# change this entrypoint if it is not the same as the repo +ENTRYPOINT ["dls-pmac-control"] +CMD ["--version"] diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..8dada3ed --- /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.md b/README.md index 9e7adae7..46fbd4a3 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,19 @@ -# pmac -EPICS driver and support module for the Delta Tau PMAC motion controller family. +[![CI](https://github.com/DiamondLightSource/dls-pmac-control/actions/workflows/ci.yml/badge.svg)](https://github.com/DiamondLightSource/dls-pmac-control/actions/workflows/ci.yml) +[![Coverage](https://codecov.io/gh/DiamondLightSource/dls-pmac-control/branch/main/graph/badge.svg)](https://codecov.io/gh/DiamondLightSource/dls-pmac-control) +[![PyPI](https://img.shields.io/pypi/v/dls-pmac-control.svg)](https://pypi.org/project/dls-pmac-control) +[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) + +# dls_pmac_control + +A GUI for low level interaction with turbo pmacs and power pmacs + + +Source | +:---: | :---: +PyPI | `pip install dls-pmac-control` +Documentation | +Releases | + + + +See https://diamondlightsource.github.io/dls-pmac-control for more detailed documentation. diff --git a/catalog-info.yaml b/catalog-info.yaml new file mode 100644 index 00000000..062f54ee --- /dev/null +++ b/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: dls-pmac-control + title: dls-pmac-control + description: A GUI for low level interaction with turbo pmacs and power pmacs +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 00000000..f254507c --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,188 @@ +# 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 dls_pmac_control + +# -- General configuration ------------------------------------------------ + +# General information about the project. +project = "dls-pmac-control" + +# The full version, including alpha/beta/rc tags. +release = dls_pmac_control.__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", + # So we can write markdown files + "myst_parser", +] + +# So we can use the ::: syntax +myst_enable_extensions = ["colon_fence"] + +# 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 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"} + +# 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 = "dls-pmac-control" +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 = html_logo diff --git a/docs/explanations.md b/docs/explanations.md new file mode 100644 index 00000000..73ab289b --- /dev/null +++ b/docs/explanations.md @@ -0,0 +1,10 @@ +# Explanations + +Explanations of how it works and why it works that way. + +```{toctree} +:maxdepth: 1 +:glob: + +explanations/* +``` diff --git a/docs/explanations/decisions.md b/docs/explanations/decisions.md new file mode 100644 index 00000000..f97c2f9f --- /dev/null +++ b/docs/explanations/decisions.md @@ -0,0 +1,18 @@ +% 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](http://thinkrelevance.com/blog/2011/11/15/documenting-architecture-decisions). +Below is the list of our current ADRs. + +```{toctree} +:glob: true +:maxdepth: 1 + +decisions/* +``` diff --git a/docs/explanations/decisions/0001-record-architecture-decisions.md b/docs/explanations/decisions/0001-record-architecture-decisions.md new file mode 100644 index 00000000..44d234ef --- /dev/null +++ b/docs/explanations/decisions/0001-record-architecture-decisions.md @@ -0,0 +1,18 @@ +# 1. Record architecture decisions + +## 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](http://thinkrelevance.com/blog/2011/11/15/documenting-architecture-decisions). + +## Consequences + +See Michael Nygard's article, linked above. To create new ADRs we will copy and +paste from existing ones. diff --git a/docs/explanations/decisions/0002-switched-to-python-copier-template.md b/docs/explanations/decisions/0002-switched-to-python-copier-template.md new file mode 100644 index 00000000..66fe5d8b --- /dev/null +++ b/docs/explanations/decisions/0002-switched-to-python-copier-template.md @@ -0,0 +1,28 @@ +# 2. Adopt python-copier-template for project structure + +## Status + +Accepted + +## Context + +We should use the following [python-copier-template](https://github.com/DiamondLightSource/python-copier-template). +The template will ensure consistency in developer +environments and package management. + +## Decision + +We have switched to using the template. + +## 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/genindex.md b/docs/genindex.md new file mode 100644 index 00000000..73f1191b --- /dev/null +++ b/docs/genindex.md @@ -0,0 +1,3 @@ +# Index + + diff --git a/docs/how-to.md b/docs/how-to.md new file mode 100644 index 00000000..6b161417 --- /dev/null +++ b/docs/how-to.md @@ -0,0 +1,10 @@ +# How-to Guides + +Practical step-by-step guides for the more experienced user. + +```{toctree} +:maxdepth: 1 +:glob: + +how-to/* +``` diff --git a/docs/how-to/contribute.md b/docs/how-to/contribute.md new file mode 100644 index 00000000..f9c4ca1d --- /dev/null +++ b/docs/how-to/contribute.md @@ -0,0 +1,2 @@ +```{include} ../../.github/CONTRIBUTING.md +``` \ No newline at end of file diff --git a/docs/how-to/run-container.md b/docs/how-to/run-container.md new file mode 100644 index 00000000..8676b841 --- /dev/null +++ b/docs/how-to/run-container.md @@ -0,0 +1,14 @@ +# Run in a container + +Pre-built containers with {\{repo_name}} and its dependencies already +installed are available on [Github Container Registry](https://ghcr.io/DiamondLightSource/dls-pmac-control). + +## Starting the container + +To pull the container from github container registry and run: + +``` +$ docker run ghcr.io/DiamondLightSource/dls-pmac-control:main --version +``` + +To get a released version, use a numbered release instead of `main`. diff --git a/docs/images/dls-logo.svg b/docs/images/dls-logo.svg new file mode 100644 index 00000000..0af1a177 --- /dev/null +++ b/docs/images/dls-logo.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 00000000..730b3fdc --- /dev/null +++ b/docs/index.md @@ -0,0 +1,56 @@ +--- +html_theme.sidebar_secondary.remove: true +--- + +```{include} ../README.md +:end-before: + +::::{grid} 2 +:gutter: 4 + +:::{grid-item-card} {material-regular}`directions_walk;2em` +```{toctree} +:maxdepth: 2 +tutorials +``` ++++ +Tutorials for installation and typical usage. New users start here. +::: + +:::{grid-item-card} {material-regular}`directions;2em` +```{toctree} +:maxdepth: 2 +how-to +``` ++++ +Practical step-by-step guides for the more experienced user. +::: + +:::{grid-item-card} {material-regular}`info;2em` +```{toctree} +:maxdepth: 2 +explanations +``` ++++ +Explanations of how it works and why it works that way. +::: + +:::{grid-item-card} {material-regular}`menu_book;2em` +```{toctree} +:maxdepth: 2 +reference +``` ++++ +Technical reference material including APIs and release notes. +::: + +:::: diff --git a/docs/reference.md b/docs/reference.md new file mode 100644 index 00000000..6a7d085a --- /dev/null +++ b/docs/reference.md @@ -0,0 +1,11 @@ +# Reference + +Technical reference material including APIs and release notes. + +```{toctree} +:maxdepth: 1 +:glob: + +reference/* +genindex +``` diff --git a/docs/reference/api.md b/docs/reference/api.md new file mode 100644 index 00000000..82732e95 --- /dev/null +++ b/docs/reference/api.md @@ -0,0 +1,17 @@ +# API + +```{eval-rst} +.. automodule:: dls_pmac_control + + ``dls_pmac_control`` + ----------------------------------- +``` + +This is the internal API reference for {\{package_name}} + +```{eval-rst} +.. data:: dls_pmac_control.__version__ + :type: str + + Version number as calculated by https://github.com/pypa/setuptools_scm +``` diff --git a/docs/tutorials.md b/docs/tutorials.md new file mode 100644 index 00000000..1fe66c54 --- /dev/null +++ b/docs/tutorials.md @@ -0,0 +1,10 @@ +# Tutorials + +Tutorials for installation and typical usage. New users start here. + +```{toctree} +:maxdepth: 1 +:glob: + +tutorials/* +``` diff --git a/docs/tutorials/installation.md b/docs/tutorials/installation.md new file mode 100644 index 00000000..836070d9 --- /dev/null +++ b/docs/tutorials/installation.md @@ -0,0 +1,42 @@ +# 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-pmac-control +``` + +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/dls-pmac-control.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: + +``` +$ dls-pmac-control --version +``` diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..360bdfc2 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,110 @@ +[build-system] +requires = ["setuptools>=64", "setuptools_scm[toml]>=6.2"] +build-backend = "setuptools.build_meta" + +[project] +name = "dls-pmac-control" +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 = "A GUI for low level interaction with turbo pmacs and power pmacs" +dependencies = [] # Add project dependencies here, e.g. ["click", "numpy"] +dynamic = ["version"] +license.file = "LICENSE" +readme = "README.md" +requires-python = ">=3.7" + +[project.optional-dependencies] +dev = [ + "copier", + "myst-parser", + "pipdeptree", + "pre-commit", + "pydata-sphinx-theme>=0.12", + "pyright", + "pytest", + "pytest-cov", + "ruff", + "sphinx-autobuild", + "sphinx-copybutton", + "sphinx-design", + "tox-direct", + "types-mock", +] + +[project.scripts] +dls-pmac-control = "dls_pmac_control.__main__:main" + +[project.urls] +GitHub = "https://github.com/DiamondLightSource/dls-pmac-control" + +[[project.authors]] # Further authors may be added by duplicating this section +email = "giles.knap@diamond.ac.uk" +name = "Giles Knap" + + +[tool.setuptools_scm] +write_to = "src/dls_pmac_control/_version.py" + +[tool.pyright] +reportMissingImports = false # 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/dls_pmac_control.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,pyright,pytest,docs}] +# Don't create a virtualenv for the command, requires tox-direct plugin +direct = True +passenv = * +allowlist_externals = + pytest + pre-commit + pyright + sphinx-build + sphinx-autobuild +commands = + pytest: pytest --cov=dls_pmac_control --cov-report term --cov-report xml:cov.xml {posargs} + pyright: pyright 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 +lint.select = [ + "B", # flake8-bugbear - https://docs.astral.sh/ruff/rules/#flake8-bugbear-b + "C4", # flake8-comprehensions - https://docs.astral.sh/ruff/rules/#flake8-comprehensions-c4 + "E", # pycodestyle errors - https://docs.astral.sh/ruff/rules/#error-e + "F", # pyflakes rules - https://docs.astral.sh/ruff/rules/#pyflakes-f + "W", # pycodestyle warnings - https://docs.astral.sh/ruff/rules/#warning-w + "I", # isort - https://docs.astral.sh/ruff/rules/#isort-i + "UP", # pyupgrade - https://docs.astral.sh/ruff/rules/#pyupgrade-up +] diff --git a/src/dls_pmac_control/__init__.py b/src/dls_pmac_control/__init__.py new file mode 100644 index 00000000..26d23bad --- /dev/null +++ b/src/dls_pmac_control/__init__.py @@ -0,0 +1,3 @@ +from ._version import __version__ + +__all__ = ["__version__"] diff --git a/src/dls_pmac_control/__main__.py b/src/dls_pmac_control/__main__.py new file mode 100644 index 00000000..823f3b98 --- /dev/null +++ b/src/dls_pmac_control/__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 dls_pmac_control +if __name__ == "__main__": + main() diff --git a/tests/test_cli.py b/tests/test_cli.py new file mode 100644 index 00000000..9d950201 --- /dev/null +++ b/tests/test_cli.py @@ -0,0 +1,9 @@ +import subprocess +import sys + +from dls_pmac_control import __version__ + + +def test_cli_version(): + cmd = [sys.executable, "-m", "dls_pmac_control", "--version"] + assert subprocess.check_output(cmd).decode().strip() == __version__