Repository: bluesky/scanspec (language: python)

Issue:
Python 3.11 Compatibility
* Relock pipfile bumping unpinned dependencies
* Add CI for Python 3.10 and 3.11


Discussion / hints:

Fix patch:
diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile
new file mode 100644
index 00000000..0266d11e
--- /dev/null
+++ b/.devcontainer/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 ["scanspec"]
+CMD ["--version"]
diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
new file mode 100644
index 00000000..f5e71fe9
--- /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]"
+}
\ No newline at end of file
diff --git a/.gitattributes b/.gitattributes
deleted file mode 100644
index 075748c4..00000000
--- a/.gitattributes
+++ /dev/null
@@ -1,1 +0,0 @@
-src/*/_version_git.py export-subst
diff --git a/.github/CONTRIBUTING.rst b/.github/CONTRIBUTING.rst
new file mode 100644
index 00000000..a2fa3ae6
--- /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/dls-controls/scanspec/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/dls-controls/scanspec/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/scanspec/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 00000000..25a146d1
--- /dev/null
+++ b/.github/actions/install_requirements/action.yml
@@ -0,0 +1,58 @@
+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
+  python_version:
+    description: Python version to install
+    default: "3.x"
+
+runs:
+  using: composite
+
+  steps:
+    - name: Setup python
+      uses: actions/setup-python@v4
+      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 '/file:/d' lockfiles/${{ inputs.requirements_file }}
+      shell: bash
+
+    - name: Upload lockfiles
+      uses: actions/upload-artifact@v3
+      with:
+        name: lockfiles
+        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 00000000..fb7c6ee6
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,16 @@
+# 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"
+
+  - package-ecosystem: "pip"
+    directory: "/"
+    schedule:
+      interval: "weekly"
diff --git a/.github/pages/make_switcher.py b/.github/pages/make_switcher.py
new file mode 100755
index 00000000..39c12772
--- /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 = [
+        dict(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)
+
+
+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
index d90956a1..6b97ca74 100644
--- a/.github/workflows/code.yml
+++ b/.github/workflows/code.yml
@@ -2,79 +2,51 @@ name: Code CI
 
 on:
   push:
-    branches:
-      # Restricting to these branches and tags stops duplicate jobs on internal
-      # PRs but stops CI running on internal branches without a PR. Delete the
-      # next 5 lines to restore the original behaviour
-      - master
-      - main
-    tags:
-      - "*"
   pull_request:
   schedule:
-    # Run every Monday at 8am to check latest versions of dependencies
-    - cron: "0 8 * * MON"
+    # Run weekly to check latest versions of dependencies
+    - cron: "0 8 * * WED"
+env:
+  # The target python version, which must match the Dockerfile version
+  CONTAINER_PYTHON: "3.11"
 
 jobs:
   lint:
-    runs-on: "ubuntu-latest"
-    steps:
-      - name: Run black, flake8, mypy
-        uses: dls-controls/pipenv-run-action@v1
-        with:
-          pipenv-run: lint
-
-  wheel:
-    strategy:
-      fail-fast: false
-      matrix:
-        os: ["ubuntu-latest"]
-        python: ["3.7"]
+    # 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
 
-    runs-on: ${{ matrix.os }}
     steps:
-      - uses: actions/checkout@v2
-        with:
-          fetch-depth: 0
-
-      - name: Create Sdist and Wheel
-        # Set SOURCE_DATE_EPOCH from git commit for reproducible build
-        # https://reproducible-builds.org/
-        # Set group writable and umask to do the same to match inside DLS
-        run: |
-          chmod -R g+w .
-          umask 0002
-          SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct) pipx run build --sdist --wheel
+      - name: Checkout
+        uses: actions/checkout@v3
 
-      - name: Upload Wheel and Sdist as artifacts
-        uses: actions/upload-artifact@v2
+      - name: Install python packages
+        uses: ./.github/actions/install_requirements
         with:
-          name: dist
-          path: dist/*
+          requirements_file: requirements-dev-3.x.txt
+          install_options: -e .[dev]
+
+      - name: Lint
+        run: tox -e pre-commit,mypy
 
       - name: Install minimum python version
         uses: actions/setup-python@v2
         with:
           python-version: ${{ matrix.python }}
 
-      - name: Install wheel in a venv and check cli works
-        # ${GITHUB_REPOSITORY##*/} is the repo name without org
-        # Replace this with the cli command if different to the repo name
-        run: pipx run --python $(which python${{ matrix.python }}) --spec dist/*.whl ${GITHUB_REPOSITORY##*/} --version
-
   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.7", "3.8", "3.9"]
-        pipenv: ["skip-lock"]
-
+        python: ["3.9", "3.10", "3.11"]
+        install: ["-e .[dev,graphql,plotting]"]
+        # Make one version be non-editable to test both paths of version code
         include:
-          # Add an extra Python3.7 runner to use the lockfile
           - os: "ubuntu-latest"
-            python: "3.7"
-            pipenv: "deploy"
+            python: "3.8"
+            install: ".[dev,graphql,plotting]"
 
     runs-on: ${{ matrix.os }}
     env:
@@ -82,43 +54,157 @@ jobs:
       PY_IGNORE_IMPORTMISMATCH: "1"
 
     steps:
-      - name: Setup repo and test
-        uses: dls-controls/pipenv-run-action@v1
+      - name: Checkout
+        uses: actions/checkout@v3
         with:
-          python-version: ${{ matrix.python }}
-          pipenv-install: --dev --${{ matrix.pipenv }}
-          allow-editable-installs: ${{ matrix.pipenv == 'deploy' }}
-          pipenv-run: tests
+          # 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 }}
+
+      - name: List dependency tree
+        run: pipdeptree
+
+      - name: Run tests
+        run: pytest
 
       - name: Upload coverage to Codecov
-        uses: codecov/codecov-action@v2
+        uses: codecov/codecov-action@v3
         with:
-          name: ${{ matrix.python }}/${{ matrix.os }}/${{ matrix.pipenv }}
+          name: ${{ matrix.python }}/${{ matrix.os }}
           files: cov.xml
 
-  release:
-    needs: [lint, wheel, test]
-    runs-on: ubuntu-latest
-    # upload to PyPI and make a release on every tag
-    if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
+  dist:
+    if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository
+    runs-on: "ubuntu-latest"
+
     steps:
-      - uses: actions/download-artifact@v2
+      - name: Checkout
+        uses: actions/checkout@v3
+        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@v3
         with:
           name: dist
           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
+
+      - 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
+
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v3
+
+      # 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: Download wheel and lockfiles
+        uses: actions/download-artifact@v3
+        with:
+          path: .devcontainer
+
+      - name: Log in to GitHub Docker Registry
+        if: github.event_name != 'pull_request'
+        uses: docker/login-action@v2
+        with:
+          registry: ghcr.io
+          username: ${{ github.actor }}
+          password: ${{ secrets.GITHUB_TOKEN }}
+
+      - name: Docker meta
+        id: meta
+        uses: docker/metadata-action@v4
+        with:
+          images: ${{ env.IMAGE_REPOSITORY }}
+          tags: |
+            type=ref,event=tag
+            type=raw,value=latest
+
+      - name: Set up Docker Buildx
+        id: buildx
+        uses: docker/setup-buildx-action@v2
+
+      - name: Build runtime image
+        uses: docker/build-push-action@v3
+        with:
+          build-args: |
+            PIP_OPTIONS=-r lockfiles/requirements.txt dist/*.whl
+          push: ${{ github.event_name == 'push' && startsWith(github.ref, 'refs/tags') }}
+          load: ${{ ! (github.event_name == 'push' && startsWith(github.ref, 'refs/tags')) }}
+          tags: ${{ steps.meta.outputs.tags }}
+          labels: ${{ steps.meta.outputs.labels }}
+          context: .devcontainer
+          # If you have a long docker build, 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 runtime image
+        run: docker run ${{ env.IMAGE_REPOSITORY }} --version
+
+  release:
+    # upload to PyPI and make a release on every tag
+    needs: [lint, dist, test]
+    if: ${{ github.event_name == 'push' && startsWith(github.ref, 'refs/tags') }}
+    runs-on: ubuntu-latest
+    env:
+      HAS_PYPI_TOKEN: ${{ secrets.PYPI_TOKEN != '' }}
+
+    steps:
+      - uses: actions/download-artifact@v3
+
+      - name: Fixup blank lockfiles
+        # Github release artifacts can't be blank
+        run: for f in lockfiles/*; 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@1e07f4398721186383de40550babbdf2b84acfc5 # v0.1.14
         with:
-          files: dist/*
+          prerelease: ${{ contains(github.ref_name, 'a') || contains(github.ref_name, 'b') || contains(github.ref_name, 'rc') }}
+          files: |
+            dist/*
+            lockfiles/*
           generate_release_notes: true
         env:
           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
 
       - name: Publish to PyPI
-        env:
-          TWINE_USERNAME: __token__
-          TWINE_PASSWORD: ${{ secrets.pypi_token }}
-        run: pipx run twine upload dist/*
+        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
index 8ac5bdf4..dc8136ca 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -2,16 +2,11 @@ name: Docs CI
 
 on:
   push:
-    branches:
-      # Add more branches here to publish docs from other branches
-      - master
-      - main
-    tags:
-      - "*"
   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:
@@ -19,27 +14,39 @@ jobs:
         if: startsWith(github.ref, 'refs/tags')
         run: sleep 60
 
-      - name: Install Packages
+      - name: Checkout
+        uses: actions/checkout@v3
+        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: Build docs
-        uses: dls-controls/pipenv-run-action@v1
+      - name: Install python packages
+        uses: ./.github/actions/install_requirements
         with:
-          pipenv-run: docs
+          requirements_file: requirements-dev-3.x.txt
+          install_options: -e .[dev,graphql,plotting]
+
+      - 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
-        # e.g. master or 0.1.2
-        run: mv build/html ".github/pages/${GITHUB_REF##*/}"
+        run: mv build/html .github/pages/$DOCS_VERSION
 
-      - name: Write versions.txt
-        run: pipenv run sphinx_rtd_theme_github_versions .github/pages
+      - 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'
+        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@068dc23d9710f1ba62e86896f84735d869951305 # v3.8.0
+        uses: peaceiris/actions-gh-pages@de7ea6f8efb354206b205ef54722213d99067935 # v3.9.0
         with:
           github_token: ${{ secrets.GITHUB_TOKEN }}
           publish_dir: .github/pages
diff --git a/.github/workflows/docs_clean.yml b/.github/workflows/docs_clean.yml
new file mode 100644
index 00000000..a67e1881
--- /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@v3
+        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
index e9384c9d..02d8df4c 100644
--- a/.github/workflows/linkcheck.yml
+++ b/.github/workflows/linkcheck.yml
@@ -1,16 +1,24 @@
 name: Link Check
 
 on:
+  workflow_dispatch:
   schedule:
-    # Run every Monday at 8am to check URL links still resolve
-    - cron: "0 8 * * MON"
+    # Run weekly to check URL links still resolve
+    - cron: "0 8 * * WED"
 
 jobs:
   docs:
     runs-on: ubuntu-latest
 
     steps:
-      - name: Build docs
-        uses: dls-controls/pipenv-run-action@v1
+      - name: Checkout
+        uses: actions/checkout@v3
+
+      - name: Install python packages
+        uses: ./.github/actions/install_requirements
         with:
-          pipenv-run: docs -b linkcheck
+          requirements_file: requirements-dev-3.x.txt
+          install_options: -e .[dev]
+
+      - name: Check links
+        run: tox -e docs build -- -b linkcheck
diff --git a/.gitignore b/.gitignore
index 0ce69d99..9fbb6bfe 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,6 +8,7 @@ __pycache__/
 # Distribution / packaging
 .Python
 env/
+.venv
 build/
 develop-eggs/
 dist/
@@ -22,6 +23,7 @@ var/
 *.egg-info/
 .installed.cfg
 *.egg
+**/_version.py
 
 # PyInstaller
 #  Usually these files are written by a python script from a template
@@ -58,8 +60,10 @@ docs/_build/
 # PyBuilder
 target/
 
-# DLS build dir and virtual environment
-/prefix/
-/venv/
-/lightweight-venv/
-/installed.files
+# likely venv names
+.venv*
+venv*
+
+# further build artifacts
+lockfiles/
+
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
deleted file mode 100644
index 1efd5024..00000000
--- a/.gitlab-ci.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-include:
-  - project: 'controls/reports/ci_templates'
-    ref: master
-    file: 'python3/dls_py3_template.yml'
diff --git a/.gitremotes b/.gitremotes
deleted file mode 100644
index 8fc70b6c..00000000
--- a/.gitremotes
+++ /dev/null
@@ -1,1 +0,0 @@
-github git@github.com:dls-controls/scanspec.git
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index d081700e..5e270b08 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -12,19 +12,12 @@ repos:
         name: Run black
         stages: [commit]
         language: system
-        entry: pipenv run black --check --diff
+        entry: black --check --diff
         types: [python]
 
       - id: flake8
         name: Run flake8
         stages: [commit]
         language: system
-        entry: pipenv run flake8
+        entry: flake8
         types: [python]
-
-      - id: mypy
-        name: Run mypy
-        stages: [commit]
-        language: system
-        entry: pipenv run mypy src tests
-        pass_filenames: false
diff --git a/.vscode/extensions.json b/.vscode/extensions.json
index 734f215e..81922991 100644
--- a/.vscode/extensions.json
+++ b/.vscode/extensions.json
@@ -1,7 +1,9 @@
 {
     "recommendations": [
-        "ms-python.vscode-pylance",
+        "ms-vscode-remote.remote-containers",
         "ms-python.python",
+        "tamasfe.even-better-toml",
+        "redhat.vscode-yaml",
         "ryanluker.vscode-coverage-gutters"
     ]
 }
\ No newline at end of file
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
index ff78a11b..946e69d4 100644
--- a/.vscode/tasks.json
+++ b/.vscode/tasks.json
@@ -5,8 +5,8 @@
     "tasks": [
         {
             "type": "shell",
-            "label": "Tests with coverage",
-            "command": "pipenv run tests",
+            "label": "Tests, lint and docs",
+            "command": "tox -p",
             "options": {
                 "cwd": "${workspaceRoot}"
             },
diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst
deleted file mode 100644
index acd1a9d2..00000000
--- a/CONTRIBUTING.rst
+++ /dev/null
@@ -1,124 +0,0 @@
-Contributing
-============
-
-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/dls-controls/scanspec/issues
-
-Running the tests
------------------
-
-To get the source source code and run the unit tests, run::
-
-    $ git clone git://github.com/dls-controls/scanspec.git
-    $ cd scanspec
-    $ pipenv install --dev
-    $ pipenv run tests
-
-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!
-
-Code Styling
-------------
-
-The code in this repository conforms to standards set by the following tools:
-
-- black_ for code formatting
-- flake8_ for style checks
-- isort_ for import ordering
-- mypy_ for static type checking
-- pydocstyle_ for docstring checking
-
-These checks will be run by pre-commit_. You can either choose to run these
-tests on all files tracked by git::
-
-    $ pipenv run lint
-
-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::
-
-    $ pipenv run pre-commit install
-
-.. _black: https://github.com/psf/black
-.. _flake8: https://flake8.pycqa.org/en/latest/
-.. _isort: https://github.com/PyCQA/isort
-.. _mypy: https://github.com/python/mypy
-.. _pydocstyle: http://www.pydocstyle.org
-.. _pre-commit: https://pre-commit.com/
-
-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::
-
-    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
--------------
-
-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
-    ~~~~~~~~~
-
-You can build the docs from the project directory by running::
-
-    $ pipenv run docs
-    $ firefox build/html/index.html
-
-Release Process
----------------
-
-To make a new release, please follow this checklist:
-
-- Choose a new PEP440 compliant release number
-- Git tag the version
-- Push to GitHub and the actions will make a release on pypi
-- Push to internal gitlab and do a dls-release.py of the tag
-- Check and edit for clarity the autogenerated GitHub release_
-
-.. _release: https://github.com/dls-controls/scanspec/releases
-
-Updating the tools
-------------------
-
-This module is merged with the dls-python3-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 https://github.com/dls-controls/dls-python3-skeleton 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
-<https://github.com/dls-controls/dls-python3-skeleton/pulls?q=is%3Apr+is%3Aclosed>`_
-of the skeleton module for more details.
-
-.. _dls-python3-skeleton: https://dls-controls.github.io/dls-python3-skeleton
diff --git a/Pipfile b/Pipfile
deleted file mode 100644
index 366c3ca3..00000000
--- a/Pipfile
+++ /dev/null
@@ -1,17 +0,0 @@
-[[source]]
-name = "pypi"
-url = "https://pypi.org/simple"
-verify_ssl = true
-
-[dev-packages]
-scanspec = {editable = true, extras = ["dev"], path = "."}
-
-[packages]
-scanspec = {editable = true, extras = ["plotting", "graphql"], path = "."}
-
-[scripts]
-lint = "pre-commit run --all-files --show-diff-on-failure --color=always -v"
-tests = "pytest"
-docs = "sphinx-build -EWT --keep-going docs build/html"
-# Delete any files that git ignore hides from us
-gitclean = "git clean -fdX"
diff --git a/Pipfile.lock b/Pipfile.lock
deleted file mode 100644
index a68c2ee4..00000000
--- a/Pipfile.lock
+++ /dev/null
@@ -1,1348 +0,0 @@
-{
-    "_meta": {
-        "hash": {
-            "sha256": "e023f3f734b2e37cc9bc32550280530622e79bcae091f8774454319e6daf4431"
-        },
-        "pipfile-spec": 6,
-        "requires": {},
-        "sources": [
-            {
-                "name": "pypi",
-                "url": "https://pypi.org/simple",
-                "verify_ssl": true
-            }
-        ]
-    },
-    "default": {
-        "aiohttp": {
-            "hashes": [
-                "sha256:01d7bdb774a9acc838e6b8f1d114f45303841b89b95984cbb7d80ea41172a9e3",
-                "sha256:03a6d5349c9ee8f79ab3ff3694d6ce1cfc3ced1c9d36200cb8f08ba06bd3b782",
-                "sha256:04d48b8ce6ab3cf2097b1855e1505181bdd05586ca275f2505514a6e274e8e75",
-                "sha256:0770e2806a30e744b4e21c9d73b7bee18a1cfa3c47991ee2e5a65b887c49d5cf",
-                "sha256:07b05cd3305e8a73112103c834e91cd27ce5b4bd07850c4b4dbd1877d3f45be7",
-                "sha256:086f92daf51a032d062ec5f58af5ca6a44d082c35299c96376a41cbb33034675",
-                "sha256:099ebd2c37ac74cce10a3527d2b49af80243e2a4fa39e7bce41617fbc35fa3c1",
-                "sha256:0c7ebbbde809ff4e970824b2b6cb7e4222be6b95a296e46c03cf050878fc1785",
-                "sha256:102e487eeb82afac440581e5d7f8f44560b36cf0bdd11abc51a46c1cd88914d4",
-                "sha256:11691cf4dc5b94236ccc609b70fec991234e7ef8d4c02dd0c9668d1e486f5abf",
-                "sha256:11a67c0d562e07067c4e86bffc1553f2cf5b664d6111c894671b2b8712f3aba5",
-                "sha256:12de6add4038df8f72fac606dff775791a60f113a725c960f2bab01d8b8e6b15",
-                "sha256:13487abd2f761d4be7c8ff9080de2671e53fff69711d46de703c310c4c9317ca",
-                "sha256:15b09b06dae900777833fe7fc4b4aa426556ce95847a3e8d7548e2d19e34edb8",
-                "sha256:1c182cb873bc91b411e184dab7a2b664d4fea2743df0e4d57402f7f3fa644bac",
-                "sha256:1ed0b6477896559f17b9eaeb6d38e07f7f9ffe40b9f0f9627ae8b9926ae260a8",
-                "sha256:28d490af82bc6b7ce53ff31337a18a10498303fe66f701ab65ef27e143c3b0ef",
-                "sha256:2e5d962cf7e1d426aa0e528a7e198658cdc8aa4fe87f781d039ad75dcd52c516",
-                "sha256:2ed076098b171573161eb146afcb9129b5ff63308960aeca4b676d9d3c35e700",
-                "sha256:2f2f69dca064926e79997f45b2f34e202b320fd3782f17a91941f7eb85502ee2",
-                "sha256:31560d268ff62143e92423ef183680b9829b1b482c011713ae941997921eebc8",
-                "sha256:31d1e1c0dbf19ebccbfd62eff461518dcb1e307b195e93bba60c965a4dcf1ba0",
-                "sha256:37951ad2f4a6df6506750a23f7cbabad24c73c65f23f72e95897bb2cecbae676",
-                "sha256:3af642b43ce56c24d063325dd2cf20ee012d2b9ba4c3c008755a301aaea720ad",
-                "sha256:44db35a9e15d6fe5c40d74952e803b1d96e964f683b5a78c3cc64eb177878155",
-                "sha256:473d93d4450880fe278696549f2e7aed8cd23708c3c1997981464475f32137db",
-                "sha256:477c3ea0ba410b2b56b7efb072c36fa91b1e6fc331761798fa3f28bb224830dd",
-                "sha256:4a4a4e30bf1edcad13fb0804300557aedd07a92cabc74382fdd0ba6ca2661091",
-                "sha256:4aed991a28ea3ce320dc8ce655875e1e00a11bdd29fe9444dd4f88c30d558602",
-                "sha256:51467000f3647d519272392f484126aa716f747859794ac9924a7aafa86cd411",
-                "sha256:55c3d1072704d27401c92339144d199d9de7b52627f724a949fc7d5fc56d8b93",
-                "sha256:589c72667a5febd36f1315aa6e5f56dd4aa4862df295cb51c769d16142ddd7cd",
-                "sha256:5bfde62d1d2641a1f5173b8c8c2d96ceb4854f54a44c23102e2ccc7e02f003ec",
-                "sha256:5c23b1ad869653bc818e972b7a3a79852d0e494e9ab7e1a701a3decc49c20d51",
-                "sha256:61bfc23df345d8c9716d03717c2ed5e27374e0fe6f659ea64edcd27b4b044cf7",
-                "sha256:6ae828d3a003f03ae31915c31fa684b9890ea44c9c989056fea96e3d12a9fa17",
-                "sha256:6c7cefb4b0640703eb1069835c02486669312bf2f12b48a748e0a7756d0de33d",
-                "sha256:6d69f36d445c45cda7b3b26afef2fc34ef5ac0cdc75584a87ef307ee3c8c6d00",
-                "sha256:6f0d5f33feb5f69ddd57a4a4bd3d56c719a141080b445cbf18f238973c5c9923",
-                "sha256:6f8b01295e26c68b3a1b90efb7a89029110d3a4139270b24fda961893216c440",
-                "sha256:713ac174a629d39b7c6a3aa757b337599798da4c1157114a314e4e391cd28e32",
-                "sha256:718626a174e7e467f0558954f94af117b7d4695d48eb980146016afa4b580b2e",
-                "sha256:7187a76598bdb895af0adbd2fb7474d7f6025d170bc0a1130242da817ce9e7d1",
-                "sha256:71927042ed6365a09a98a6377501af5c9f0a4d38083652bcd2281a06a5976724",
-                "sha256:7d08744e9bae2ca9c382581f7dce1273fe3c9bae94ff572c3626e8da5b193c6a",
-                "sha256:7dadf3c307b31e0e61689cbf9e06be7a867c563d5a63ce9dca578f956609abf8",
-                "sha256:81e3d8c34c623ca4e36c46524a3530e99c0bc95ed068fd6e9b55cb721d408fb2",
-                "sha256:844a9b460871ee0a0b0b68a64890dae9c415e513db0f4a7e3cab41a0f2fedf33",
-                "sha256:8b7ef7cbd4fec9a1e811a5de813311ed4f7ac7d93e0fda233c9b3e1428f7dd7b",
-                "sha256:97ef77eb6b044134c0b3a96e16abcb05ecce892965a2124c566af0fd60f717e2",
-                "sha256:99b5eeae8e019e7aad8af8bb314fb908dd2e028b3cdaad87ec05095394cce632",
-                "sha256:a25fa703a527158aaf10dafd956f7d42ac6d30ec80e9a70846253dd13e2f067b",
-                "sha256:a2f635ce61a89c5732537a7896b6319a8fcfa23ba09bec36e1b1ac0ab31270d2",
-                "sha256:a79004bb58748f31ae1cbe9fa891054baaa46fb106c2dc7af9f8e3304dc30316",
-                "sha256:a996d01ca39b8dfe77440f3cd600825d05841088fd6bc0144cc6c2ec14cc5f74",
-                "sha256:b0e20cddbd676ab8a64c774fefa0ad787cc506afd844de95da56060348021e96",
-                "sha256:b6613280ccedf24354406caf785db748bebbddcf31408b20c0b48cb86af76866",
-                "sha256:b9d00268fcb9f66fbcc7cd9fe423741d90c75ee029a1d15c09b22d23253c0a44",
-                "sha256:bb01ba6b0d3f6c68b89fce7305080145d4877ad3acaed424bae4d4ee75faa950",
-                "sha256:c2aef4703f1f2ddc6df17519885dbfa3514929149d3ff900b73f45998f2532fa",
-                "sha256:c34dc4958b232ef6188c4318cb7b2c2d80521c9a56c52449f8f93ab7bc2a8a1c",
-                "sha256:c3630c3ef435c0a7c549ba170a0633a56e92629aeed0e707fec832dee313fb7a",
-                "sha256:c3d6a4d0619e09dcd61021debf7059955c2004fa29f48788a3dfaf9c9901a7cd",
-                "sha256:d15367ce87c8e9e09b0f989bfd72dc641bcd04ba091c68cd305312d00962addd",
-                "sha256:d2f9b69293c33aaa53d923032fe227feac867f81682f002ce33ffae978f0a9a9",
-                "sha256:e999f2d0e12eea01caeecb17b653f3713d758f6dcc770417cf29ef08d3931421",
-                "sha256:ea302f34477fda3f85560a06d9ebdc7fa41e82420e892fc50b577e35fc6a50b2",
-                "sha256:eaba923151d9deea315be1f3e2b31cc39a6d1d2f682f942905951f4e40200922",
-                "sha256:ef9612483cb35171d51d9173647eed5d0069eaa2ee812793a75373447d487aa4",
-                "sha256:f5315a2eb0239185af1bddb1abf472d877fede3cc8d143c6cddad37678293237",
-                "sha256:fa0ffcace9b3aa34d205d8130f7873fcfefcb6a4dd3dd705b0dab69af6712642",
-                "sha256:fc5471e1a54de15ef71c1bc6ebe80d4dc681ea600e68bfd1cbce40427f0b7578"
-            ],
-            "version": "==3.8.1"
-        },
-        "aiohttp-cors": {
-            "hashes": [
-                "sha256:0451ba59fdf6909d0e2cd21e4c0a43752bc0703d33fc78ae94d9d9321710193e",
-                "sha256:4d39c6d7100fd9764ed1caf8cebf0eb01bf5e3f24e2e073fda6234bc48b19f5d"
-            ],
-            "version": "==0.7.0"
-        },
-        "aiosignal": {
-            "hashes": [
-                "sha256:26e62109036cd181df6e6ad646f91f0dcfd05fe16d0cb924138ff2ab75d64e3a",
-                "sha256:78ed67db6c7b7ced4f98e495e572106d5c432a93e1ddd1bf475e1dc05f5b7df2"
-            ],
-            "version": "==1.2.0"
-        },
-        "apischema": {
-            "hashes": [
-                "sha256:0c3f9337af3614e7518e518d238bd21a3aecbae9258a8b1efcba69a352b8b6e3",
-                "sha256:0e69a1374cbc1f9d1e213b7327de52e8af8acbbaa15d7a21d3b4d60c7a92817b",
-                "sha256:18c68e257d51a6440c9404a8069a6d2a0fb25780a54914d656c1312609e9da29",
-                "sha256:25404d21784901e5eaa1ad405c4d551713fc4d28b098b1c17c5a185a3b986331",
-                "sha256:26a56d03599cfc8241e559d2af9888717c1665d70d3e59ee58927e0e5351b477",
-                "sha256:2dedb9e5c4fe3528430191e3e10b6bf30f84ab38ca32829c98605a0f253dda6c",
-                "sha256:2e3c065055ef42ea858a59d900888cddf36893cee1e4715b9757b00538bcce0d",
-                "sha256:2f30ebb7b3b2031390f9cee27cd8e43fc0321748c05092ff99f1bd39a9914032",
-                "sha256:2f4712a29ca52d6fd4b80407cf748711fe65d5ac3bc1cb206e306d9b4d0f4dc8",
-                "sha256:2f49df499434041243b78bf669acaa00c9e0016b377c9a10676b27d0612beda4",
-                "sha256:3eb7ffea77fd3c5da384ac2efb163676dbe1f2b0ac914b20e84f9aee8e7363ab",
-                "sha256:40e03f594fe574159b2a13a22c793cb0aa9d90366aa1468e5796460026ee5743",
-                "sha256:415eb19a6638c4134f9db409504f9572ec24b25e480993ade75c97b6da12d2fe",
-                "sha256:533f763b5c53cd1aff7f69f373b307fe24e09f50cc6a213256fed0e45dbee6ad",
-                "sha256:590d8dd4fcded2cb0b84bed64ac6c98d164e59b71ffb7a49b9433153dc2506a1",
-                "sha256:5e354e9b5a0c7e2594cf2d0e70ee7cdea96ed245381167f94377cac0924fd3dd",
-                "sha256:65a2b2f50583b48224a197a147929cc42ee45edf77b1c6a0c23c6304a0844dc8",
-                "sha256:71b2dc4873f07bf223cda9f1d830128504482329cc0ebe487dead23e113c7f47",
-                "sha256:74aa5bace4ee8395857a942009e4c708d93a1dc6978a323dd46b1e735c2fff77",
-                "sha256:79275207c3ba957cb5428fe58ae87d57cec9868b95803c65979326f55eba9f81",
-                "sha256:7cf581781c3fbcebd129d9ddc07e3e96b17c2375cbce9a8f4eee3a7855f95818",
-                "sha256:7f18c91bcdf44e6201334841d342445e9d53943a69f6cb95368be9e14d3653cc",
-                "sha256:867f7afd928a760a7560868abe4107a35f680ddce7725be5d8e84df1660c160c",
-                "sha256:8eda7b5eb0b71847e544320bfcc526203c1213ce41e786da7dd6cc504c849920",
-                "sha256:93c88c23066d8b2333cb5bc30b86cc5ecae620a0b746fa9a9316ef0e595c1cc5",
-                "sha256:9d55bd627b3081b9bbf1e4f1250f1a138bf7f14d0d4dcc775b8ce8b972f0951a",
-                "sha256:a389388b4438e36c85a8229dfd99ceafe0ebda713f22ac3f0e2c0c37c5f14018",
-                "sha256:ce450f7be33e7c1ab98823bfb740cf9e8d311406622df7193d4d7fb99def2343",
-                "sha256:d005126ded0ea7373cf18bf0d40afa5953266eb8e68c56c2a2fb6aa6e63043f1",
-                "sha256:dd8c55f3ab806a051f253c67cdd35679862c706652b3297060f4c42edba6fb3c",
-                "sha256:e7f68b6b5b66751188933077401f06de40c5bbcdb55c3df9728591dd4483be3e",
-                "sha256:ea2342be76cc6ea3720350e8f69b308827715dcde28c90a29faf0889c838cb52",
-                "sha256:f7065058d02f57665be5fc68a51a62e1a0ec841f92e31f39f24f42507a5802b2",
-                "sha256:f73dd19b260ea75451ada019d9ff6a86a022fc36304b316ed6ca3c71537ffa01",
-                "sha256:f7ef99982cae7c109d7d01803d8daee2f47843d103b62d88777f1378ab21a694",
-                "sha256:fd9c7bf436681fdd22fe295895c41a2eba747ba76782b700232fb6585d36908e"
-            ],
-            "version": "==0.17.2"
-        },
-        "async-timeout": {
-            "hashes": [
-                "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15",
-                "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"
-            ],
-            "version": "==4.0.2"
-        },
-        "asynctest": {
-            "hashes": [
-                "sha256:5da6118a7e6d6b54d83a8f7197769d046922a44d2a99c21382f0a6e4fadae676",
-                "sha256:c27862842d15d83e6a34eb0b2866c323880eb3a75e4485b079ea11748fd77fac"
-            ],
-            "markers": "python_version < '3.8'",
-            "version": "==0.13.0"
-        },
-        "attrs": {
-            "hashes": [
-                "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1",
-                "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"
-            ],
-            "version": "==21.2.0"
-        },
-        "charset-normalizer": {
-            "hashes": [
-                "sha256:e019de665e2bcf9c2b64e2e5aa025fa991da8720daa3c1138cadd2fd1856aed0",
-                "sha256:f7af805c321bfa1ce6714c51f254e0d5bb5e5834039bc17db7ebe3a4cec9492b"
-            ],
-            "version": "==2.0.7"
-        },
-        "click": {
-            "hashes": [
-                "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3",
-                "sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b"
-            ],
-            "version": "==8.0.3"
-        },
-        "cycler": {
-            "hashes": [
-                "sha256:1d8a5ae1ff6c5cf9b93e8811e581232ad8920aeec647c37316ceac982b08cb2d",
-                "sha256:cd7b2d1018258d7247a71425e9f26463dfb444d411c39569972f4ce586b0c9d8"
-            ],
-            "version": "==0.10.0"
-        },
-        "fonttools": {
-            "hashes": [
-                "sha256:1933415e0fbdf068815cb1baaa1f159e17830215f7e8624e5731122761627557",
-                "sha256:2b18a172120e32128a80efee04cff487d5d140fe7d817deb648b2eee023a40e4"
-            ],
-            "version": "==4.29.1"
-        },
-        "frozenlist": {
-            "hashes": [
-                "sha256:006d3595e7d4108a12025ddf415ae0f6c9e736e726a5db0183326fd191b14c5e",
-                "sha256:01a73627448b1f2145bddb6e6c2259988bb8aee0fb361776ff8604b99616cd08",
-                "sha256:03a7dd1bfce30216a3f51a84e6dd0e4a573d23ca50f0346634916ff105ba6e6b",
-                "sha256:0437fe763fb5d4adad1756050cbf855bbb2bf0d9385c7bb13d7a10b0dd550486",
-                "sha256:04cb491c4b1c051734d41ea2552fde292f5f3a9c911363f74f39c23659c4af78",
-                "sha256:0c36e78b9509e97042ef869c0e1e6ef6429e55817c12d78245eb915e1cca7468",
-                "sha256:25af28b560e0c76fa41f550eacb389905633e7ac02d6eb3c09017fa1c8cdfde1",
-                "sha256:2fdc3cd845e5a1f71a0c3518528bfdbfe2efaf9886d6f49eacc5ee4fd9a10953",
-                "sha256:30530930410855c451bea83f7b272fb1c495ed9d5cc72895ac29e91279401db3",
-                "sha256:31977f84828b5bb856ca1eb07bf7e3a34f33a5cddce981d880240ba06639b94d",
-                "sha256:3c62964192a1c0c30b49f403495911298810bada64e4f03249ca35a33ca0417a",
-                "sha256:3f7c935c7b58b0d78c0beea0c7358e165f95f1fd8a7e98baa40d22a05b4a8141",
-                "sha256:40dff8962b8eba91fd3848d857203f0bd704b5f1fa2b3fc9af64901a190bba08",
-                "sha256:40ec383bc194accba825fbb7d0ef3dda5736ceab2375462f1d8672d9f6b68d07",
-                "sha256:436496321dad302b8b27ca955364a439ed1f0999311c393dccb243e451ff66aa",
-                "sha256:4406cfabef8f07b3b3af0f50f70938ec06d9f0fc26cbdeaab431cbc3ca3caeaa",
-                "sha256:45334234ec30fc4ea677f43171b18a27505bfb2dba9aca4398a62692c0ea8868",
-                "sha256:47be22dc27ed933d55ee55845d34a3e4e9f6fee93039e7f8ebadb0c2f60d403f",
-                "sha256:4a44ebbf601d7bac77976d429e9bdb5a4614f9f4027777f9e54fd765196e9d3b",
-                "sha256:4eda49bea3602812518765810af732229b4291d2695ed24a0a20e098c45a707b",
-                "sha256:57f4d3f03a18facacb2a6bcd21bccd011e3b75d463dc49f838fd699d074fabd1",
-                "sha256:603b9091bd70fae7be28bdb8aa5c9990f4241aa33abb673390a7f7329296695f",
-                "sha256:65bc6e2fece04e2145ab6e3c47428d1bbc05aede61ae365b2c1bddd94906e478",
-                "sha256:691ddf6dc50480ce49f68441f1d16a4c3325887453837036e0fb94736eae1e58",
-                "sha256:6983a31698490825171be44ffbafeaa930ddf590d3f051e397143a5045513b01",
-                "sha256:6a202458d1298ced3768f5a7d44301e7c86defac162ace0ab7434c2e961166e8",
-                "sha256:6eb275c6385dd72594758cbe96c07cdb9bd6becf84235f4a594bdf21e3596c9d",
-                "sha256:754728d65f1acc61e0f4df784456106e35afb7bf39cfe37227ab00436fb38676",
-                "sha256:768efd082074bb203c934e83a61654ed4931ef02412c2fbdecea0cff7ecd0274",
-                "sha256:772965f773757a6026dea111a15e6e2678fbd6216180f82a48a40b27de1ee2ab",
-                "sha256:871d42623ae15eb0b0e9df65baeee6976b2e161d0ba93155411d58ff27483ad8",
-                "sha256:88aafd445a233dbbf8a65a62bc3249a0acd0d81ab18f6feb461cc5a938610d24",
-                "sha256:8c905a5186d77111f02144fab5b849ab524f1e876a1e75205cd1386a9be4b00a",
-                "sha256:8cf829bd2e2956066dd4de43fd8ec881d87842a06708c035b37ef632930505a2",
-                "sha256:92e650bd09b5dda929523b9f8e7f99b24deac61240ecc1a32aeba487afcd970f",
-                "sha256:93641a51f89473837333b2f8100f3f89795295b858cd4c7d4a1f18e299dc0a4f",
-                "sha256:94c7a8a9fc9383b52c410a2ec952521906d355d18fccc927fca52ab575ee8b93",
-                "sha256:9f892d6a94ec5c7b785e548e42722e6f3a52f5f32a8461e82ac3e67a3bd073f1",
-                "sha256:acb267b09a509c1df5a4ca04140da96016f40d2ed183cdc356d237286c971b51",
-                "sha256:adac9700675cf99e3615eb6a0eb5e9f5a4143c7d42c05cea2e7f71c27a3d0846",
-                "sha256:aff388be97ef2677ae185e72dc500d19ecaf31b698986800d3fc4f399a5e30a5",
-                "sha256:b5009062d78a8c6890d50b4e53b0ddda31841b3935c1937e2ed8c1bda1c7fb9d",
-                "sha256:b684c68077b84522b5c7eafc1dc735bfa5b341fb011d5552ebe0968e22ed641c",
-                "sha256:b9e3e9e365991f8cc5f5edc1fd65b58b41d0514a6a7ad95ef5c7f34eb49b3d3e",
-                "sha256:bd89acd1b8bb4f31b47072615d72e7f53a948d302b7c1d1455e42622de180eae",
-                "sha256:bde99812f237f79eaf3f04ebffd74f6718bbd216101b35ac7955c2d47c17da02",
-                "sha256:c6c321dd013e8fc20735b92cb4892c115f5cdb82c817b1e5b07f6b95d952b2f0",
-                "sha256:ce6f2ba0edb7b0c1d8976565298ad2deba6f8064d2bebb6ffce2ca896eb35b0b",
-                "sha256:d2257aaba9660f78c7b1d8fea963b68f3feffb1a9d5d05a18401ca9eb3e8d0a3",
-                "sha256:d26b650b71fdc88065b7a21f8ace70175bcf3b5bdba5ea22df4bfd893e795a3b",
-                "sha256:d6d32ff213aef0fd0bcf803bffe15cfa2d4fde237d1d4838e62aec242a8362fa",
-                "sha256:e1e26ac0a253a2907d654a37e390904426d5ae5483150ce3adedb35c8c06614a",
-                "sha256:e30b2f9683812eb30cf3f0a8e9f79f8d590a7999f731cf39f9105a7c4a39489d",
-                "sha256:e84cb61b0ac40a0c3e0e8b79c575161c5300d1d89e13c0e02f76193982f066ed",
-                "sha256:e982878792c971cbd60ee510c4ee5bf089a8246226dea1f2138aa0bb67aff148",
-                "sha256:f20baa05eaa2bcd5404c445ec51aed1c268d62600362dc6cfe04fae34a424bd9",
-                "sha256:f7353ba3367473d1d616ee727945f439e027f0bb16ac1a750219a8344d1d5d3c",
-                "sha256:f96293d6f982c58ebebb428c50163d010c2f05de0cde99fd681bfdc18d4b2dc2",
-                "sha256:ff9310f05b9d9c5c4dd472983dc956901ee6cb2c3ec1ab116ecdde25f3ce4951"
-            ],
-            "version": "==1.3.0"
-        },
-        "graphql-core": {
-            "hashes": [
-                "sha256:0dda7e63676f119bb3d814621190fedad72fda07a8e9ab780bedd9f1957c6dc6",
-                "sha256:86e2a0be008bfde19ef78388de8a725a1d942a9190ca431c24a60837973803ce"
-            ],
-            "version": "==3.2.0"
-        },
-        "graphql-server": {
-            "extras": [
-                "aiohttp"
-            ],
-            "hashes": [
-                "sha256:85c12c421e14f339883865d75fe316f626f3f875c70f8c366a93d50c5b1c8a23"
-            ],
-            "version": "==3.0.0b5"
-        },
-        "idna": {
-            "hashes": [
-                "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff",
-                "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"
-            ],
-            "version": "==3.3"
-        },
-        "importlib-metadata": {
-            "hashes": [
-                "sha256:175f4ee440a0317f6e8d81b7f8d4869f93316170a65ad2b007d2929186c8052c",
-                "sha256:e0bc84ff355328a4adfc5240c4f211e0ab386f80aa640d1b11f0618a1d282094"
-            ],
-            "markers": "python_version < '3.8'",
-            "version": "==4.11.1"
-        },
-        "kiwisolver": {
-            "hashes": [
-                "sha256:0cd53f403202159b44528498de18f9285b04482bab2a6fc3f5dd8dbb9352e30d",
-                "sha256:1e1bc12fb773a7b2ffdeb8380609f4f8064777877b2225dec3da711b421fda31",
-                "sha256:225e2e18f271e0ed8157d7f4518ffbf99b9450fca398d561eb5c4a87d0986dd9",
-                "sha256:232c9e11fd7ac3a470d65cd67e4359eee155ec57e822e5220322d7b2ac84fbf0",
-                "sha256:31dfd2ac56edc0ff9ac295193eeaea1c0c923c0355bf948fbd99ed6018010b72",
-                "sha256:33449715e0101e4d34f64990352bce4095c8bf13bed1b390773fc0a7295967b3",
-                "sha256:401a2e9afa8588589775fe34fc22d918ae839aaaf0c0e96441c0fdbce6d8ebe6",
-                "sha256:44a62e24d9b01ba94ae7a4a6c3fb215dc4af1dde817e7498d901e229aaf50e4e",
-                "sha256:50af681a36b2a1dee1d3c169ade9fdc59207d3c31e522519181e12f1b3ba7000",
-                "sha256:563c649cfdef27d081c84e72a03b48ea9408c16657500c312575ae9d9f7bc1c3",
-                "sha256:5989db3b3b34b76c09253deeaf7fbc2707616f130e166996606c284395da3f18",
-                "sha256:5a7a7dbff17e66fac9142ae2ecafb719393aaee6a3768c9de2fd425c63b53e21",
-                "sha256:5c3e6455341008a054cccee8c5d24481bcfe1acdbc9add30aa95798e95c65621",
-                "sha256:5f6ccd3dd0b9739edcf407514016108e2280769c73a85b9e59aa390046dbf08b",
-                "sha256:72c99e39d005b793fb7d3d4e660aed6b6281b502e8c1eaf8ee8346023c8e03bc",
-                "sha256:78751b33595f7f9511952e7e60ce858c6d64db2e062afb325985ddbd34b5c131",
-                "sha256:834ee27348c4aefc20b479335fd422a2c69db55f7d9ab61721ac8cd83eb78882",
-                "sha256:8be8d84b7d4f2ba4ffff3665bcd0211318aa632395a1a41553250484a871d454",
-                "sha256:950a199911a8d94683a6b10321f9345d5a3a8433ec58b217ace979e18f16e248",
-                "sha256:a357fd4f15ee49b4a98b44ec23a34a95f1e00292a139d6015c11f55774ef10de",
-                "sha256:a53d27d0c2a0ebd07e395e56a1fbdf75ffedc4a05943daf472af163413ce9598",
-                "sha256:acef3d59d47dd85ecf909c359d0fd2c81ed33bdff70216d3956b463e12c38a54",
-                "sha256:b38694dcdac990a743aa654037ff1188c7a9801ac3ccc548d3341014bc5ca278",
-                "sha256:b9edd0110a77fc321ab090aaa1cfcaba1d8499850a12848b81be2222eab648f6",
-                "sha256:c08e95114951dc2090c4a630c2385bef681cacf12636fb0241accdc6b303fd81",
-                "sha256:c5518d51a0735b1e6cee1fdce66359f8d2b59c3ca85dc2b0813a8aa86818a030",
-                "sha256:c8fd0f1ae9d92b42854b2979024d7597685ce4ada367172ed7c09edf2cef9cb8",
-                "sha256:ca3820eb7f7faf7f0aa88de0e54681bddcb46e485beb844fcecbcd1c8bd01689",
-                "sha256:cf8b574c7b9aa060c62116d4181f3a1a4e821b2ec5cbfe3775809474113748d4",
-                "sha256:d3155d828dec1d43283bd24d3d3e0d9c7c350cdfcc0bd06c0ad1209c1bbc36d0",
-                "sha256:f8d6f8db88049a699817fd9178782867bf22283e3813064302ac59f61d95be05",
-                "sha256:fd34fbbfbc40628200730bc1febe30631347103fc8d3d4fa012c21ab9c11eca9"
-            ],
-            "version": "==1.3.1"
-        },
-        "matplotlib": {
-            "hashes": [
-                "sha256:0abf8b51cc6d3ba34d1b15b26e329f23879848a0cf1216954c1f432ffc7e1af7",
-                "sha256:0e020a42f3338823a393dd2f80e39a2c07b9f941dfe2c778eb104eeb33d60bb5",
-                "sha256:13930a0c9bec0fd25f43c448b047a21af1353328b946f044a8fc3be077c6b1a8",
-                "sha256:153a0cf6a6ff4f406a0600d2034710c49988bacc6313d193b32716f98a697580",
-                "sha256:18f6e52386300db5cc4d1e9019ad9da2e80658bab018834d963ebb0aa5355095",
-                "sha256:2089b9014792dcc87bb1d620cde847913338abf7d957ef05587382b0cb76d44e",
-                "sha256:2eea16883aa7724c95eea0eb473ab585c6cf66f0e28f7f13e63deb38f4fd6d0f",
-                "sha256:38892a254420d95594285077276162a5e9e9c30b6da08bdc2a4d53331ad9a6fa",
-                "sha256:4b018ea6f26424a0852eb60eb406420d9f0d34f65736ea7bbfbb104946a66d86",
-                "sha256:65f877882b7ddede7090c7d87be27a0f4720fe7fc6fddd4409c06e1aa0f1ae8d",
-                "sha256:666d717a4798eb9c5d3ae83fe80c7bc6ed696b93e879cb01cb24a74155c73612",
-                "sha256:66b172610db0ececebebb09d146f54205f87c7b841454e408fba854764f91bdd",
-                "sha256:6db02c5605f063b67780f4d5753476b6a4944343284aa4e93c5e8ff6e9ec7f76",
-                "sha256:6e0e6b2111165522ad336705499b1f968c34a9e84d05d498ee5af0b5697d1efe",
-                "sha256:71a1851111f23f82fc43d2b6b2bfdd3f760579a664ebc939576fe21cc6133d01",
-                "sha256:7a7cb59ebd63a8ac4542ec1c61dd08724f82ec3aa7bb6b4b9e212d43c611ce3d",
-                "sha256:7baf23adb698d8c6ca7339c9dde00931bc47b2dd82fa912827fef9f93db77f5e",
-                "sha256:970aa97297537540369d05fe0fd1bb952593f9ab696c9b427c06990a83e2418b",
-                "sha256:9bac8eb1eccef540d7f4e844b6313d9f7722efd48c07e1b4bfec1056132127fd",
-                "sha256:a07ff2565da72a7b384a9e000b15b6b8270d81370af8a3531a16f6fbcee023cc",
-                "sha256:a0dcaf5648cecddc328e81a0421821a1f65a1d517b20746c94a1f0f5c36fb51a",
-                "sha256:a0ea10faa3bab0714d3a19c7e0921279a68d57552414d6eceaea99f97d7735db",
-                "sha256:a5b62d1805cc83d755972033c05cea78a1e177a159fc84da5c9c4ab6303ccbd9",
-                "sha256:a6cef5b31e27c31253c0f852b629a38d550ae66ec6850129c49d872f9ee428cb",
-                "sha256:a7bf8b05c214d32fb7ca7c001fde70b9b426378e897b0adbf77b85ea3569d56a",
-                "sha256:ac17a7e7b06ee426a4989f0b7f24ab1a592e39cdf56353a90f4e998bc0bf44d6",
-                "sha256:b3b687e905da32e5f2e5f16efa713f5d1fcd9fb8b8c697895de35c91fedeb086",
-                "sha256:b5e439d9e55d645f2a4dca63e2f66d68fe974c405053b132d61c7e98c25dfeb2",
-                "sha256:ba107add08e12600b072cf3c47aaa1ab85dd4d3c48107a5d3377d1bf80f8b235",
-                "sha256:d092b7ba63182d2dd427904e3eb58dd5c46ec67c5968de14a4b5007010a3a4cc",
-                "sha256:dc8c5c23e7056e126275dbf29efba817b3d94196690930d0968873ac3a94ab82",
-                "sha256:df0042cab69f4d246f4cb8fc297770ac4ae6ec2983f61836b04a117722037dcd",
-                "sha256:ee3d9ff16d749a9aa521bd7d86f0dbf256b2d2ac8ce31b19e4d2c86d2f2ff0b6",
-                "sha256:f23fbf70d2e80f4e03a83fc1206a8306d9bc50482fee4239f10676ce7e470c83",
-                "sha256:ff5d9fe518ad2de14ce82ab906b6ab5c2b0c7f4f984400ff8a7a905daa580a0a"
-            ],
-            "version": "==3.5.0"
-        },
-        "multidict": {
-            "hashes": [
-                "sha256:018132dbd8688c7a69ad89c4a3f39ea2f9f33302ebe567a879da8f4ca73f0d0a",
-                "sha256:051012ccee979b2b06be928a6150d237aec75dd6bf2d1eeeb190baf2b05abc93",
-                "sha256:05c20b68e512166fddba59a918773ba002fdd77800cad9f55b59790030bab632",
-                "sha256:07b42215124aedecc6083f1ce6b7e5ec5b50047afa701f3442054373a6deb656",
-                "sha256:0e3c84e6c67eba89c2dbcee08504ba8644ab4284863452450520dad8f1e89b79",
-                "sha256:0e929169f9c090dae0646a011c8b058e5e5fb391466016b39d21745b48817fd7",
-                "sha256:1ab820665e67373de5802acae069a6a05567ae234ddb129f31d290fc3d1aa56d",
-                "sha256:25b4e5f22d3a37ddf3effc0710ba692cfc792c2b9edfb9c05aefe823256e84d5",
-                "sha256:2e68965192c4ea61fff1b81c14ff712fc7dc15d2bd120602e4a3494ea6584224",
-                "sha256:2f1a132f1c88724674271d636e6b7351477c27722f2ed789f719f9e3545a3d26",
-                "sha256:37e5438e1c78931df5d3c0c78ae049092877e5e9c02dd1ff5abb9cf27a5914ea",
-                "sha256:3a041b76d13706b7fff23b9fc83117c7b8fe8d5fe9e6be45eee72b9baa75f348",
-                "sha256:3a4f32116f8f72ecf2a29dabfb27b23ab7cdc0ba807e8459e59a93a9be9506f6",
-                "sha256:46c73e09ad374a6d876c599f2328161bcd95e280f84d2060cf57991dec5cfe76",
-                "sha256:46dd362c2f045095c920162e9307de5ffd0a1bfbba0a6e990b344366f55a30c1",
-                "sha256:4b186eb7d6ae7c06eb4392411189469e6a820da81447f46c0072a41c748ab73f",
-                "sha256:54fd1e83a184e19c598d5e70ba508196fd0bbdd676ce159feb412a4a6664f952",
-                "sha256:585fd452dd7782130d112f7ddf3473ffdd521414674c33876187e101b588738a",
-                "sha256:5cf3443199b83ed9e955f511b5b241fd3ae004e3cb81c58ec10f4fe47c7dce37",
-                "sha256:6a4d5ce640e37b0efcc8441caeea8f43a06addace2335bd11151bc02d2ee31f9",
-                "sha256:7df80d07818b385f3129180369079bd6934cf70469f99daaebfac89dca288359",
-                "sha256:806068d4f86cb06af37cd65821554f98240a19ce646d3cd24e1c33587f313eb8",
-                "sha256:830f57206cc96ed0ccf68304141fec9481a096c4d2e2831f311bde1c404401da",
-                "sha256:929006d3c2d923788ba153ad0de8ed2e5ed39fdbe8e7be21e2f22ed06c6783d3",
-                "sha256:9436dc58c123f07b230383083855593550c4d301d2532045a17ccf6eca505f6d",
-                "sha256:9dd6e9b1a913d096ac95d0399bd737e00f2af1e1594a787e00f7975778c8b2bf",
-                "sha256:ace010325c787c378afd7f7c1ac66b26313b3344628652eacd149bdd23c68841",
-                "sha256:b47a43177a5e65b771b80db71e7be76c0ba23cc8aa73eeeb089ed5219cdbe27d",
-                "sha256:b797515be8743b771aa868f83563f789bbd4b236659ba52243b735d80b29ed93",
-                "sha256:b7993704f1a4b204e71debe6095150d43b2ee6150fa4f44d6d966ec356a8d61f",
-                "sha256:d5c65bdf4484872c4af3150aeebe101ba560dcfb34488d9a8ff8dbcd21079647",
-                "sha256:d81eddcb12d608cc08081fa88d046c78afb1bf8107e6feab5d43503fea74a635",
-                "sha256:dc862056f76443a0db4509116c5cd480fe1b6a2d45512a653f9a855cc0517456",
-                "sha256:ecc771ab628ea281517e24fd2c52e8f31c41e66652d07599ad8818abaad38cda",
-                "sha256:f200755768dc19c6f4e2b672421e0ebb3dd54c38d5a4f262b872d8cfcc9e93b5",
-                "sha256:f21756997ad8ef815d8ef3d34edd98804ab5ea337feedcd62fb52d22bf531281",
-                "sha256:fc13a9524bc18b6fb6e0dbec3533ba0496bbed167c56d0aabefd965584557d80"
-            ],
-            "version": "==5.1.0"
-        },
-        "numpy": {
-            "hashes": [
-                "sha256:00c9fa73a6989895b8815d98300a20ac993c49ac36c8277e8ffeaa3631c0dbbb",
-                "sha256:025b497014bc33fc23897859350f284323f32a2fff7654697f5a5fc2a19e9939",
-                "sha256:08de8472d9f7571f9d51b27b75e827f5296295fa78817032e84464be8bb905bc",
-                "sha256:1964db2d4a00348b7a60ee9d013c8cb0c566644a589eaa80995126eac3b99ced",
-                "sha256:2a9add27d7fc0fdb572abc3b2486eb3b1395da71e0254c5552b2aad2a18b5441",
-                "sha256:2d8adfca843bc46ac199a4645233f13abf2011a0b2f4affc5c37cd552626f27b",
-                "sha256:301e408a052fdcda5cdcf03021ebafc3c6ea093021bf9d1aa47c54d48bdad166",
-                "sha256:311283acf880cfcc20369201bd75da907909afc4666966c7895cbed6f9d2c640",
-                "sha256:341dddcfe3b7b6427a28a27baa59af5ad51baa59bfec3264f1ab287aa3b30b13",
-                "sha256:3a5098df115340fb17fc93867317a947e1dcd978c3888c5ddb118366095851f8",
-                "sha256:3c978544be9e04ed12016dd295a74283773149b48f507d69b36f91aa90a643e5",
-                "sha256:3d893b0871322eaa2f8c7072cdb552d8e2b27645b7875a70833c31e9274d4611",
-                "sha256:4fe6a006557b87b352c04596a6e3f12a57d6e5f401d804947bd3188e6b0e0e76",
-                "sha256:507c05c7a37b3683eb08a3ff993bd1ee1e6c752f77c2f275260533b265ecdb6c",
-                "sha256:58ca1d7c8aef6e996112d0ce873ac9dfa1eaf4a1196b4ff7ff73880a09923ba7",
-                "sha256:61bada43d494515d5b122f4532af226fdb5ee08fe5b5918b111279843dc6836a",
-                "sha256:69a5a8d71c308d7ef33ef72371c2388a90e3495dbb7993430e674006f94797d5",
-                "sha256:6a5928bc6241264dce5ed509e66f33676fc97f464e7a919edc672fb5532221ee",
-                "sha256:7b9d6b14fc9a4864b08d1ba57d732b248f0e482c7b2ff55c313137e3ed4d8449",
-                "sha256:a7c4b701ca418cd39e28ec3b496e6388fe06de83f5f0cb74794fa31cfa384c02",
-                "sha256:a7e8f6216f180f3fd4efb73de5d1eaefb5f5a1ee5b645c67333033e39440e63a",
-                "sha256:b545ebadaa2b878c8630e5bcdb97fc4096e779f335fc0f943547c1c91540c815",
-                "sha256:c293d3c0321996cd8ffe84215ffe5d269fd9d1d12c6f4ffe2b597a7c30d3e593",
-                "sha256:c5562bcc1a9b61960fc8950ade44d00e3de28f891af0acc96307c73613d18f6e",
-                "sha256:ca9c23848292c6fe0a19d212790e62f398fd9609aaa838859be8459bfbe558aa",
-                "sha256:cc1b30205d138d1005adb52087ff45708febbef0e420386f58664f984ef56954",
-                "sha256:dbce7adeb66b895c6aaa1fad796aaefc299ced597f6fbd9ceddb0dd735245354",
-                "sha256:dc4b2fb01f1b4ddbe2453468ea0719f4dbb1f5caa712c8b21bb3dd1480cd30d9",
-                "sha256:eed2afaa97ec33b4411995be12f8bdb95c87984eaa28d76cf628970c8a2d689a",
-                "sha256:fc7a7d7b0ed72589fd8b8486b9b42a564f10b8762be8bd4d9df94b807af4a089"
-            ],
-            "version": "==1.21.5"
-        },
-        "packaging": {
-            "hashes": [
-                "sha256:096d689d78ca690e4cd8a89568ba06d07ca097e3306a4381635073ca91479966",
-                "sha256:14317396d1e8cdb122989b916fa2c7e9ca8e2be9e8060a6eff75b6b7b4d8a7e0"
-            ],
-            "version": "==21.2"
-        },
-        "pillow": {
-            "hashes": [
-                "sha256:01425106e4e8cee195a411f729cff2a7d61813b0b11737c12bd5991f5f14bcd5",
-                "sha256:031a6c88c77d08aab84fecc05c3cde8414cd6f8406f4d2b16fed1e97634cc8a4",
-                "sha256:083781abd261bdabf090ad07bb69f8f5599943ddb539d64497ed021b2a67e5a9",
-                "sha256:0d19d70ee7c2ba97631bae1e7d4725cdb2ecf238178096e8c82ee481e189168a",
-                "sha256:0e04d61f0064b545b989126197930807c86bcbd4534d39168f4aa5fda39bb8f9",
-                "sha256:12e5e7471f9b637762453da74e390e56cc43e486a88289995c1f4c1dc0bfe727",
-                "sha256:22fd0f42ad15dfdde6c581347eaa4adb9a6fc4b865f90b23378aa7914895e120",
-                "sha256:238c197fc275b475e87c1453b05b467d2d02c2915fdfdd4af126145ff2e4610c",
-                "sha256:3b570f84a6161cf8865c4e08adf629441f56e32f180f7aa4ccbd2e0a5a02cba2",
-                "sha256:463822e2f0d81459e113372a168f2ff59723e78528f91f0bd25680ac185cf797",
-                "sha256:4d98abdd6b1e3bf1a1cbb14c3895226816e666749ac040c4e2554231068c639b",
-                "sha256:5afe6b237a0b81bd54b53f835a153770802f164c5570bab5e005aad693dab87f",
-                "sha256:5b70110acb39f3aff6b74cf09bb4169b167e2660dabc304c1e25b6555fa781ef",
-                "sha256:5cbf3e3b1014dddc45496e8cf38b9f099c95a326275885199f427825c6522232",
-                "sha256:624b977355cde8b065f6d51b98497d6cd5fbdd4f36405f7a8790e3376125e2bb",
-                "sha256:63728564c1410d99e6d1ae8e3b810fe012bc440952168af0a2877e8ff5ab96b9",
-                "sha256:66cc56579fd91f517290ab02c51e3a80f581aba45fd924fcdee01fa06e635812",
-                "sha256:6c32cc3145928c4305d142ebec682419a6c0a8ce9e33db900027ddca1ec39178",
-                "sha256:8bb1e155a74e1bfbacd84555ea62fa21c58e0b4e7e6b20e4447b8d07990ac78b",
-                "sha256:95d5ef984eff897850f3a83883363da64aae1000e79cb3c321915468e8c6add5",
-                "sha256:a013cbe25d20c2e0c4e85a9daf438f85121a4d0344ddc76e33fd7e3965d9af4b",
-                "sha256:a787ab10d7bb5494e5f76536ac460741788f1fbce851068d73a87ca7c35fc3e1",
-                "sha256:a7d5e9fad90eff8f6f6106d3b98b553a88b6f976e51fce287192a5d2d5363713",
-                "sha256:aac00e4bc94d1b7813fe882c28990c1bc2f9d0e1aa765a5f2b516e8a6a16a9e4",
-                "sha256:b91c36492a4bbb1ee855b7d16fe51379e5f96b85692dc8210831fbb24c43e484",
-                "sha256:c03c07ed32c5324939b19e36ae5f75c660c81461e312a41aea30acdd46f93a7c",
-                "sha256:c5236606e8570542ed424849f7852a0ff0bce2c4c8d0ba05cc202a5a9c97dee9",
-                "sha256:c6b39294464b03457f9064e98c124e09008b35a62e3189d3513e5148611c9388",
-                "sha256:cb7a09e173903541fa888ba010c345893cd9fc1b5891aaf060f6ca77b6a3722d",
-                "sha256:d68cb92c408261f806b15923834203f024110a2e2872ecb0bd2a110f89d3c602",
-                "sha256:dc38f57d8f20f06dd7c3161c59ca2c86893632623f33a42d592f097b00f720a9",
-                "sha256:e98eca29a05913e82177b3ba3d198b1728e164869c613d76d0de4bde6768a50e",
-                "sha256:f217c3954ce5fd88303fc0c317af55d5e0204106d86dea17eb8205700d47dec2"
-            ],
-            "version": "==8.2.0"
-        },
-        "pyparsing": {
-            "hashes": [
-                "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1",
-                "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"
-            ],
-            "version": "==2.4.7"
-        },
-        "python-dateutil": {
-            "hashes": [
-                "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c",
-                "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"
-            ],
-            "version": "==2.8.1"
-        },
-        "scanspec": {
-            "editable": true,
-            "extras": [
-                "graphql",
-                "plotting"
-            ],
-            "path": "."
-        },
-        "scipy": {
-            "hashes": [
-                "sha256:01b38dec7e9f897d4db04f8de4e20f0f5be3feac98468188a0f47a991b796055",
-                "sha256:10dbcc7de03b8d635a1031cb18fd3eaa997969b64fdf78f99f19ac163a825445",
-                "sha256:19aeac1ad3e57338723f4657ac8520f41714804568f2e30bd547d684d72c392e",
-                "sha256:1b21c6e0dc97b1762590b70dee0daddb291271be0580384d39f02c480b78290a",
-                "sha256:1caade0ede6967cc675e235c41451f9fb89ae34319ddf4740194094ab736b88d",
-                "sha256:23995dfcf269ec3735e5a8c80cfceaf384369a47699df111a6246b83a55da582",
-                "sha256:2a799714bf1f791fb2650d73222b248d18d53fd40d6af2df2c898db048189606",
-                "sha256:3274ce145b5dc416c49c0cf8b6119f787f0965cd35e22058fe1932c09fe15d77",
-                "sha256:33d1677d46111cfa1c84b87472a0274dde9ef4a7ef2e1f155f012f5f1e995d8f",
-                "sha256:44d452850f77e65e25b1eb1ac01e25770323a782bfe3a1a3e43847ad4266d93d",
-                "sha256:9e3302149a369697c6aaea18b430b216e3c88f9a61b62869f6104881e5f9ef85",
-                "sha256:a75b014d3294fce26852a9d04ea27b5671d86736beb34acdfc05859246260707",
-                "sha256:ad7269254de06743fb4768f658753de47d8b54e4672c5ebe8612a007a088bd48",
-                "sha256:b30280fbc1fd8082ac822994a98632111810311a9ece71a0e48f739df3c555a2",
-                "sha256:b79104878003487e2b4639a20b9092b02e1bad07fc4cf924b495cf413748a777",
-                "sha256:d449d40e830366b4c612692ad19fbebb722b6b847f78a7b701b1e0d6cda3cc13",
-                "sha256:d647757373985207af3343301d89fe738d5a294435a4f2aafb04c13b4388c896",
-                "sha256:f68eb46b86b2c246af99fcaa6f6e37c7a7a413e1084a794990b877f2ff71f7b6",
-                "sha256:fdf606341cd798530b05705c87779606fcdfaf768a8129c348ea94441da15b04"
-            ],
-            "version": "==1.6.3"
-        },
-        "setuptools-scm": {
-            "hashes": [
-                "sha256:6833ac65c6ed9711a4d5d2266f8024cfa07c533a0e55f4c12f6eff280a5a9e30",
-                "sha256:acea13255093849de7ccb11af9e1fb8bde7067783450cee9ef7a93139bddf6d4"
-            ],
-            "version": "==6.4.2"
-        },
-        "six": {
-            "hashes": [
-                "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926",
-                "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"
-            ],
-            "version": "==1.16.0"
-        },
-        "tomli": {
-            "hashes": [
-                "sha256:c6ce0015eb38820eaf32b5db832dbc26deb3dd427bd5f6556cf0acac2c214fee",
-                "sha256:f04066f68f5554911363063a30b108d2b5a5b1a010aa8b6132af78489fe3aade"
-            ],
-            "version": "==1.2.2"
-        },
-        "typing-extensions": {
-            "hashes": [
-                "sha256:4ca091dea149f945ec56afb48dae714f21e8692ef22a395223bcd328961b6a0e",
-                "sha256:7f001e5ac290a0c0401508864c7ec868be4e701886d5b573a9528ed3973d9d3b"
-            ],
-            "markers": "python_version < '3.8' and python_version < '3.8'",
-            "version": "==4.0.1"
-        },
-        "yarl": {
-            "hashes": [
-                "sha256:00d7ad91b6583602eb9c1d085a2cf281ada267e9a197e8b7cae487dadbfa293e",
-                "sha256:0355a701b3998dcd832d0dc47cc5dedf3874f966ac7f870e0f3a6788d802d434",
-                "sha256:15263c3b0b47968c1d90daa89f21fcc889bb4b1aac5555580d74565de6836366",
-                "sha256:2ce4c621d21326a4a5500c25031e102af589edb50c09b321049e388b3934eec3",
-                "sha256:31ede6e8c4329fb81c86706ba8f6bf661a924b53ba191b27aa5fcee5714d18ec",
-                "sha256:324ba3d3c6fee56e2e0b0d09bf5c73824b9f08234339d2b788af65e60040c959",
-                "sha256:329412812ecfc94a57cd37c9d547579510a9e83c516bc069470db5f75684629e",
-                "sha256:4736eaee5626db8d9cda9eb5282028cc834e2aeb194e0d8b50217d707e98bb5c",
-                "sha256:4953fb0b4fdb7e08b2f3b3be80a00d28c5c8a2056bb066169de00e6501b986b6",
-                "sha256:4c5bcfc3ed226bf6419f7a33982fb4b8ec2e45785a0561eb99274ebbf09fdd6a",
-                "sha256:547f7665ad50fa8563150ed079f8e805e63dd85def6674c97efd78eed6c224a6",
-                "sha256:5b883e458058f8d6099e4420f0cc2567989032b5f34b271c0827de9f1079a424",
-                "sha256:63f90b20ca654b3ecc7a8d62c03ffa46999595f0167d6450fa8383bab252987e",
-                "sha256:68dc568889b1c13f1e4745c96b931cc94fdd0defe92a72c2b8ce01091b22e35f",
-                "sha256:69ee97c71fee1f63d04c945f56d5d726483c4762845400a6795a3b75d56b6c50",
-                "sha256:6d6283d8e0631b617edf0fd726353cb76630b83a089a40933043894e7f6721e2",
-                "sha256:72a660bdd24497e3e84f5519e57a9ee9220b6f3ac4d45056961bf22838ce20cc",
-                "sha256:73494d5b71099ae8cb8754f1df131c11d433b387efab7b51849e7e1e851f07a4",
-                "sha256:7356644cbed76119d0b6bd32ffba704d30d747e0c217109d7979a7bc36c4d970",
-                "sha256:8a9066529240171b68893d60dca86a763eae2139dd42f42106b03cf4b426bf10",
-                "sha256:8aa3decd5e0e852dc68335abf5478a518b41bf2ab2f330fe44916399efedfae0",
-                "sha256:97b5bdc450d63c3ba30a127d018b866ea94e65655efaf889ebeabc20f7d12406",
-                "sha256:9ede61b0854e267fd565e7527e2f2eb3ef8858b301319be0604177690e1a3896",
-                "sha256:b2e9a456c121e26d13c29251f8267541bd75e6a1ccf9e859179701c36a078643",
-                "sha256:b5dfc9a40c198334f4f3f55880ecf910adebdcb2a0b9a9c23c9345faa9185721",
-                "sha256:bafb450deef6861815ed579c7a6113a879a6ef58aed4c3a4be54400ae8871478",
-                "sha256:c49ff66d479d38ab863c50f7bb27dee97c6627c5fe60697de15529da9c3de724",
-                "sha256:ce3beb46a72d9f2190f9e1027886bfc513702d748047b548b05dab7dfb584d2e",
-                "sha256:d26608cf178efb8faa5ff0f2d2e77c208f471c5a3709e577a7b3fd0445703ac8",
-                "sha256:d597767fcd2c3dc49d6eea360c458b65643d1e4dbed91361cf5e36e53c1f8c96",
-                "sha256:d5c32c82990e4ac4d8150fd7652b972216b204de4e83a122546dce571c1bdf25",
-                "sha256:d8d07d102f17b68966e2de0e07bfd6e139c7c02ef06d3a0f8d2f0f055e13bb76",
-                "sha256:e46fba844f4895b36f4c398c5af062a9808d1f26b2999c58909517384d5deda2",
-                "sha256:e6b5460dc5ad42ad2b36cca524491dfcaffbfd9c8df50508bddc354e787b8dc2",
-                "sha256:f040bcc6725c821a4c0665f3aa96a4d0805a7aaf2caf266d256b8ed71b9f041c",
-                "sha256:f0b059678fd549c66b89bed03efcabb009075bd131c248ecdf087bdb6faba24a",
-                "sha256:fcbb48a93e8699eae920f8d92f7160c03567b421bc17362a9ffbbd706a816f71"
-            ],
-            "version": "==1.6.3"
-        },
-        "zipp": {
-            "hashes": [
-                "sha256:71c644c5369f4a6e07636f0aa966270449561fcea2e3d6747b8d23efaa9d7832",
-                "sha256:9fe5ea21568a0a70e50f273397638d39b03353731e6cbbb3fd8502a33fec40bc"
-            ],
-            "version": "==3.6.0"
-        }
-    },
-    "develop": {
-        "alabaster": {
-            "hashes": [
-                "sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359",
-                "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"
-            ],
-            "version": "==0.7.12"
-        },
-        "apischema": {
-            "hashes": [
-                "sha256:0c3f9337af3614e7518e518d238bd21a3aecbae9258a8b1efcba69a352b8b6e3",
-                "sha256:0e69a1374cbc1f9d1e213b7327de52e8af8acbbaa15d7a21d3b4d60c7a92817b",
-                "sha256:18c68e257d51a6440c9404a8069a6d2a0fb25780a54914d656c1312609e9da29",
-                "sha256:25404d21784901e5eaa1ad405c4d551713fc4d28b098b1c17c5a185a3b986331",
-                "sha256:26a56d03599cfc8241e559d2af9888717c1665d70d3e59ee58927e0e5351b477",
-                "sha256:2dedb9e5c4fe3528430191e3e10b6bf30f84ab38ca32829c98605a0f253dda6c",
-                "sha256:2e3c065055ef42ea858a59d900888cddf36893cee1e4715b9757b00538bcce0d",
-                "sha256:2f30ebb7b3b2031390f9cee27cd8e43fc0321748c05092ff99f1bd39a9914032",
-                "sha256:2f4712a29ca52d6fd4b80407cf748711fe65d5ac3bc1cb206e306d9b4d0f4dc8",
-                "sha256:2f49df499434041243b78bf669acaa00c9e0016b377c9a10676b27d0612beda4",
-                "sha256:3eb7ffea77fd3c5da384ac2efb163676dbe1f2b0ac914b20e84f9aee8e7363ab",
-                "sha256:40e03f594fe574159b2a13a22c793cb0aa9d90366aa1468e5796460026ee5743",
-                "sha256:415eb19a6638c4134f9db409504f9572ec24b25e480993ade75c97b6da12d2fe",
-                "sha256:533f763b5c53cd1aff7f69f373b307fe24e09f50cc6a213256fed0e45dbee6ad",
-                "sha256:590d8dd4fcded2cb0b84bed64ac6c98d164e59b71ffb7a49b9433153dc2506a1",
-                "sha256:5e354e9b5a0c7e2594cf2d0e70ee7cdea96ed245381167f94377cac0924fd3dd",
-                "sha256:65a2b2f50583b48224a197a147929cc42ee45edf77b1c6a0c23c6304a0844dc8",
-                "sha256:71b2dc4873f07bf223cda9f1d830128504482329cc0ebe487dead23e113c7f47",
-                "sha256:74aa5bace4ee8395857a942009e4c708d93a1dc6978a323dd46b1e735c2fff77",
-                "sha256:79275207c3ba957cb5428fe58ae87d57cec9868b95803c65979326f55eba9f81",
-                "sha256:7cf581781c3fbcebd129d9ddc07e3e96b17c2375cbce9a8f4eee3a7855f95818",
-                "sha256:7f18c91bcdf44e6201334841d342445e9d53943a69f6cb95368be9e14d3653cc",
-                "sha256:867f7afd928a760a7560868abe4107a35f680ddce7725be5d8e84df1660c160c",
-                "sha256:8eda7b5eb0b71847e544320bfcc526203c1213ce41e786da7dd6cc504c849920",
-                "sha256:93c88c23066d8b2333cb5bc30b86cc5ecae620a0b746fa9a9316ef0e595c1cc5",
-                "sha256:9d55bd627b3081b9bbf1e4f1250f1a138bf7f14d0d4dcc775b8ce8b972f0951a",
-                "sha256:a389388b4438e36c85a8229dfd99ceafe0ebda713f22ac3f0e2c0c37c5f14018",
-                "sha256:ce450f7be33e7c1ab98823bfb740cf9e8d311406622df7193d4d7fb99def2343",
-                "sha256:d005126ded0ea7373cf18bf0d40afa5953266eb8e68c56c2a2fb6aa6e63043f1",
-                "sha256:dd8c55f3ab806a051f253c67cdd35679862c706652b3297060f4c42edba6fb3c",
-                "sha256:e7f68b6b5b66751188933077401f06de40c5bbcdb55c3df9728591dd4483be3e",
-                "sha256:ea2342be76cc6ea3720350e8f69b308827715dcde28c90a29faf0889c838cb52",
-                "sha256:f7065058d02f57665be5fc68a51a62e1a0ec841f92e31f39f24f42507a5802b2",
-                "sha256:f73dd19b260ea75451ada019d9ff6a86a022fc36304b316ed6ca3c71537ffa01",
-                "sha256:f7ef99982cae7c109d7d01803d8daee2f47843d103b62d88777f1378ab21a694",
-                "sha256:fd9c7bf436681fdd22fe295895c41a2eba747ba76782b700232fb6585d36908e"
-            ],
-            "version": "==0.17.2"
-        },
-        "appdirs": {
-            "hashes": [
-                "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41",
-                "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"
-            ],
-            "version": "==1.4.4"
-        },
-        "aspy.yaml": {
-            "hashes": [
-                "sha256:463372c043f70160a9ec950c3f1e4c3a82db5fca01d334b6bc89c7164d744bdc"
-            ],
-            "version": "==1.3.0"
-        },
-        "attrs": {
-            "hashes": [
-                "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1",
-                "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"
-            ],
-            "version": "==21.2.0"
-        },
-        "babel": {
-            "hashes": [
-                "sha256:ab49e12b91d937cd11f0b67cb259a57ab4ad2b59ac7a3b41d6c06c0ac5b0def9",
-                "sha256:bc0c176f9f6a994582230df350aa6e05ba2ebe4b3ac317eab29d9be5d2768da0"
-            ],
-            "version": "==2.9.1"
-        },
-        "black": {
-            "hashes": [
-                "sha256:06f9d8846f2340dfac80ceb20200ea5d1b3f181dd0556b47af4e8e0b24fa0a6b",
-                "sha256:10dbe6e6d2988049b4655b2b739f98785a884d4d6b85bc35133a8fb9a2233176",
-                "sha256:2497f9c2386572e28921fa8bec7be3e51de6801f7459dffd6e62492531c47e09",
-                "sha256:30d78ba6bf080eeaf0b7b875d924b15cd46fec5fd044ddfbad38c8ea9171043a",
-                "sha256:328efc0cc70ccb23429d6be184a15ce613f676bdfc85e5fe8ea2a9354b4e9015",
-                "sha256:35020b8886c022ced9282b51b5a875b6d1ab0c387b31a065b84db7c33085ca79",
-                "sha256:5795a0375eb87bfe902e80e0c8cfaedf8af4d49694d69161e5bd3206c18618bb",
-                "sha256:5891ef8abc06576985de8fa88e95ab70641de6c1fca97e2a15820a9b69e51b20",
-                "sha256:637a4014c63fbf42a692d22b55d8ad6968a946b4a6ebc385c5505d9625b6a464",
-                "sha256:67c8301ec94e3bcc8906740fe071391bce40a862b7be0b86fb5382beefecd968",
-                "sha256:6d2fc92002d44746d3e7db7cf9313cf4452f43e9ea77a2c939defce3b10b5c82",
-                "sha256:6ee227b696ca60dd1c507be80a6bc849a5a6ab57ac7352aad1ffec9e8b805f21",
-                "sha256:863714200ada56cbc366dc9ae5291ceb936573155f8bf8e9de92aef51f3ad0f0",
-                "sha256:9b542ced1ec0ceeff5b37d69838106a6348e60db7b8fdd245294dc1d26136265",
-                "sha256:a6342964b43a99dbc72f72812bf88cad8f0217ae9acb47c0d4f141a6416d2d7b",
-                "sha256:ad4efa5fad66b903b4a5f96d91461d90b9507a812b3c5de657d544215bb7877a",
-                "sha256:bc58025940a896d7e5356952228b68f793cf5fcb342be703c3a2669a1488cb72",
-                "sha256:cc1e1de68c8e5444e8f94c3670bb48a2beef0e91dddfd4fcc29595ebd90bb9ce",
-                "sha256:cee3e11161dde1b2a33a904b850b0899e0424cc331b7295f2a9698e79f9a69a0",
-                "sha256:e3556168e2e5c49629f7b0f377070240bd5511e45e25a4497bb0073d9dda776a",
-                "sha256:e8477ec6bbfe0312c128e74644ac8a02ca06bcdb8982d4ee06f209be28cdf163",
-                "sha256:ee8f1f7228cce7dffc2b464f07ce769f478968bfb3dd1254a4c2eeed84928aad",
-                "sha256:fd57160949179ec517d32ac2ac898b5f20d68ed1a9c977346efbac9c2f1e779d"
-            ],
-            "version": "==22.3.0"
-        },
-        "certifi": {
-            "hashes": [
-                "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872",
-                "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"
-            ],
-            "version": "==2021.10.8"
-        },
-        "cfgv": {
-            "hashes": [
-                "sha256:fbd93c9ab0a523bf7daec408f3be2ed99a980e20b2d19b50fc184ca6b820d289"
-            ],
-            "version": "==2.0.1"
-        },
-        "charset-normalizer": {
-            "hashes": [
-                "sha256:e019de665e2bcf9c2b64e2e5aa025fa991da8720daa3c1138cadd2fd1856aed0",
-                "sha256:f7af805c321bfa1ce6714c51f254e0d5bb5e5834039bc17db7ebe3a4cec9492b"
-            ],
-            "version": "==2.0.7"
-        },
-        "click": {
-            "hashes": [
-                "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3",
-                "sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b"
-            ],
-            "version": "==8.0.3"
-        },
-        "coverage": {
-            "extras": [
-                "toml"
-            ],
-            "hashes": [
-                "sha256:03e2a7826086b91ef345ff18742ee9fc47a6839ccd517061ef8fa1976e652ce9",
-                "sha256:07e6db90cd9686c767dcc593dff16c8c09f9814f5e9c51034066cad3373b914d",
-                "sha256:18d520c6860515a771708937d2f78f63cc47ab3b80cb78e86573b0a760161faf",
-                "sha256:1ebf730d2381158ecf3dfd4453fbca0613e16eaa547b4170e2450c9707665ce7",
-                "sha256:21b7745788866028adeb1e0eca3bf1101109e2dc58456cb49d2d9b99a8c516e6",
-                "sha256:26e2deacd414fc2f97dd9f7676ee3eaecd299ca751412d89f40bc01557a6b1b4",
-                "sha256:2c6dbb42f3ad25760010c45191e9757e7dce981cbfb90e42feef301d71540059",
-                "sha256:2fea046bfb455510e05be95e879f0e768d45c10c11509e20e06d8fcaa31d9e39",
-                "sha256:34626a7eee2a3da12af0507780bb51eb52dca0e1751fd1471d0810539cefb536",
-                "sha256:37d1141ad6b2466a7b53a22e08fe76994c2d35a5b6b469590424a9953155afac",
-                "sha256:46191097ebc381fbf89bdce207a6c107ac4ec0890d8d20f3360345ff5976155c",
-                "sha256:4dd8bafa458b5c7d061540f1ee9f18025a68e2d8471b3e858a9dad47c8d41903",
-                "sha256:4e21876082ed887baed0146fe222f861b5815455ada3b33b890f4105d806128d",
-                "sha256:58303469e9a272b4abdb9e302a780072c0633cdcc0165db7eec0f9e32f901e05",
-                "sha256:5ca5aeb4344b30d0bec47481536b8ba1181d50dbe783b0e4ad03c95dc1296684",
-                "sha256:68353fe7cdf91f109fc7d474461b46e7f1f14e533e911a2a2cbb8b0fc8613cf1",
-                "sha256:6f89d05e028d274ce4fa1a86887b071ae1755082ef94a6740238cd7a8178804f",
-                "sha256:7a15dc0a14008f1da3d1ebd44bdda3e357dbabdf5a0b5034d38fcde0b5c234b7",
-                "sha256:8bdde1177f2311ee552f47ae6e5aa7750c0e3291ca6b75f71f7ffe1f1dab3dca",
-                "sha256:8ce257cac556cb03be4a248d92ed36904a59a4a5ff55a994e92214cde15c5bad",
-                "sha256:8cf5cfcb1521dc3255d845d9dca3ff204b3229401994ef8d1984b32746bb45ca",
-                "sha256:8fbbdc8d55990eac1b0919ca69eb5a988a802b854488c34b8f37f3e2025fa90d",
-                "sha256:9548f10d8be799551eb3a9c74bbf2b4934ddb330e08a73320123c07f95cc2d92",
-                "sha256:96f8a1cb43ca1422f36492bebe63312d396491a9165ed3b9231e778d43a7fca4",
-                "sha256:9b27d894748475fa858f9597c0ee1d4829f44683f3813633aaf94b19cb5453cf",
-                "sha256:9baff2a45ae1f17c8078452e9e5962e518eab705e50a0aa8083733ea7d45f3a6",
-                "sha256:a2a8b8bcc399edb4347a5ca8b9b87e7524c0967b335fbb08a83c8421489ddee1",
-                "sha256:acf53bc2cf7282ab9b8ba346746afe703474004d9e566ad164c91a7a59f188a4",
-                "sha256:b0be84e5a6209858a1d3e8d1806c46214e867ce1b0fd32e4ea03f4bd8b2e3359",
-                "sha256:b31651d018b23ec463e95cf10070d0b2c548aa950a03d0b559eaa11c7e5a6fa3",
-                "sha256:b78e5afb39941572209f71866aa0b206c12f0109835aa0d601e41552f9b3e620",
-                "sha256:c76aeef1b95aff3905fb2ae2d96e319caca5b76fa41d3470b19d4e4a3a313512",
-                "sha256:dd035edafefee4d573140a76fdc785dc38829fe5a455c4bb12bac8c20cfc3d69",
-                "sha256:dd6fe30bd519694b356cbfcaca9bd5c1737cddd20778c6a581ae20dc8c04def2",
-                "sha256:e5f4e1edcf57ce94e5475fe09e5afa3e3145081318e5fd1a43a6b4539a97e518",
-                "sha256:ec6bc7fe73a938933d4178c9b23c4e0568e43e220aef9472c4f6044bfc6dd0f0",
-                "sha256:f1555ea6d6da108e1999b2463ea1003fe03f29213e459145e70edbaf3e004aaa",
-                "sha256:f5fa5803f47e095d7ad8443d28b01d48c0359484fec1b9d8606d0e3282084bc4",
-                "sha256:f7331dbf301b7289013175087636bbaf5b2405e57259dd2c42fdcc9fcc47325e",
-                "sha256:f9987b0354b06d4df0f4d3e0ec1ae76d7ce7cbca9a2f98c25041eb79eec766f1",
-                "sha256:fd9e830e9d8d89b20ab1e5af09b32d33e1a08ef4c4e14411e559556fd788e6b2"
-            ],
-            "version": "==6.3.2"
-        },
-        "distlib": {
-            "hashes": [
-                "sha256:106fef6dc37dd8c0e2c0a60d3fca3e77460a48907f335fa28420463a6f799736",
-                "sha256:23e223426b28491b1ced97dc3bbe183027419dfc7982b4fa2f05d5f3ff10711c"
-            ],
-            "version": "==0.3.2"
-        },
-        "docutils": {
-            "hashes": [
-                "sha256:686577d2e4c32380bb50cbb22f575ed742d58168cee37e99117a854bcd88f125",
-                "sha256:cf316c8370a737a022b72b56874f6602acf974a37a9fba42ec2876387549fc61"
-            ],
-            "version": "==0.17.1"
-        },
-        "filelock": {
-            "hashes": [
-                "sha256:7afc856f74fa7006a289fd10fa840e1eebd8bbff6bffb69c26c54a0512ea8cf8",
-                "sha256:bb2a1c717df74c48a2d00ed625e5a66f8572a3a30baacb7657add1d7bac4097b"
-            ],
-            "version": "==3.3.2"
-        },
-        "flake8": {
-            "hashes": [
-                "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b",
-                "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"
-            ],
-            "version": "==3.9.2"
-        },
-        "flake8-isort": {
-            "hashes": [
-                "sha256:c4e8b6dcb7be9b71a02e6e5d4196cefcef0f3447be51e82730fb336fff164949",
-                "sha256:d814304ab70e6e58859bc5c3e221e2e6e71c958e7005239202fee19c24f82717"
-            ],
-            "version": "==4.1.1"
-        },
-        "flake8-pydocstyle": {
-            "hashes": [
-                "sha256:3872486de955621734e2e47dfaf711c640112ebbe9ef4289c9a4ae36c2420b69",
-                "sha256:d2553a22d6d255030b1f81e3059470efd075c544aeca81fee4be8558551f615a"
-            ],
-            "markers": "python_version >= '3.7' and python_version < '4.0'",
-            "version": "==0.2.0"
-        },
-        "identify": {
-            "hashes": [
-                "sha256:72e9c4ed3bc713c7045b762b0d2e2115c572b85abfc1f4604f5a4fd4c6642b71"
-            ],
-            "version": "==1.4.9"
-        },
-        "idna": {
-            "hashes": [
-                "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff",
-                "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"
-            ],
-            "version": "==3.3"
-        },
-        "imagesize": {
-            "hashes": [
-                "sha256:6965f19a6a2039c7d48bca7dba2473069ff854c36ae6f19d2cde309d998228a1",
-                "sha256:b1f6b5a4eab1f73479a50fb79fcf729514a900c341d8503d62a62dbc4127a2b1"
-            ],
-            "version": "==1.2.0"
-        },
-        "importlib-metadata": {
-            "hashes": [
-                "sha256:175f4ee440a0317f6e8d81b7f8d4869f93316170a65ad2b007d2929186c8052c",
-                "sha256:e0bc84ff355328a4adfc5240c4f211e0ab386f80aa640d1b11f0618a1d282094"
-            ],
-            "markers": "python_version < '3.8'",
-            "version": "==4.11.1"
-        },
-        "iniconfig": {
-            "hashes": [
-                "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3",
-                "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"
-            ],
-            "version": "==1.1.1"
-        },
-        "isort": {
-            "hashes": [
-                "sha256:1a18ccace2ed8910bd9458b74a3ecbafd7b2f581301b0ab65cfdd4338272d76f",
-                "sha256:e52ff6d38012b131628cf0f26c51e7bd3a7c81592eefe3ac71411e692f1b9345"
-            ],
-            "version": "==5.10.0"
-        },
-        "jinja2": {
-            "hashes": [
-                "sha256:827a0e32839ab1600d4eb1c4c33ec5a8edfbc5cb42dafa13b81f182f97784b45",
-                "sha256:8569982d3f0889eed11dd620c706d39b60c36d6d25843961f33f77fb6bc6b20c"
-            ],
-            "version": "==3.0.2"
-        },
-        "markupsafe": {
-            "hashes": [
-                "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298",
-                "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64",
-                "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b",
-                "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567",
-                "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff",
-                "sha256:0d4b31cc67ab36e3392bbf3862cfbadac3db12bdd8b02a2731f509ed5b829724",
-                "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74",
-                "sha256:168cd0a3642de83558a5153c8bd34f175a9a6e7f6dc6384b9655d2697312a646",
-                "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35",
-                "sha256:1f2ade76b9903f39aa442b4aadd2177decb66525062db244b35d71d0ee8599b6",
-                "sha256:2a7d351cbd8cfeb19ca00de495e224dea7e7d919659c2841bbb7f420ad03e2d6",
-                "sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad",
-                "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26",
-                "sha256:36bc903cbb393720fad60fc28c10de6acf10dc6cc883f3e24ee4012371399a38",
-                "sha256:37205cac2a79194e3750b0af2a5720d95f786a55ce7df90c3af697bfa100eaac",
-                "sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7",
-                "sha256:3dd007d54ee88b46be476e293f48c85048603f5f516008bee124ddd891398ed6",
-                "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75",
-                "sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f",
-                "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135",
-                "sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8",
-                "sha256:5855f8438a7d1d458206a2466bf82b0f104a3724bf96a1c781ab731e4201731a",
-                "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a",
-                "sha256:5bb28c636d87e840583ee3adeb78172efc47c8b26127267f54a9c0ec251d41a9",
-                "sha256:60bf42e36abfaf9aff1f50f52644b336d4f0a3fd6d8a60ca0d054ac9f713a864",
-                "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914",
-                "sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18",
-                "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8",
-                "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2",
-                "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d",
-                "sha256:6fcf051089389abe060c9cd7caa212c707e58153afa2c649f00346ce6d260f1b",
-                "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b",
-                "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f",
-                "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb",
-                "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833",
-                "sha256:99df47edb6bda1249d3e80fdabb1dab8c08ef3975f69aed437cb69d0a5de1e28",
-                "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415",
-                "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902",
-                "sha256:add36cb2dbb8b736611303cd3bfcee00afd96471b09cda130da3581cbdc56a6d",
-                "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9",
-                "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d",
-                "sha256:baa1a4e8f868845af802979fcdbf0bb11f94f1cb7ced4c4b8a351bb60d108145",
-                "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066",
-                "sha256:bf5d821ffabf0ef3533c39c518f3357b171a1651c1ff6827325e4489b0e46c3c",
-                "sha256:c47adbc92fc1bb2b3274c4b3a43ae0e4573d9fbff4f54cd484555edbf030baf1",
-                "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f",
-                "sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53",
-                "sha256:e0f138900af21926a02425cf736db95be9f4af72ba1bb21453432a07f6082134",
-                "sha256:e9936f0b261d4df76ad22f8fee3ae83b60d7c3e871292cd42f40b81b70afae85",
-                "sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5",
-                "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94",
-                "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509",
-                "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51",
-                "sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872"
-            ],
-            "version": "==2.0.1"
-        },
-        "mccabe": {
-            "hashes": [
-                "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42",
-                "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"
-            ],
-            "version": "==0.6.1"
-        },
-        "mypy": {
-            "hashes": [
-                "sha256:088cd9c7904b4ad80bec811053272986611b84221835e079be5bcad029e79dd9",
-                "sha256:0aadfb2d3935988ec3815952e44058a3100499f5be5b28c34ac9d79f002a4a9a",
-                "sha256:119bed3832d961f3a880787bf621634ba042cb8dc850a7429f643508eeac97b9",
-                "sha256:1a85e280d4d217150ce8cb1a6dddffd14e753a4e0c3cf90baabb32cefa41b59e",
-                "sha256:3c4b8ca36877fc75339253721f69603a9c7fdb5d4d5a95a1a1b899d8b86a4de2",
-                "sha256:3e382b29f8e0ccf19a2df2b29a167591245df90c0b5a2542249873b5c1d78212",
-                "sha256:42c266ced41b65ed40a282c575705325fa7991af370036d3f134518336636f5b",
-                "sha256:53fd2eb27a8ee2892614370896956af2ff61254c275aaee4c230ae771cadd885",
-                "sha256:704098302473cb31a218f1775a873b376b30b4c18229421e9e9dc8916fd16150",
-                "sha256:7df1ead20c81371ccd6091fa3e2878559b5c4d4caadaf1a484cf88d93ca06703",
-                "sha256:866c41f28cee548475f146aa4d39a51cf3b6a84246969f3759cb3e9c742fc072",
-                "sha256:a155d80ea6cee511a3694b108c4494a39f42de11ee4e61e72bc424c490e46457",
-                "sha256:adaeee09bfde366d2c13fe6093a7df5df83c9a2ba98638c7d76b010694db760e",
-                "sha256:b6fb13123aeef4a3abbcfd7e71773ff3ff1526a7d3dc538f3929a49b42be03f0",
-                "sha256:b94e4b785e304a04ea0828759172a15add27088520dc7e49ceade7834275bedb",
-                "sha256:c0df2d30ed496a08de5daed2a9ea807d07c21ae0ab23acf541ab88c24b26ab97",
-                "sha256:c6c2602dffb74867498f86e6129fd52a2770c48b7cd3ece77ada4fa38f94eba8",
-                "sha256:ceb6e0a6e27fb364fb3853389607cf7eb3a126ad335790fa1e14ed02fba50811",
-                "sha256:d9dd839eb0dc1bbe866a288ba3c1afc33a202015d2ad83b31e875b5905a079b6",
-                "sha256:e4dab234478e3bd3ce83bac4193b2ecd9cf94e720ddd95ce69840273bf44f6de",
-                "sha256:ec4e0cd079db280b6bdabdc807047ff3e199f334050db5cbb91ba3e959a67504",
-                "sha256:ecd2c3fe726758037234c93df7e98deb257fd15c24c9180dacf1ef829da5f921",
-                "sha256:ef565033fa5a958e62796867b1df10c40263ea9ded87164d67572834e57a174d"
-            ],
-            "version": "==0.910"
-        },
-        "mypy-extensions": {
-            "hashes": [
-                "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d",
-                "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"
-            ],
-            "version": "==0.4.3"
-        },
-        "nodeenv": {
-            "hashes": [
-                "sha256:561057acd4ae3809e665a9aaaf214afff110bbb6a6d5c8a96121aea6878408b3"
-            ],
-            "version": "==1.3.4"
-        },
-        "numpy": {
-            "hashes": [
-                "sha256:00c9fa73a6989895b8815d98300a20ac993c49ac36c8277e8ffeaa3631c0dbbb",
-                "sha256:025b497014bc33fc23897859350f284323f32a2fff7654697f5a5fc2a19e9939",
-                "sha256:08de8472d9f7571f9d51b27b75e827f5296295fa78817032e84464be8bb905bc",
-                "sha256:1964db2d4a00348b7a60ee9d013c8cb0c566644a589eaa80995126eac3b99ced",
-                "sha256:2a9add27d7fc0fdb572abc3b2486eb3b1395da71e0254c5552b2aad2a18b5441",
-                "sha256:2d8adfca843bc46ac199a4645233f13abf2011a0b2f4affc5c37cd552626f27b",
-                "sha256:301e408a052fdcda5cdcf03021ebafc3c6ea093021bf9d1aa47c54d48bdad166",
-                "sha256:311283acf880cfcc20369201bd75da907909afc4666966c7895cbed6f9d2c640",
-                "sha256:341dddcfe3b7b6427a28a27baa59af5ad51baa59bfec3264f1ab287aa3b30b13",
-                "sha256:3a5098df115340fb17fc93867317a947e1dcd978c3888c5ddb118366095851f8",
-                "sha256:3c978544be9e04ed12016dd295a74283773149b48f507d69b36f91aa90a643e5",
-                "sha256:3d893b0871322eaa2f8c7072cdb552d8e2b27645b7875a70833c31e9274d4611",
-                "sha256:4fe6a006557b87b352c04596a6e3f12a57d6e5f401d804947bd3188e6b0e0e76",
-                "sha256:507c05c7a37b3683eb08a3ff993bd1ee1e6c752f77c2f275260533b265ecdb6c",
-                "sha256:58ca1d7c8aef6e996112d0ce873ac9dfa1eaf4a1196b4ff7ff73880a09923ba7",
-                "sha256:61bada43d494515d5b122f4532af226fdb5ee08fe5b5918b111279843dc6836a",
-                "sha256:69a5a8d71c308d7ef33ef72371c2388a90e3495dbb7993430e674006f94797d5",
-                "sha256:6a5928bc6241264dce5ed509e66f33676fc97f464e7a919edc672fb5532221ee",
-                "sha256:7b9d6b14fc9a4864b08d1ba57d732b248f0e482c7b2ff55c313137e3ed4d8449",
-                "sha256:a7c4b701ca418cd39e28ec3b496e6388fe06de83f5f0cb74794fa31cfa384c02",
-                "sha256:a7e8f6216f180f3fd4efb73de5d1eaefb5f5a1ee5b645c67333033e39440e63a",
-                "sha256:b545ebadaa2b878c8630e5bcdb97fc4096e779f335fc0f943547c1c91540c815",
-                "sha256:c293d3c0321996cd8ffe84215ffe5d269fd9d1d12c6f4ffe2b597a7c30d3e593",
-                "sha256:c5562bcc1a9b61960fc8950ade44d00e3de28f891af0acc96307c73613d18f6e",
-                "sha256:ca9c23848292c6fe0a19d212790e62f398fd9609aaa838859be8459bfbe558aa",
-                "sha256:cc1b30205d138d1005adb52087ff45708febbef0e420386f58664f984ef56954",
-                "sha256:dbce7adeb66b895c6aaa1fad796aaefc299ced597f6fbd9ceddb0dd735245354",
-                "sha256:dc4b2fb01f1b4ddbe2453468ea0719f4dbb1f5caa712c8b21bb3dd1480cd30d9",
-                "sha256:eed2afaa97ec33b4411995be12f8bdb95c87984eaa28d76cf628970c8a2d689a",
-                "sha256:fc7a7d7b0ed72589fd8b8486b9b42a564f10b8762be8bd4d9df94b807af4a089"
-            ],
-            "version": "==1.21.5"
-        },
-        "packaging": {
-            "hashes": [
-                "sha256:096d689d78ca690e4cd8a89568ba06d07ca097e3306a4381635073ca91479966",
-                "sha256:14317396d1e8cdb122989b916fa2c7e9ca8e2be9e8060a6eff75b6b7b4d8a7e0"
-            ],
-            "version": "==21.2"
-        },
-        "pathspec": {
-            "hashes": [
-                "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a",
-                "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"
-            ],
-            "version": "==0.9.0"
-        },
-        "platformdirs": {
-            "hashes": [
-                "sha256:367a5e80b3d04d2428ffa76d33f124cf11e8fff2acdaa9b43d545f5c7d661ef2",
-                "sha256:8868bbe3c3c80d42f20156f22e7131d2fb321f5bc86a2a345375c6481a67021d"
-            ],
-            "version": "==2.4.0"
-        },
-        "pluggy": {
-            "hashes": [
-                "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159",
-                "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"
-            ],
-            "version": "==1.0.0"
-        },
-        "pre-commit": {
-            "hashes": [
-                "sha256:f92a359477f3252452ae2e8d3029de77aec59415c16ae4189bcfba40b757e029"
-            ],
-            "version": "==1.21.0"
-        },
-        "py": {
-            "hashes": [
-                "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3",
-                "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"
-            ],
-            "version": "==1.10.0"
-        },
-        "pycodestyle": {
-            "hashes": [
-                "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068",
-                "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef"
-            ],
-            "version": "==2.7.0"
-        },
-        "pydocstyle": {
-            "hashes": [
-                "sha256:1d41b7c459ba0ee6c345f2eb9ae827cab14a7533a88c5c6f7e94923f72df92dc",
-                "sha256:6987826d6775056839940041beef5c08cc7e3d71d63149b48e36727f70144dc4"
-            ],
-            "version": "==6.1.1"
-        },
-        "pyflakes": {
-            "hashes": [
-                "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3",
-                "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"
-            ],
-            "version": "==2.3.1"
-        },
-        "pygments": {
-            "hashes": [
-                "sha256:b8e67fe6af78f492b3c4b3e2970c0624cbf08beb1e493b2c99b9fa1b67a20380",
-                "sha256:f398865f7eb6874156579fdf36bc840a03cab64d1cde9e93d68f46a425ec52c6"
-            ],
-            "version": "==2.10.0"
-        },
-        "pyparsing": {
-            "hashes": [
-                "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1",
-                "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"
-            ],
-            "version": "==2.4.7"
-        },
-        "pytest": {
-            "hashes": [
-                "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89",
-                "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"
-            ],
-            "version": "==6.2.5"
-        },
-        "pytest-cov": {
-            "hashes": [
-                "sha256:578d5d15ac4a25e5f961c938b85a05b09fdaae9deef3bb6de9a6e766622ca7a6",
-                "sha256:e7f0f5b1617d2210a2cabc266dfe2f4c75a8d32fb89eafb7ad9d06f6d076d470"
-            ],
-            "version": "==3.0.0"
-        },
-        "pytz": {
-            "hashes": [
-                "sha256:3672058bc3453457b622aab7a1c3bfd5ab0bdae451512f6cf25f64ed37f5b87c",
-                "sha256:acad2d8b20a1af07d4e4c9d2e9285c5ed9104354062f275f3fcd88dcef4f1326"
-            ],
-            "version": "==2021.3"
-        },
-        "pyyaml": {
-            "hashes": [
-                "sha256:02c78d77281d8f8d07a255e57abdbf43b02257f59f50cc6b636937d68efa5dd0",
-                "sha256:0dc9f2eb2e3c97640928dec63fd8dc1dd91e6b6ed236bd5ac00332b99b5c2ff9",
-                "sha256:124fd7c7bc1e95b1eafc60825f2daf67c73ce7b33f1194731240d24b0d1bf628",
-                "sha256:26fcb33776857f4072601502d93e1a619f166c9c00befb52826e7b774efaa9db",
-                "sha256:31ba07c54ef4a897758563e3a0fcc60077698df10180abe4b8165d9895c00ebf",
-                "sha256:3c49e39ac034fd64fd576d63bb4db53cda89b362768a67f07749d55f128ac18a",
-                "sha256:52bf0930903818e600ae6c2901f748bc4869c0c406056f679ab9614e5d21a166",
-                "sha256:5a3f345acff76cad4aa9cb171ee76c590f37394186325d53d1aa25318b0d4a09",
-                "sha256:5e7ac4e0e79a53451dc2814f6876c2fa6f71452de1498bbe29c0b54b69a986f4",
-                "sha256:7242790ab6c20316b8e7bb545be48d7ed36e26bbe279fd56f2c4a12510e60b4b",
-                "sha256:737bd70e454a284d456aa1fa71a0b429dd527bcbf52c5c33f7c8eee81ac16b89",
-                "sha256:8635d53223b1f561b081ff4adecb828fd484b8efffe542edcfdff471997f7c39",
-                "sha256:8b818b6c5a920cbe4203b5a6b14256f0e5244338244560da89b7b0f1313ea4b6",
-                "sha256:8bf38641b4713d77da19e91f8b5296b832e4db87338d6aeffe422d42f1ca896d",
-                "sha256:a36a48a51e5471513a5aea920cdad84cbd56d70a5057cca3499a637496ea379c",
-                "sha256:b2243dd033fd02c01212ad5c601dafb44fbb293065f430b0d3dbf03f3254d615",
-                "sha256:cc547d3ead3754712223abb7b403f0a184e4c3eae18c9bb7fd15adef1597cc4b",
-                "sha256:cc552b6434b90d9dbed6a4f13339625dc466fd82597119897e9489c953acbc22",
-                "sha256:f3790156c606299ff499ec44db422f66f05a7363b39eb9d5b064f17bd7d7c47b",
-                "sha256:f7a21e3d99aa3095ef0553e7ceba36fb693998fbb1226f1392ce33681047465f",
-                "sha256:fdc6b2cb4b19e431994f25a9160695cc59a4e861710cc6fc97161c5e845fc579"
-            ],
-            "version": "==5.4"
-        },
-        "requests": {
-            "hashes": [
-                "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24",
-                "sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7"
-            ],
-            "version": "==2.26.0"
-        },
-        "scanspec": {
-            "editable": true,
-            "extras": [
-                "graphql",
-                "plotting"
-            ],
-            "path": "."
-        },
-        "six": {
-            "hashes": [
-                "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926",
-                "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"
-            ],
-            "version": "==1.16.0"
-        },
-        "snowballstemmer": {
-            "hashes": [
-                "sha256:b51b447bea85f9968c13b650126a888aabd4cb4463fca868ec596826325dedc2",
-                "sha256:e997baa4f2e9139951b6f4c631bad912dfd3c792467e2f03d7239464af90e914"
-            ],
-            "version": "==2.1.0"
-        },
-        "sphinx": {
-            "hashes": [
-                "sha256:0a8836751a68306b3fe97ecbe44db786f8479c3bf4b80e3a7f5c838657b4698c",
-                "sha256:6a11ea5dd0bdb197f9c2abc2e0ce73e01340464feaece525e64036546d24c851"
-            ],
-            "version": "==4.3.2"
-        },
-        "sphinx-apischema": {
-            "hashes": [
-                "sha256:be8b057a38e585d00bec70c9a5ab6d133bb3d933e61c5aebadd15fc0c2129d7b",
-                "sha256:d08bc8a6fde414e0569bea2bfe107af155ded1791520d3140edb2e238c31b1c2"
-            ],
-            "version": "==0.1"
-        },
-        "sphinx-graphql": {
-            "hashes": [
-                "sha256:704acfa5eb536664475a098487410e8ecdc1daca30cad37e8ddb99cf4b9ae1d6",
-                "sha256:c93436d89b2b53f8f2826d45ca574711ccf933ee1bf241fa852d1f7317cb25bd"
-            ],
-            "version": "==0.1.2"
-        },
-        "sphinx-rtd-theme": {
-            "hashes": [
-                "sha256:4d35a56f4508cfee4c4fb604373ede6feae2a306731d533f409ef5c3496fdbd8",
-                "sha256:eec6d497e4c2195fa0e8b2016b337532b8a699a68bcb22a512870e16925c6a5c"
-            ],
-            "version": "==1.0.0"
-        },
-        "sphinx-rtd-theme-github-versions": {
-            "hashes": [
-                "sha256:0df27ae9a9cd902468c808dbee5a43f4db8ce43cbcf2ecc78d2fe47698bb0ded",
-                "sha256:23018e51a5d27ef4f69dd86314f73b19088f2cfd91c74a24db1517832233dc07"
-            ],
-            "version": "==1.1"
-        },
-        "sphinxcontrib-applehelp": {
-            "hashes": [
-                "sha256:806111e5e962be97c29ec4c1e7fe277bfd19e9652fb1a4392105b43e01af885a",
-                "sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58"
-            ],
-            "version": "==1.0.2"
-        },
-        "sphinxcontrib-devhelp": {
-            "hashes": [
-                "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e",
-                "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4"
-            ],
-            "version": "==1.0.2"
-        },
-        "sphinxcontrib-htmlhelp": {
-            "hashes": [
-                "sha256:d412243dfb797ae3ec2b59eca0e52dac12e75a241bf0e4eb861e450d06c6ed07",
-                "sha256:f5f8bb2d0d629f398bf47d0d69c07bc13b65f75a81ad9e2f71a63d4b7a2f6db2"
-            ],
-            "version": "==2.0.0"
-        },
-        "sphinxcontrib-jsmath": {
-            "hashes": [
-                "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178",
-                "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"
-            ],
-            "version": "==1.0.1"
-        },
-        "sphinxcontrib-qthelp": {
-            "hashes": [
-                "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72",
-                "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6"
-            ],
-            "version": "==1.0.3"
-        },
-        "sphinxcontrib-serializinghtml": {
-            "hashes": [
-                "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd",
-                "sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952"
-            ],
-            "version": "==1.1.5"
-        },
-        "testfixtures": {
-            "hashes": [
-                "sha256:2600100ae96ffd082334b378e355550fef8b4a529a6fa4c34f47130905c7426d",
-                "sha256:6ddb7f56a123e1a9339f130a200359092bd0a6455e31838d6c477e8729bb7763"
-            ],
-            "version": "==6.18.3"
-        },
-        "toml": {
-            "hashes": [
-                "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b",
-                "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"
-            ],
-            "version": "==0.10.2"
-        },
-        "tomli": {
-            "hashes": [
-                "sha256:c6ce0015eb38820eaf32b5db832dbc26deb3dd427bd5f6556cf0acac2c214fee",
-                "sha256:f04066f68f5554911363063a30b108d2b5a5b1a010aa8b6132af78489fe3aade"
-            ],
-            "version": "==1.2.2"
-        },
-        "typed-ast": {
-            "hashes": [
-                "sha256:01ae5f73431d21eead5015997ab41afa53aa1fbe252f9da060be5dad2c730ace",
-                "sha256:067a74454df670dcaa4e59349a2e5c81e567d8d65458d480a5b3dfecec08c5ff",
-                "sha256:0fb71b8c643187d7492c1f8352f2c15b4c4af3f6338f21681d3681b3dc31a266",
-                "sha256:1b3ead4a96c9101bef08f9f7d1217c096f31667617b58de957f690c92378b528",
-                "sha256:2068531575a125b87a41802130fa7e29f26c09a2833fea68d9a40cf33902eba6",
-                "sha256:209596a4ec71d990d71d5e0d312ac935d86930e6eecff6ccc7007fe54d703808",
-                "sha256:2c726c276d09fc5c414693a2de063f521052d9ea7c240ce553316f70656c84d4",
-                "sha256:398e44cd480f4d2b7ee8d98385ca104e35c81525dd98c519acff1b79bdaac363",
-                "sha256:52b1eb8c83f178ab787f3a4283f68258525f8d70f778a2f6dd54d3b5e5fb4341",
-                "sha256:5feca99c17af94057417d744607b82dd0a664fd5e4ca98061480fd8b14b18d04",
-                "sha256:7538e495704e2ccda9b234b82423a4038f324f3a10c43bc088a1636180f11a41",
-                "sha256:760ad187b1041a154f0e4d0f6aae3e40fdb51d6de16e5c99aedadd9246450e9e",
-                "sha256:777a26c84bea6cd934422ac2e3b78863a37017618b6e5c08f92ef69853e765d3",
-                "sha256:95431a26309a21874005845c21118c83991c63ea800dd44843e42a916aec5899",
-                "sha256:9ad2c92ec681e02baf81fdfa056fe0d818645efa9af1f1cd5fd6f1bd2bdfd805",
-                "sha256:9c6d1a54552b5330bc657b7ef0eae25d00ba7ffe85d9ea8ae6540d2197a3788c",
-                "sha256:aee0c1256be6c07bd3e1263ff920c325b59849dc95392a05f258bb9b259cf39c",
-                "sha256:af3d4a73793725138d6b334d9d247ce7e5f084d96284ed23f22ee626a7b88e39",
-                "sha256:b36b4f3920103a25e1d5d024d155c504080959582b928e91cb608a65c3a49e1a",
-                "sha256:b9574c6f03f685070d859e75c7f9eeca02d6933273b5e69572e5ff9d5e3931c3",
-                "sha256:bff6ad71c81b3bba8fa35f0f1921fb24ff4476235a6e94a26ada2e54370e6da7",
-                "sha256:c190f0899e9f9f8b6b7863debfb739abcb21a5c054f911ca3596d12b8a4c4c7f",
-                "sha256:c907f561b1e83e93fad565bac5ba9c22d96a54e7ea0267c708bffe863cbe4075",
-                "sha256:cae53c389825d3b46fb37538441f75d6aecc4174f615d048321b716df2757fb0",
-                "sha256:dd4a21253f42b8d2b48410cb31fe501d32f8b9fbeb1f55063ad102fe9c425e40",
-                "sha256:dde816ca9dac1d9c01dd504ea5967821606f02e510438120091b84e852367428",
-                "sha256:f2362f3cb0f3172c42938946dbc5b7843c2a28aec307c49100c8b38764eb6927",
-                "sha256:f328adcfebed9f11301eaedfa48e15bdece9b519fb27e6a8c01aa52a17ec31b3",
-                "sha256:f8afcf15cc511ada719a88e013cec87c11aff7b91f019295eb4530f96fe5ef2f",
-                "sha256:fb1bbeac803adea29cedd70781399c99138358c26d05fcbd23c13016b7f5ec65"
-            ],
-            "markers": "python_version < '3.8' and implementation_name == 'cpython' and python_version < '3.8'",
-            "version": "==1.4.3"
-        },
-        "typing-extensions": {
-            "hashes": [
-                "sha256:4ca091dea149f945ec56afb48dae714f21e8692ef22a395223bcd328961b6a0e",
-                "sha256:7f001e5ac290a0c0401508864c7ec868be4e701886d5b573a9528ed3973d9d3b"
-            ],
-            "markers": "python_version < '3.8' and python_version < '3.8'",
-            "version": "==4.0.1"
-        },
-        "urllib3": {
-            "hashes": [
-                "sha256:4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece",
-                "sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844"
-            ],
-            "version": "==1.26.7"
-        },
-        "virtualenv": {
-            "hashes": [
-                "sha256:14fdf849f80dbb29a4eb6caa9875d476ee2a5cf76a5f5415fa2f1606010ab467",
-                "sha256:2b0126166ea7c9c3661f5b8e06773d28f83322de7a3ff7d06f0aed18c9de6a76"
-            ],
-            "version": "==20.4.7"
-        },
-        "zipp": {
-            "hashes": [
-                "sha256:71c644c5369f4a6e07636f0aa966270449561fcea2e3d6747b8d23efaa9d7832",
-                "sha256:9fe5ea21568a0a70e50f273397638d39b03353731e6cbbb3fd8502a33fec40bc"
-            ],
-            "version": "==3.6.0"
-        }
-    }
-}
diff --git a/README.rst b/README.rst
index eb6ef005..5df70873 100644
--- a/README.rst
+++ b/README.rst
@@ -1,7 +1,7 @@
 scanspec
 ========
 
-|code_ci| |docs_ci| |coverage| |pypi_version| |license|
+|code_ci| |docs_ci| |coverage| |pypi_version| |anaconda_version| |license|
 
 Specify step and flyscan Paths using combinations of:
 
@@ -26,7 +26,7 @@ Releases       https://github.com/dls-controls/scanspec/releases
 An example ScanSpec of a 2D snaked grid flyscan inside a circle spending 0.4s at
 each point looks like:
 
-.. code:: python
+.. code-block:: python
 
     from scanspec.specs import Line, fly
     from scanspec.regions import Circle
@@ -62,16 +62,15 @@ from it for performance:
     chunk.midpoints  # {'x': <ndarray len=10>, 'y': <ndarray len=10>, 'DURATION': <ndarray len=10>}
     chunk.upper  # bounds are same dimensionality as positions
 
-
-.. |code_ci| image:: https://github.com/dls-controls/scanspec/workflows/Code%20CI/badge.svg?branch=master
-    :target: https://github.com/dls-controls/scanspec/actions?query=workflow%3A%22Code+CI%22
+.. |code_ci| image:: https://github.com/dls-controls/scanspec/actions/workflows/code.yml/badge.svg?branch=main
+    :target: https://github.com/dls-controls/scanspec/actions/workflows/code.yml
     :alt: Code CI
 
-.. |docs_ci| image:: https://github.com/dls-controls/scanspec/workflows/Docs%20CI/badge.svg?branch=master
-    :target: https://github.com/dls-controls/scanspec/actions?query=workflow%3A%22Docs+CI%22
+.. |docs_ci| image:: https://github.com/dls-controls/scanspec/actions/workflows/docs.yml/badge.svg?branch=main
+    :target: https://github.com/dls-controls/scanspec/actions/workflows/docs.yml
     :alt: Docs CI
 
-.. |coverage| image:: https://codecov.io/gh/dls-controls/scanspec/branch/master/graph/badge.svg
+.. |coverage| image:: https://codecov.io/gh/dls-controls/scanspec/branch/main/graph/badge.svg
     :target: https://codecov.io/gh/dls-controls/scanspec
     :alt: Test Coverage
 
@@ -79,6 +78,10 @@ from it for performance:
     :target: https://pypi.org/project/scanspec
     :alt: Latest PyPI version
 
+.. |anaconda_version| image:: https://anaconda.org/dls-controls/scanspec/badges/version.svg
+    :target: https://anaconda.org/dls-controls/scanspec
+    :alt: Latest Anaconda 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
diff --git a/docs/_static/theme_overrides.css b/docs/_static/theme_overrides.css
deleted file mode 100644
index 5fd9b721..00000000
--- a/docs/_static/theme_overrides.css
+++ /dev/null
@@ -1,34 +0,0 @@
-/* override table width restrictions */
-@media screen and (min-width: 639px) {
-    .wy-table-responsive table td {
-        /* !important prevents the common CSS stylesheets from
-           overriding this as on RTD they are loaded after this stylesheet */
-        white-space: normal !important;
-    }
-}
-
-/* override table padding */
-.rst-content table.docutils th, .rst-content table.docutils td {
-    padding: 4px 6px;
-}
-
-/* Add two-column option */
-@media only screen and (min-width: 1000px) {
-    .columns {
-        padding-left: 10px;
-        padding-right: 10px;
-        float: left;
-        width: 50%;
-        min-height: 145px;
-    }
-}
-
-.endcolumns {
-    clear: both
-}
-
-/* Hide toctrees within columns and captions from all toctrees.
-   This is what makes the include trick in index.rst work */
-.columns .toctree-wrapper, .toctree-wrapper .caption-text {
-    display: none;
-}
diff --git a/docs/conf.py b/docs/conf.py
index d8c96a23..15fe8905 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -4,6 +4,12 @@
 # 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 scanspec
 
 # -- General configuration ------------------------------------------------
@@ -16,8 +22,10 @@
 
 # The short X.Y version.
 if "+" in release:
-    # Not on a tag
-    version = "master"
+    # 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
 
@@ -40,6 +48,10 @@
     "sphinx_apischema",
     # Add example_spec directive
     "scanspec.sphinxext",
+    # 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
@@ -53,8 +65,18 @@
 nitpick_ignore = [
     ("py:func", "int"),
     ("py:class", "Axis"),
+    ("py:class", "~Axis"),
+    ("py:class", "scanspec.core.Axis"),
     ("py:class", "AxesPoints"),
     ("py:class", "np.ndarray"),
+    ("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
@@ -107,27 +129,77 @@
 
 # Common links that should be available on every page
 rst_epilog = """
-.. _Diamond Light Source:
-    http://www.diamond.ac.uk
+.. _Diamond Light Source: http://www.diamond.ac.uk
+.. _black: https://github.com/psf/black
+.. _flake8: https://flake8.pycqa.org/en/latest/
+.. _isort: https://github.com/PyCQA/isort
+.. _mypy: http://mypy-lang.org/
+.. _pre-commit: https://pre-commit.com/
 """
 
-# Ignore localhost links for period check that links in docs are valid
+# 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 = "sphinx_rtd_theme_github_versions"
-
-# Options for the sphinx rtd theme, use DLS blue
-html_theme_options = dict(style_nav_header_background="rgb(7, 43, 93)")
+html_theme = "pydata_sphinx_theme"
+github_repo = project
+github_user = "dls-controls"
+switcher_json = f"https://{github_user}.github.io/{github_repo}/switcher.json"
+# Don't check switcher if it doesn't exist, but warn in a non-failing way
+check_switcher = requests.get(switcher_json).ok
+if not check_switcher:
+    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
+html_theme_options = dict(
+    logo=dict(
+        text=project,
+    ),
+    use_edit_page_button=True,
+    github_url=f"https://github.com/{github_user}/{github_repo}",
+    icon_links=[
+        dict(
+            name="PyPI",
+            url=f"https://pypi.org/project/{project}",
+            icon="fas fa-cube",
+        )
+    ],
+    switcher=dict(
+        json_url=switcher_json,
+        version_match=version,
+    ),
+    check_switcher=check_switcher,
+    navbar_end=["theme-switcher", "icon-links", "version-switcher"],
+    external_links=[
+        dict(
+            name="Release Notes",
+            url=f"https://github.com/{github_user}/{github_repo}/releases",
+        )
+    ],
+)
 
-# Add any paths that contain custom static files (such as style sheets) here,
-# relative to this directory. They are copied after the builtin static files,
-# so a file named "default.css" will overwrite the builtin "default.css".
-html_static_path = ["_static"]
+# A dictionary of values to pass into the template engine’s context for all pages
+html_context = dict(
+    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
@@ -135,9 +207,6 @@
 # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
 html_show_copyright = False
 
-# Add some CSS classes for columns and other tweaks in a custom css file
-html_css_files = ["theme_overrides.css"]
-
 # Logo
 html_logo = "images/scanspec-logo.svg"
 html_favicon = "images/scanspec-logo.ico"
diff --git a/docs/index.rst b/docs/index.rst
index 545dc316..2f712348 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -9,41 +9,37 @@ How the documentation is structured
 Documentation is split into four categories, also accessible from links in the
 side-bar.
 
-.. rst-class:: columns
+.. grid:: 4
 
-`tutorials`
-~~~~~~~~~~~
+    .. grid-item-card:: :material-regular:`person;4em`
+        :link: tutorials
+        :link-type: doc
 
-.. include:: tutorials.rst
-    :start-after: =========
+        Tutorials
 
-.. rst-class:: columns
+    .. grid-item-card:: :material-regular:`code;4em`
+        :link: how-to
+        :link-type: doc
 
-`how-to`
-~~~~~~~~
+        How-To
 
-.. include:: how-to.rst
-    :start-after: =============
+    .. grid-item-card:: :material-regular:`person;4em`
+        :link: explanations
+        :link-type: doc
 
-.. rst-class:: columns
+        Explanations
 
-`explanations`
-~~~~~~~~~~~~~~
+    .. grid-item-card:: :material-regular:`code;4em`
+        :link: reference
+        :link-type: doc
 
-.. include:: explanations.rst
-    :start-after: ============
+        Reference
 
-.. rst-class:: columns
 
-`reference`
-~~~~~~~~~~~
+.. toctree::
+    :hidden:
 
-.. include:: reference.rst
-    :start-after: =========
-
-.. rst-class:: endcolumns
-
-About the documentation
-~~~~~~~~~~~~~~~~~~~~~~~
-
-`Why is the documentation structured this way? <https://documentation.divio.com>`_
+    tutorials
+    how-to
+    explanations
+    reference
diff --git a/docs/reference/contributing.rst b/docs/reference/contributing.rst
index ac7b6bcf..65b72c8e 100644
--- a/docs/reference/contributing.rst
+++ b/docs/reference/contributing.rst
@@ -1,1 +1,1 @@
-.. include:: ../../CONTRIBUTING.rst
+.. include:: ../../.github/CONTRIBUTING.rst
diff --git a/docs/tutorials/installation.rst b/docs/tutorials/installation.rst
index f63afb1f..2aa34efa 100644
--- a/docs/tutorials/installation.rst
+++ b/docs/tutorials/installation.rst
@@ -1,23 +1,14 @@
 Installation
 ============
 
-.. note::
-
-    For installation inside DLS, please see the internal documentation on
-    ``dls-python3`` and ``pipenv``. Although these instructions will work
-    inside DLS, they are intended for external use.
-
-    If you want to contribute to the library itself, please follow
-    the `../reference/contributing` instructions.
-
-
 Check your version of python
 ----------------------------
 
-You will need python 3.7 or later. You can check your version of python by
+You will need python 3.8 or later. You can check your version of python by
 typing into a terminal::
 
-    python3 --version
+    $ python3 --version
+
 
 Create a virtual environment
 ----------------------------
@@ -25,8 +16,8 @@ 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
+    $ python3 -m venv /path/to/venv
+    $ source /path/to/venv/bin/activate
 
 
 Installing the library
diff --git a/pyproject.toml b/pyproject.toml
index ccd70c76..8dc29894 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,136 @@
 [build-system]
-# To get a reproducible wheel, wheel must be pinned to the same version as in
-# dls-python3, and setuptools must produce the same dist-info. Cap setuptools
-# to the last version that didn't add License-File to METADATA
-requires = ["setuptools<57", "wheel==0.33.1"]
+requires = ["setuptools>=64", "setuptools_scm[toml]>=6.2", "wheel"]
 build-backend = "setuptools.build_meta"
+
+[project]
+name = "scanspec"
+classifiers = [
+    "Development Status :: 3 - Alpha",
+    "License :: OSI Approved :: Apache Software License",
+    "Programming Language :: Python :: 3.8",
+    "Programming Language :: Python :: 3.9",
+    "Programming Language :: Python :: 3.10",
+    "Programming Language :: Python :: 3.11",
+]
+description = "Specify step and flyscan paths in a serializable, efficient and Pythonic way"
+dependencies = [
+    "numpy>=1.19.3",
+    "click",
+    "apischema>=0.17.2",
+    "typing_extensions",
+]
+dynamic = ["version"]
+license.file = "LICENSE"
+readme = "README.rst"
+requires-python = ">=3.8"
+
+[project.optional-dependencies]
+# Plotting
+plotting = [
+    # make sure a python 3.9 compatible scipy and matplotlib are selected
+    "scipy>=1.5.4",
+    "matplotlib>=3.2.2,<3.6",
+]
+# GraphQL service support
+graphql = ["graphql-server[aiohttp]==3.0.0b5", "aiohttp_cors"]
+# For development tests/docs
+dev = [
+    "black==22.3.0",
+    "mypy<=0.982",
+    "flake8-isort",
+    "Flake8-pyproject",
+    "pipdeptree",
+    "pre-commit",
+    "pydata-sphinx-theme>=0.12",
+    "pytest-cov",
+    "pydocstyle",
+    "sphinx-autobuild",
+    "sphinx-copybutton",
+    "sphinx-design",
+    "sphinx-graphql",
+    "sphinx-apischema",
+    "tox-direct",
+    "types-mock",
+]
+
+[project.scripts]
+scanspec = "scanspec.cli:cli"
+
+[project.urls]
+GitHub = "https://github.com/dls-controls/scanspec"
+
+[[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/scanspec/_version.py"
+
+[tool.mypy]
+ignore_missing_imports = true # Ignore missing stubs in imported modules
+
+[tool.isort]
+float_to_top = true
+profile = "black"
+
+[tool.flake8]
+extend-ignore = [
+    "E203", # See https://github.com/PyCQA/pycodestyle/issues/373
+    "F811", # support typing.overload decorator
+    "F722", # allow Annotated[typ, some_func("some string")]
+]
+max-line-length = 88 # Respect black's line length (default 88),
+exclude = [".tox", "venv"]
+
+[tool.pydocstyle]
+convention = "google"
+add-ignore = [
+    "D100", # Ignore missing docstring in public modules
+    "D102", # Ignore missing docstring in public method; See: https://github.com/PyCQA/pydocstyle/issues/309
+    "D104", # Ignore missing docstring in public packages
+    "D105", # Ignore missing docstring in magic method
+    "D107", # Ignore missing docstring in __init__
+]
+
+[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"
+    --cov=scanspec --cov-report term --cov-report xml:cov.xml
+    """
+# 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/scanspec.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 {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
+"""
diff --git a/setup.cfg b/setup.cfg
deleted file mode 100644
index c41e8061..00000000
--- a/setup.cfg
+++ /dev/null
@@ -1,119 +0,0 @@
-[metadata]
-name = scanspec
-description = Specify step and flyscan paths in a serializable, efficient and Pythonic way
-url = https://github.com/dls-controls/scanspec
-author = Tom Cobb
-author_email = tom.cobb@diamond.ac.uk
-license = Apache License 2.0
-long_description = file: README.rst
-long_description_content_type = text/x-rst
-classifiers =
-    Development Status :: 4 - Beta
-    License :: OSI Approved :: Apache Software License
-    Programming Language :: Python :: 3.7
-    Programming Language :: Python :: 3.8
-    Programming Language :: Python :: 3.9
-
-[options]
-python_requires = >=3.7
-packages = find:
-# =src is interpreted as {"": "src"}
-# as per recommendation here https://hynek.me/articles/testing-packaging/
-package_dir =
-    =src
-install_requires =
-    # make sure a python 3.9 compatible numpy is selected
-    numpy>=1.19.3
-    click
-    apischema>=0.17.2
-    typing_extensions
-
-[options.extras_require]
-# Plotting
-plotting =
-    # make sure a python 3.9 compatible scipy and matplotlib are selected
-    scipy>=1.5.4
-    matplotlib>=3.2.2
-# GraphQL service support
-graphql =
-    graphql-server[aiohttp]==3.0.0b5
-    aiohttp_cors
-# For development tests/docs
-dev =
-    black==22.3.0
-    isort>5.0
-    pytest-cov
-    mypy
-    # remove this dependency once flake8 has dropped "importlib-metadata <=4.3"
-    # https://github.com/PyCQA/flake8/pull/1438
-    flake8 <= 3.9.2
-    flake8-isort
-    flake8-pydocstyle
-    # TODO: work out why 4.4+ produces
-    # WARNING: py:class reference target not found: scanspec.core.Axis
-    sphinx < 4.4.0
-    sphinx-rtd-theme-github-versions
-    sphinx-graphql
-    sphinx-apischema
-    pre-commit
-
-[options.packages.find]
-where = src
-
-# Specify any package data to be included in the wheel below.
-# [options.package_data]
-# scanspec =
-#     subpackage/*.yaml
-
-[options.entry_points]
-# Include a command line script
-console_scripts =
-    scanspec = scanspec.cli:cli
-
-[mypy]
-# Ignore missing stubs for modules we use
-ignore_missing_imports = True
-
-[isort]
-profile=black
-float_to_top=true
-
-[flake8]
-# Make flake8 respect black's line length (default 88),
-max-line-length = 88
-extend-ignore =
-    E203,  # See https://github.com/PyCQA/pycodestyle/issues/373
-    F811,  # support typing.overload decorator
-    F722,  # Suppress 'syntax error in forward annotation'
-
-[pydocstyle]
-# Make pydocstyle use google docstring format
-convention = google
-add-ignore =
-    D100, # Ignore missing docstring in public modules
-    D102, # Ignore missing docstring in public method; See: https://github.com/PyCQA/pydocstyle/issues/309
-    D104, # Ignore missing docstring in public packages
-    D105, # Ignore missing docstring in magic method
-    D107, # Ignore missing docstring in __init__
-
-[tool:pytest]
-# 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"
-    --cov=scanspec --cov-report term --cov-report xml:cov.xml
-# 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
-
-[coverage:run]
-# This is covered in the versiongit test suite so exclude it here
-omit = */_version_git.py
-data_file = /tmp/scanspec.coverage
-
-[coverage:paths]
-# Tests are run from installed location, map back to the src directory
-source =
-    src
-    **/site-packages/
diff --git a/setup.py b/setup.py
deleted file mode 100644
index 39de827e..00000000
--- a/setup.py
+++ /dev/null
@@ -1,13 +0,0 @@
-# type: ignore
-import glob
-import importlib.util
-
-from setuptools import setup
-
-# Import <package>._version_git.py without importing <package>
-path = glob.glob(__file__.replace("setup.py", "src/*/_version_git.py"))[0]
-spec = importlib.util.spec_from_file_location("_version_git", path)
-vg = importlib.util.module_from_spec(spec)
-spec.loader.exec_module(vg)
-
-setup(cmdclass=vg.get_cmdclass(), version=vg.__version__)
diff --git a/src/scanspec/__init__.py b/src/scanspec/__init__.py
index 08445c4b..dee838fe 100644
--- a/src/scanspec/__init__.py
+++ b/src/scanspec/__init__.py
@@ -1,4 +1,6 @@
-from . import regions, specs
-from ._version_git import __version__
+from importlib.metadata import version
+
+__version__ = version("scanspec")
+del version
 
 __all__ = ["__version__", "specs", "regions"]
diff --git a/src/scanspec/_version_git.py b/src/scanspec/_version_git.py
deleted file mode 100644
index bb7f0c2a..00000000
--- a/src/scanspec/_version_git.py
+++ /dev/null
@@ -1,100 +0,0 @@
-# Compute a version number from a git repo or archive
-
-# This file is released into the public domain. Generated by:
-# versiongit-2.1 (https://github.com/dls-controls/versiongit)
-import re
-import sys
-from pathlib import Path
-from subprocess import STDOUT, CalledProcessError, check_output
-
-# These will be filled in if git archive is run or by setup.py cmdclasses
-GIT_REFS = "$Format:%D$"
-GIT_SHA1 = "$Format:%h$"
-
-# Git describe gives us sha1, last version-like tag, and commits since then
-CMD = "git describe --tags --dirty --always --long --match=[0-9]*[-.][0-9]*"
-
-
-def get_version_from_git(path=None):
-    """Try to parse version from git describe, fallback to git archive tags."""
-    tag, plus, suffix = "0.0", "untagged", ""
-    if not GIT_SHA1.startswith("$"):
-        # git archive or the cmdclasses below have filled in these strings
-        sha1 = GIT_SHA1
-        for ref_name in GIT_REFS.split(", "):
-            if ref_name.startswith("tag: "):
-                # git from 1.8.3 onwards labels archive tags "tag: TAGNAME"
-                tag, plus = ref_name[5:], "0"
-    else:
-        if path is None:
-            # If no path to git repo, choose the directory this file is in
-            path = Path(__file__).absolute().parent
-        # output is TAG-NUM-gHEX[-dirty] or HEX[-dirty]
-        try:
-            cmd_out = check_output(CMD.split(), stderr=STDOUT, cwd=path)
-        except Exception as e:
-            sys.stderr.write("%s: %s\n" % (type(e).__name__, str(e)))
-            if isinstance(e, CalledProcessError):
-                sys.stderr.write("-> %s" % e.output.decode())
-            return "0.0+unknown", None, e
-        else:
-            out = cmd_out.decode().strip()
-            if out.endswith("-dirty"):
-                out = out[:-6]
-                suffix = ".dirty"
-            if "-" in out:
-                # There is a tag, extract it and the other pieces
-                match = re.search(r"^(.+)-(\d+)-g([0-9a-f]+)$", out)
-                tag, plus, sha1 = match.groups()
-            else:
-                # No tag, just sha1
-                sha1 = out
-    # Replace dashes in tag for dots
-    tag = tag.replace("-", ".")
-    if plus != "0" or suffix:
-        # Not on a tag, add additional info
-        tag = f"{tag}+{plus}.g{sha1}{suffix}"
-    return tag, sha1, None
-
-
-__version__, git_sha1, git_error = get_version_from_git()
-
-
-def get_cmdclass(build_py=None, sdist=None):
-    """Create cmdclass dict to pass to setuptools.setup.
-
-    Create cmdclass dict to pass to setuptools.setup which will write a
-    _version_static.py file in our resultant sdist, wheel or egg.
-    """
-    if build_py is None:
-        from setuptools.command.build_py import build_py
-    if sdist is None:
-        from setuptools.command.sdist import sdist
-
-    def make_version_static(base_dir: str, pkg: str):
-        vg = Path(base_dir) / pkg.split(".")[0] / "_version_git.py"
-        if vg.is_file():
-            lines = open(vg).readlines()
-            with open(vg, "w") as f:
-                for line in lines:
-                    # Replace GIT_* with static versions
-                    if line.startswith("GIT_SHA1 = "):
-                        f.write("GIT_SHA1 = '%s'\n" % git_sha1)
-                    elif line.startswith("GIT_REFS = "):
-                        f.write("GIT_REFS = 'tag: %s'\n" % __version__)
-                    else:
-                        f.write(line)
-
-    class BuildPy(build_py):
-        def run(self):
-            build_py.run(self)
-            for pkg in self.packages:
-                make_version_static(self.build_lib, pkg)
-
-    class Sdist(sdist):
-        def make_release_tree(self, base_dir, files):
-            sdist.make_release_tree(self, base_dir, files)
-            for pkg in self.distribution.packages:
-                make_version_static(base_dir, pkg)
-
-    return dict(build_py=BuildPy, sdist=Sdist)
diff --git a/src/scanspec/cli.py b/src/scanspec/cli.py
index fb7b534e..75a3cf22 100644
--- a/src/scanspec/cli.py
+++ b/src/scanspec/cli.py
@@ -16,7 +16,7 @@
         ["CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG"], case_sensitive=False
     ),
 )
-@click.version_option()
+@click.version_option(prog_name="scanspec")
 @click.pass_context
 def cli(ctx, log_level: str):
     """Top level scanspec command line interface."""
diff --git a/src/scanspec/core.py b/src/scanspec/core.py
index 9774d7b0..e041055e 100644
--- a/src/scanspec/core.py
+++ b/src/scanspec/core.py
@@ -419,6 +419,7 @@ def extract(self, indices: np.ndarray, calculate_gap=True) -> Frames[Axis]:
         length = len(self)
         backwards = (indices // length) % 2
         snake_indices = np.where(backwards, (length - 1) - indices, indices) % length
+        cls: Type[Frames[Any]]
         if not calculate_gap:
             cls = Frames
             gap = self.gap[np.where(backwards, length - indices, indices) % length]
@@ -618,7 +619,7 @@ def axes(self) -> List[Axis]:
 
     def __len__(self) -> int:
         """The number of dictionaries that will be produced if iterated over."""
-        return np.product([len(frames) for frames in self.stack])
+        return int(np.product([len(frames) for frames in self.stack]))
 
     def __iter__(self) -> Iterator[Dict[Axis, float]]:
         """Yield {axis: midpoint} for each frame in the scan."""
diff --git a/src/scanspec/sphinxext.py b/src/scanspec/sphinxext.py
index 8e5edbe0..ecde40d9 100644
--- a/src/scanspec/sphinxext.py
+++ b/src/scanspec/sphinxext.py
@@ -2,7 +2,7 @@
 
 from matplotlib.sphinxext import plot_directive
 
-from ._version_git import __version__
+from . import __version__
 
 
 @contextmanager


Test patch:
diff --git a/tests/test_boilerplate_removed.py b/tests/test_boilerplate_removed.py
index ca8086cb..22949dcc 100644
--- a/tests/test_boilerplate_removed.py
+++ b/tests/test_boilerplate_removed.py
@@ -2,14 +2,14 @@
 This file checks that all the example boilerplate text has been removed.
 It can be deleted when all the contained tests pass
 """
-import configparser
+from importlib.metadata import metadata
 from pathlib import Path
 
 ROOT = Path(__file__).parent.parent
 
 
 def skeleton_check(check: bool, text: str):
-    if ROOT.name == "dls-python3-skeleton":
+    if ROOT.name == "python3-pip-skeleton" or str(ROOT) == "/project":
         # In the skeleton module the check should fail
         check = not check
         text = f"Skeleton didn't raise: {text}"
@@ -24,19 +24,12 @@ def assert_not_contains_text(path: str, text: str, explanation: str):
         skeleton_check(text in contents, f"Please change ./{path} {explanation}")
 
 
-def assert_not_exists(path: str, explanation: str):
-    exists = (ROOT / path).exists()
-    skeleton_check(exists, f"Please delete ./{path} {explanation}")
-
-
-# setup.cfg
-def test_module_description():
-    conf = configparser.ConfigParser()
-    conf.read("setup.cfg")
-    description = conf["metadata"]["description"]
+# pyproject.toml
+def test_module_summary():
+    summary = metadata("scanspec")["summary"]
     skeleton_check(
-        "One line description of your module" in description,
-        "Please change description in ./setup.cfg "
+        "One line description of your module" in summary,
+        "Please change project.description in ./pyproject.toml "
         "to be a one line description of your module",
     )
 
@@ -50,30 +43,17 @@ def test_changed_README_intro():
     )
 
 
-def test_changed_README_body():
+def test_removed_adopt_skeleton():
     assert_not_contains_text(
         "README.rst",
-        "This is where you should put some images or code snippets",
-        "to include some features and why people should use it",
+        "This project contains template code only",
+        "remove the note at the start",
     )
 
 
-# Docs
-def test_docs_ref_api_changed():
+def test_changed_README_body():
     assert_not_contains_text(
-        "docs/reference/api.rst",
-        "You can mix verbose text with docstring and signature",
-        "to introduce the API for your module",
-    )
-
-
-def test_how_tos_written():
-    assert_not_exists(
-        "docs/how-to/accomplish-a-task.rst", "and write some docs/how-tos"
-    )
-
-
-def test_explanations_written():
-    assert_not_exists(
-        "docs/explanations/why-is-something-so.rst", "and write some docs/explanations"
+        "README.rst",
+        "This is where you should put some images or code snippets",
+        "to include some features and why people should use it",
     )
diff --git a/tests/test_cli.py b/tests/test_cli.py
index 5ebf37b8..64a81018 100644
--- a/tests/test_cli.py
+++ b/tests/test_cli.py
@@ -230,4 +230,7 @@ def test_schema() -> None:
 
 def test_cli_version():
     cmd = [sys.executable, "-m", "scanspec", "--version"]
-    assert subprocess.check_output(cmd).decode().strip().endswith(__version__)
+    assert (
+        subprocess.check_output(cmd).decode().strip()
+        == f"scanspec, version {__version__}"
+    )