From a8582adfdd22b9e015450db4f0cbaabe9e961687 Mon Sep 17 00:00:00 2001 From: Man Ting Chan Date: Thu, 22 Feb 2024 10:50:02 +0000 Subject: [PATCH 01/10] changed to allow for updating last application was from a branch so this is no longer recognised --- .copier-answers.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.copier-answers.yml b/.copier-answers.yml index 11eb3df9..6249dd5e 100644 --- a/.copier-answers.yml +++ b/.copier-answers.yml @@ -1,5 +1,5 @@ # Changes here will be overwritten by Copier -_commit: 1.0.0-8-g1361223 +_commit: 1.0.2-5-g9018420 _src_path: gh:DiamondLightSource/python-copier-template author_email: tom.cobb@diamond.ac.uk author_name: Tom Cobb From ae19b7187dcc244984327d75ac46ea6c75d2c059 Mon Sep 17 00:00:00 2001 From: Man Ting Chan Date: Tue, 27 Feb 2024 14:37:14 +0000 Subject: [PATCH 02/10] Updated template to 1.3.0 --- .copier-answers.yml | 3 +- .devcontainer/devcontainer.json | 46 +++++----- .github/CONTRIBUTING.md | 37 ++++++++ .github/CONTRIBUTING.rst | 78 ---------------- .../actions/install_requirements/action.yml | 2 +- .github/pages/make_switcher.py | 2 +- .github/workflows/_check.yml | 9 +- .github/workflows/_docs.yml | 23 ++--- .github/workflows/_pypi.yml | 3 + .github/workflows/_release.yml | 19 ++-- .github/workflows/_test.yml | 20 +++- .github/workflows/ci.yml | 20 ++-- .gitignore | 1 - .pre-commit-config.yaml | 18 ++-- .vscode/settings.json | 24 +---- README.md | 50 ++++++++++ README.rst | 74 --------------- docs/conf.py | 16 ++-- docs/developer/explanations/decisions.rst | 17 ---- .../0001-record-architecture-decisions.rst | 24 ----- .../0002-switched-to-pip-skeleton.rst | 33 ------- docs/developer/how-to/build-docs.rst | 38 -------- docs/developer/how-to/contribute.rst | 1 - docs/developer/how-to/lint.rst | 39 -------- docs/developer/how-to/make-release.rst | 16 ---- docs/developer/how-to/pin-requirements.rst | 74 --------------- docs/developer/how-to/run-tests.rst | 12 --- docs/developer/how-to/static-analysis.rst | 8 -- docs/developer/how-to/test-container.rst | 25 ----- docs/developer/how-to/update-tools.rst | 16 ---- docs/developer/index.rst | 64 ------------- docs/developer/reference/standards.rst | 63 ------------- docs/developer/tutorials/dev-install.rst | 68 -------------- docs/explanations.md | 10 ++ docs/explanations/decisions.md | 12 +++ .../0001-record-architecture-decisions.md | 18 ++++ ...0002-switched-to-python-copier-template.md | 28 ++++++ docs/explanations/decisions/COPYME | 19 ++++ docs/genindex.md | 3 + docs/genindex.rst | 5 - docs/how-to.md | 10 ++ docs/how-to/contribute.md | 2 + docs/how-to/run-container.md | 14 +++ docs/index.md | 56 +++++++++++ docs/index.rst | 29 ------ docs/reference.md | 12 +++ docs/reference/api.md | 17 ++++ docs/tutorials.md | 10 ++ docs/tutorials/installation.md | 48 ++++++++++ docs/user/explanations/docs-structure.rst | 18 ---- docs/user/how-to/run-container.rst | 15 --- docs/user/index.rst | 67 -------------- docs/user/reference/api.rst | 92 ------------------- docs/user/tutorials/installation.rst | 42 --------- pyproject.toml | 26 ++++-- src/pandablocks/__init__.py | 10 +- 56 files changed, 464 insertions(+), 1042 deletions(-) create mode 100644 .github/CONTRIBUTING.md delete mode 100644 .github/CONTRIBUTING.rst create mode 100644 README.md delete mode 100644 README.rst delete mode 100644 docs/developer/explanations/decisions.rst delete mode 100644 docs/developer/explanations/decisions/0001-record-architecture-decisions.rst delete mode 100644 docs/developer/explanations/decisions/0002-switched-to-pip-skeleton.rst delete mode 100644 docs/developer/how-to/build-docs.rst delete mode 100644 docs/developer/how-to/contribute.rst delete mode 100644 docs/developer/how-to/lint.rst delete mode 100644 docs/developer/how-to/make-release.rst delete mode 100644 docs/developer/how-to/pin-requirements.rst delete mode 100644 docs/developer/how-to/run-tests.rst delete mode 100644 docs/developer/how-to/static-analysis.rst delete mode 100644 docs/developer/how-to/test-container.rst delete mode 100644 docs/developer/how-to/update-tools.rst delete mode 100644 docs/developer/index.rst delete mode 100644 docs/developer/reference/standards.rst delete mode 100644 docs/developer/tutorials/dev-install.rst create mode 100644 docs/explanations.md create mode 100644 docs/explanations/decisions.md create mode 100644 docs/explanations/decisions/0001-record-architecture-decisions.md create mode 100644 docs/explanations/decisions/0002-switched-to-python-copier-template.md create mode 100644 docs/explanations/decisions/COPYME create mode 100644 docs/genindex.md delete mode 100644 docs/genindex.rst create mode 100644 docs/how-to.md create mode 100644 docs/how-to/contribute.md create mode 100644 docs/how-to/run-container.md create mode 100644 docs/index.md delete mode 100644 docs/index.rst create mode 100644 docs/reference.md create mode 100644 docs/reference/api.md create mode 100644 docs/tutorials.md create mode 100644 docs/tutorials/installation.md delete mode 100644 docs/user/explanations/docs-structure.rst delete mode 100644 docs/user/how-to/run-container.rst delete mode 100644 docs/user/index.rst delete mode 100644 docs/user/reference/api.rst delete mode 100644 docs/user/tutorials/installation.rst diff --git a/.copier-answers.yml b/.copier-answers.yml index 6249dd5e..ec817bcb 100644 --- a/.copier-answers.yml +++ b/.copier-answers.yml @@ -1,5 +1,5 @@ # Changes here will be overwritten by Copier -_commit: 1.0.2-5-g9018420 +_commit: 1.3.0 _src_path: gh:DiamondLightSource/python-copier-template author_email: tom.cobb@diamond.ac.uk author_name: Tom Cobb @@ -12,3 +12,4 @@ git_platform: github.com github_org: PandABlocks package_name: pandablocks repo_name: PandABlocks-client +type_checker: mypy diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 96427ee4..79b85ff4 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -6,45 +6,41 @@ "target": "developer" }, "remoteEnv": { + // Allow X11 apps to run inside the container "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 - } - }, "customizations": { - // Set *default* container specific settings.json values on container create. - "settings": { - "python.defaultInterpreterPath": "/venv/bin/python" - }, "vscode": { + // Set *default* container specific settings.json values on container create. + "settings": { + "python.defaultInterpreterPath": "/venv/bin/python" + }, // Add the IDs of extensions you want installed when the container is created. "extensions": [ "ms-python.python", + "github.vscode-github-actions", "tamasfe.even-better-toml", "redhat.vscode-yaml", - "ryanluker.vscode-coverage-gutters" + "ryanluker.vscode-coverage-gutters", + "charliermarsh.ruff", + "ms-azuretools.vscode-docker" ] } }, - // 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'", + "features": { + // Some default things like git config + "ghcr.io/devcontainers/features/common-utils:2": { + "upgradePackages": false + } + }, "runArgs": [ + // Allow the container to access the host X11 display and EPICS CA "--net=host", - "--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 sure SELinux does not disable with access to host filesystems like tmp + "--security-opt=label=disable" ], - // make the workspace folder the same inside and outside of the container - "workspaceMount": "source=${localWorkspaceFolder},target=${localWorkspaceFolder},type=bind", - "workspaceFolder": "${localWorkspaceFolder}", + // Mount the parent as /workspaces so we can pip install peers as editable + "workspaceMount": "source=${localWorkspaceFolder}/..,target=/workspaces,type=bind", // After the container is created, install the python project in editable form - "postCreateCommand": "pip install -e '.[dev]'" + "postCreateCommand": "pip install $([ -f dev-requirements.txt ] && echo '-c dev-requirements.txt') -e '.[dev]' && pre-commit install" } \ No newline at end of file diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 00000000..08119298 --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,37 @@ +# Contribute to the project + +Contributions and issues are most welcome! All issues and pull requests are +handled through [GitHub](https://github.com/PandABlocks/PandABlocks-client/issues). Also, please check for any existing issues before +filing a new one. If you have a great idea but it involves big changes, please +file a ticket before making a pull request! We want to make sure you don't spend +your time coding something that might not fit the scope of the project. + +## Issue or Discussion? + +Github also offers [discussions](https://github.com/PandABlocks/PandABlocks-client/discussions) as a place to ask questions and share ideas. If +your issue is open ended and it is not obvious when it can be "closed", please +raise it as a discussion instead. + +## Code Coverage + +While 100% code coverage does not make a library bug-free, it significantly +reduces the number of easily caught bugs! Please make sure coverage remains the +same or is improved by a pull request! + +## Developer Information + +It is recommended that developers use a [vscode devcontainer](https://code.visualstudio.com/docs/devcontainers/containers). This repository contains configuration to set up a containerized development environment that suits its own needs. + +This project was created using the [Diamond Light Source Copier Template](https://github.com/DiamondLightSource/python-copier-template) for Python projects. + +For more information on common tasks like setting up a developer environment, running the tests, and setting a pre-commit hook, see the template's [How-to guides](https://diamondlightsource.github.io/python-copier-template/1.3.0/how-to.html). + +## Release Checklist + +Before a new release, please go through the following checklist: + +- Choose a new PEP440 compliant release number +- Add a release note in CHANGELOG.rst +- Git tag the version with message from CHANGELOG +- 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 diff --git a/.github/CONTRIBUTING.rst b/.github/CONTRIBUTING.rst deleted file mode 100644 index b426da9a..00000000 --- a/.github/CONTRIBUTING.rst +++ /dev/null @@ -1,78 +0,0 @@ -Contributing -============ - -Contributions and issues are most welcome! All issues and pull requests are -handled through github on the `PandABlocks-client repository`_. 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. - -.. _PandABlocks-client repository: https://github.com/PandABlocks/PandABlocks-client/issues - -Running the tests ------------------ - -To get the source code and run the unit tests, run:: - - $ git clone git@github.com:PandABlocks/PandABlocks-client.git - $ cd PandABlocks-client - $ python3 -m venv /path/to/venv - $ source /path/to/venv/bin/activate - $ pytest - -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 - -.. _black: https://github.com/psf/black -.. _flake8: http://flake8.pycqa.org/en/latest/ -.. _isort: https://github.com/timothycrosley/isort - -These tests will be run on code when running ``pytest`` and also -automatically at check in. Please read the tool documentation for details -on how to fix the errors it reports. - -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:: - - $ tox -e docs - $ firefox build/html/index.html - -Release Checklist ------------------ - -Before a new release, please go through the following checklist: - -- Choose a new PEP440 compliant release number -- Add a release note in CHANGELOG.rst -- Git tag the version with message from CHANGELOG -- 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 diff --git a/.github/actions/install_requirements/action.yml b/.github/actions/install_requirements/action.yml index aab283a7..d33e0805 100644 --- a/.github/actions/install_requirements/action.yml +++ b/.github/actions/install_requirements/action.yml @@ -6,7 +6,7 @@ inputs: default: "dev" pip-install: description: Parameters to pass to pip install - default: "-e .[dev]" + default: "$([ -f dev-requirements.txt ] && echo '-c dev-requirements.txt') -e .[dev]" runs: using: composite diff --git a/.github/pages/make_switcher.py b/.github/pages/make_switcher.py index f4cffd1b..e2c8e6f6 100755 --- a/.github/pages/make_switcher.py +++ b/.github/pages/make_switcher.py @@ -56,7 +56,7 @@ def get_versions(ref: str, add: Optional[str]) -> List[str]: 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}/") + {"version": version, "url": f"https://{org}.github.io/{repo_name}/{version}/"} for version in versions ] text = json.dumps(struct, indent=2) diff --git a/.github/workflows/_check.yml b/.github/workflows/_check.yml index b26d72a5..a6139c19 100644 --- a/.github/workflows/_check.yml +++ b/.github/workflows/_check.yml @@ -1,15 +1,15 @@ on: workflow_call: outputs: - not-in-pr: + branch-pr: description: The PR number if the branch is in one - value: ${{ jobs.pr.outputs.not-in-pr }} + value: ${{ jobs.pr.outputs.branch-pr }} jobs: pr: runs-on: "ubuntu-latest" outputs: - not-in-pr: ${{ steps.script.outputs.result }} + branch-pr: ${{ steps.script.outputs.result }} steps: - uses: actions/github-script@v7 id: script @@ -23,6 +23,5 @@ jobs: }) if (prs.data.length) { console.log(`::notice ::Skipping CI on branch push as it is already run in PR #${prs.data[0]["number"]}`) - } else { - return "not-in-pr" + return prs.data[0]["number"] } diff --git a/.github/workflows/_docs.yml b/.github/workflows/_docs.yml index 4d9d7ad1..40446e33 100644 --- a/.github/workflows/_docs.yml +++ b/.github/workflows/_docs.yml @@ -6,6 +6,10 @@ jobs: runs-on: ubuntu-latest steps: + - name: Avoid git conflicts when tag and branch pushed at same time + if: github.ref_type == 'tag' + run: sleep 60 + - name: Checkout uses: actions/checkout@v4 with: @@ -24,23 +28,20 @@ jobs: - name: Remove environment.pickle run: rm build/html/.doctrees/environment.pickle - - name: Sanitize ref name for docs version - run: echo "DOCS_VERSION=${GITHUB_REF_NAME//[^A-Za-z0-9._-]/_}" >> $GITHUB_ENV - - - name: Move to versioned directory - run: mv build/html build/$DOCS_VERSION - - name: Upload built docs artifact uses: actions/upload-artifact@v4 with: name: docs path: build - - name: Add other static pages files - run: cp .github/pages/* build + - name: Sanitize ref name for docs version + run: echo "DOCS_VERSION=${GITHUB_REF_NAME//[^A-Za-z0-9._-]/_}" >> $GITHUB_ENV + + - name: Move to versioned directory + run: mv build/html .github/pages/$DOCS_VERSION - name: Write switcher.json - run: python .github/pages/make_switcher.py --add $DOCS_VERSION ${{ github.repository }} build/switcher.json + run: python .github/pages/make_switcher.py --add $DOCS_VERSION ${{ github.repository }} .github/pages/switcher.json - name: Publish Docs to gh-pages if: github.ref_type == 'tag' || github.ref_name == 'main' @@ -49,5 +50,5 @@ jobs: uses: peaceiris/actions-gh-pages@373f7f263a76c20808c831209c920827a82a2847 # v3.9.3 with: github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: build - keep_files: true + publish_dir: .github/pages + keep_files: true \ No newline at end of file diff --git a/.github/workflows/_pypi.yml b/.github/workflows/_pypi.yml index 69103d1e..f2ead1bc 100644 --- a/.github/workflows/_pypi.yml +++ b/.github/workflows/_pypi.yml @@ -1,5 +1,8 @@ on: workflow_call: + secrets: + PYPI_TOKEN: + required: true jobs: upload: diff --git a/.github/workflows/_release.yml b/.github/workflows/_release.yml index 84f68ed6..b49fa7dc 100644 --- a/.github/workflows/_release.yml +++ b/.github/workflows/_release.yml @@ -8,15 +8,16 @@ jobs: steps: - name: Download artifacts uses: actions/download-artifact@v4 + with: + merge-multiple: true - - name: Prepare release files + - name: Zip up docs run: | - if [ -d docs ]; then - cd docs && zip -r docs.zip * - echo 'DOCS=docs/docs.zip' >> $GITHUB_ENV - fi - if [ -d dist ]; then - echo 'DIST=dist/*' >> $GITHUB_ENV + set -vxeuo pipefail + if [ -d html ]; then + mv html $GITHUB_REF_NAME + zip -r docs.zip $GITHUB_REF_NAME + rm -rf $GITHUB_REF_NAME fi - name: Create GitHub Release @@ -25,9 +26,7 @@ jobs: uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v0.1.15 with: prerelease: ${{ contains(github.ref_name, 'a') || contains(github.ref_name, 'b') || contains(github.ref_name, 'rc') }} - files: | - ${{ env.DOCS }} - ${{ env.DIST }} + files: "*" generate_release_notes: true env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/_test.yml b/.github/workflows/_test.yml index 9f164afe..f652d414 100644 --- a/.github/workflows/_test.yml +++ b/.github/workflows/_test.yml @@ -9,6 +9,9 @@ on: type: string description: The runner to run this job on required: true + secrets: + CODECOV_TOKEN: + required: true env: # https://github.com/pytest-dev/pytest/issues/2042 @@ -29,6 +32,17 @@ jobs: name: Install dev versions of python packages uses: ./.github/actions/install_requirements + - if: inputs.python-version == 'dev' + name: Write the requirements as an artifact + run: pip freeze --exclude-editable > /tmp/dev-requirements.txt + + - if: inputs.python-version == 'dev' + name: Upload dev-requirements.txt + uses: actions/upload-artifact@v4 + with: + name: dev-requirements + path: /tmp/dev-requirements.txt + - if: inputs.python-version != 'dev' name: Install latest versions of python packages uses: ./.github/actions/install_requirements @@ -37,10 +51,12 @@ jobs: pip-install: ".[dev]" - name: Run tests - run: tox -e pytest + run: tox -e tests - name: Upload coverage to Codecov - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 with: name: ${{ inputs.python-version }}/${{ inputs.runs-on }} files: cov.xml + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5ba6f3a8..7e17ec93 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,14 +10,14 @@ jobs: lint: needs: check - if: needs.check.outputs.not-in-pr + if: needs.check.outputs.branch-pr == '' uses: ./.github/workflows/_tox.yml with: - tox: pre-commit,mypy + tox: pre-commit,type-checking test: needs: check - if: needs.check.outputs.not-in-pr + if: needs.check.outputs.branch-pr == '' strategy: matrix: runs-on: ["ubuntu-latest"] # can add windows-latest, macos-latest @@ -31,31 +31,35 @@ jobs: with: runs-on: ${{ matrix.runs-on }} python-version: ${{ matrix.python-version }} + secrets: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} container: needs: check - if: needs.check.outputs.not-in-pr + if: needs.check.outputs.branch-pr == '' uses: ./.github/workflows/_container.yml permissions: packages: write docs: needs: check - if: needs.check.outputs.not-in-pr + if: needs.check.outputs.branch-pr == '' uses: ./.github/workflows/_docs.yml dist: needs: check - if: needs.check.outputs.not-in-pr + if: needs.check.outputs.branch-pr == '' uses: ./.github/workflows/_dist.yml - + pypi: if: github.ref_type == 'tag' needs: dist uses: ./.github/workflows/_pypi.yml permissions: id-token: write - + secrets: + PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }} + release: if: github.ref_type == 'tag' needs: [dist, docs] diff --git a/.gitignore b/.gitignore index a37be99b..2593ec75 100644 --- a/.gitignore +++ b/.gitignore @@ -8,7 +8,6 @@ __pycache__/ # Distribution / packaging .Python env/ -.venv build/ develop-eggs/ dist/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5bc9f001..5a4cbf7b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + rev: v4.5.0 hooks: - id: check-added-large-files - id: check-yaml @@ -8,16 +8,16 @@ repos: - repo: local hooks: - - id: black - name: Run black - stages: [commit] + - id: ruff + name: lint with ruff language: system - entry: black --check --diff + entry: ruff check --force-exclude types: [python] + require_serial: true - - id: ruff - name: Run ruff - stages: [commit] + - id: ruff-format + name: format with ruff language: system - entry: ruff + entry: ruff format --force-exclude types: [python] + require_serial: true diff --git a/.vscode/settings.json b/.vscode/settings.json index 8b583913..c129d991 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,29 +1,11 @@ { - "python.linting.pylintEnabled": false, - "python.linting.flake8Enabled": false, - "python.linting.mypyEnabled": true, - "python.linting.enabled": true, - "python.testing.pytestArgs": [ - "--cov=pandablocks", - "--cov-report", - "xml:cov.xml" - ], "python.testing.unittestEnabled": false, "python.testing.pytestEnabled": true, - "python.formatting.provider": "black", - "python.languageServer": "Pylance", "editor.formatOnSave": true, "editor.codeActionsOnSave": { "source.organizeImports": "explicit" }, - "[jsonc]": { - "editor.defaultFormatter": "vscode.json-language-features" - }, - "python.analysis.typeCheckingMode": "off", "[python]": { - "editor.codeActionsOnSave": { - "source.fixAll.ruff": "never", - "source.organizeImports.ruff": "explicit" - } - } -} + "editor.defaultFormatter": "charliermarsh.ruff", + }, +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 00000000..7efd8a43 --- /dev/null +++ b/README.md @@ -0,0 +1,50 @@ +[![CI](https://github.com/PandABlocks/PandABlocks-client/actions/workflows/ci.yml/badge.svg)](https://github.com/PandABlocks/PandABlocks-client/actions/workflows/ci.yml) +[![Coverage](https://codecov.io/gh/PandABlocks/PandABlocks-client/branch/main/graph/badge.svg)](https://codecov.io/gh/PandABlocks/PandABlocks-client) +[![PyPI](https://img.shields.io/pypi/v/pandablocks.svg)](https://pypi.org/project/pandablocks) +[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) + +# pandablocks + +A Python client to control and data ports of the PandABlocks TCP server + +Source | +:---: | :---: +PyPI | `pip install pandablocks` +Documentation | +Releases | + +Command line tool features an interactive console, load/save control, and HDF5 writing: + +```shell +$ pip install pandablocks + +$ pandablocks control +< PCAP. # Hit TAB key... +PCAP.ACTIVE PCAP.BITS1 PCAP.BITS3 PCAP.GATE PCAP.SAMPLES PCAP.TRIG PCAP.TS_END PCAP.TS_TRIG +PCAP.BITS0 PCAP.BITS2 PCAP.ENABLE PCAP.HEALTH PCAP.SHIFT_SUM PCAP.TRIG_EDGE PCAP.TS_START +< PCAP.ACTIVE? +OK =1 + +$ pandablocks hdf /tmp/panda-%d.h5 +INFO:Opened '/tmp/panda-1.h5' with 60 byte samples stored in 11 datasets +INFO:Closed '/tmp/panda-1.h5' after writing 50000000 samples. End reason is 'Disarmed' +``` + +Library features a Sans-IO core with both asyncio and blocking wrappers: + +```python +from pandablocks.blocking import BlockingClient +from pandablocks.commands import Get + +with BlockingClient("hostname-or-ip") as client: + # Commands sent to Control port + idn = client.send(Get("*IDN")) + print(f"Hello {idn}") + for data in client.data(): + # Data captured from Data port + print(f"I got some PCAP data {data}") +``` + + + +See https://pandablocks.github.io/PandABlocks-client for more detailed documentation. diff --git a/README.rst b/README.rst deleted file mode 100644 index cbcca332..00000000 --- a/README.rst +++ /dev/null @@ -1,74 +0,0 @@ -PandABlocks Python Client -========================= - -|code_ci| |docs_ci| |coverage| |pypi_version| |license| - -A Python client which connects to the control and data ports of the PandABlocks TCP server. - -============== ============================================================== -PyPI ``pip install pandablocks`` -Source code https://github.com/PandABlocks/PandABlocks-client -Documentation https://PandABlocks.github.io/PandABlocks-client -Releases https://github.com/PandABlocks/PandABlocks-client/releases -============== ============================================================== - -Command line tool features an interactive console, load/save control, and HDF5 -writing: - -.. code:: - - $ pip install pandablocks - - $ pandablocks control - < PCAP. # Hit TAB key... - PCAP.ACTIVE PCAP.BITS1 PCAP.BITS3 PCAP.GATE PCAP.SAMPLES PCAP.TRIG PCAP.TS_END PCAP.TS_TRIG - PCAP.BITS0 PCAP.BITS2 PCAP.ENABLE PCAP.HEALTH PCAP.SHIFT_SUM PCAP.TRIG_EDGE PCAP.TS_START - < PCAP.ACTIVE? - OK =1 - - $ pandablocks hdf /tmp/panda-%d.h5 - INFO:Opened '/tmp/panda-1.h5' with 60 byte samples stored in 11 datasets - INFO:Closed '/tmp/panda-1.h5' after writing 50000000 samples. End reason is 'Disarmed' - -Library features a Sans-IO core with both asyncio and blocking wrappers: - -.. code:: python - - from pandablocks.blocking import BlockingClient - from pandablocks.commands import Get - - with BlockingClient("hostname-or-ip") as client: - # Commands sent to Control port - idn = client.send(Get("*IDN")) - print(f"Hello {idn}") - for data in client.data(): - # Data captured from Data port - print(f"I got some PCAP data {data}") - - -.. |code_ci| image:: https://github.com/PandABlocks/PandABlocks-client/actions/workflows/code.yml/badge.svg?branch=master - :target: https://github.com/PandABlocks/PandABlocks-client/actions/workflows/code.yml - :alt: Code CI - -.. |docs_ci| image:: https://github.com/PandABlocks/PandABlocks-client/actions/workflows/docs.yml/badge.svg?branch=master - :target: https://github.com/PandABlocks/PandABlocks-client/actions/workflows/docs.yml - :alt: Docs CI - -.. |coverage| image:: https://codecov.io/gh/PandABlocks/PandABlocks-client/branch/master/graph/badge.svg - :target: https://codecov.io/gh/PandABlocks/PandABlocks-client - :alt: Test Coverage - -.. |pypi_version| image:: https://img.shields.io/pypi/v/pandablocks.svg - :target: https://pypi.org/project/pandablocks - :alt: Latest PyPI version - -.. |license| image:: https://img.shields.io/badge/License-Apache%202.0-blue.svg - :target: https://opensource.org/licenses/Apache-2.0 - :alt: Apache License - - -.. - Anything below this line is used when viewing README.rst and will be replaced - when included in index.rst - -See https://pandablocks.github.io/PandABlocks-client for more detailed documentation. diff --git a/docs/conf.py b/docs/conf.py index 698ab94e..a3b457b5 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -48,8 +48,13 @@ "sphinx_copybutton", # For the card element "sphinx_design", + # So we can write markdown files + "myst_parser", ] +# So we can use the ::: syntax +myst_enable_extensions = ["colon_fence"] + # If true, Sphinx will warn about all references where the target cannot # be found. nitpicky = True @@ -87,9 +92,6 @@ # role, that is, for text marked up `like this` default_role = "any" -# The suffix of source filenames. -source_suffix = ".rst" - # The master toctree document. master_doc = "index" @@ -139,7 +141,7 @@ html_theme = "pydata_sphinx_theme" github_repo = "PandABlocks-client" github_user = "PandABlocks" -switcher_json = "https://PandABlocks.github.io/PandABlocks-client/switcher.json" +switcher_json = f"https://{github_user}.github.io/{github_repo}/switcher.json" switcher_exists = requests.get(switcher_json).ok if not switcher_exists: print( @@ -178,12 +180,6 @@ }, "check_switcher": False, "navbar_end": ["theme-switcher", "icon-links", "version-switcher"], - "external_links": [ - { - "name": "Release Notes", - "url": f"https://github.com/{github_user}/{github_repo}/releases", - } - ], "navigation_with_keys": False, } diff --git a/docs/developer/explanations/decisions.rst b/docs/developer/explanations/decisions.rst deleted file mode 100644 index 5841e6ea..00000000 --- a/docs/developer/explanations/decisions.rst +++ /dev/null @@ -1,17 +0,0 @@ -.. This Source Code Form is subject to the terms of the Mozilla Public -.. License, v. 2.0. If a copy of the MPL was not distributed with this -.. file, You can obtain one at http://mozilla.org/MPL/2.0/. - -Architectural Decision Records -============================== - -We record major architectural decisions in Architecture Decision Records (ADRs), -as `described by Michael Nygard -`_. -Below is the list of our current ADRs. - -.. toctree:: - :maxdepth: 1 - :glob: - - decisions/* \ No newline at end of file diff --git a/docs/developer/explanations/decisions/0001-record-architecture-decisions.rst b/docs/developer/explanations/decisions/0001-record-architecture-decisions.rst deleted file mode 100644 index 0604062c..00000000 --- a/docs/developer/explanations/decisions/0001-record-architecture-decisions.rst +++ /dev/null @@ -1,24 +0,0 @@ -1. Record architecture decisions -================================ - -Status ------- - -Accepted - -Context -------- - -We need to record the architectural decisions made on this project. - -Decision --------- - -We will use Architecture Decision Records, as `described by Michael Nygard -`_. - -Consequences ------------- - -See Michael Nygard's article, linked above. To create new ADRs we will copy and -paste from existing ones. diff --git a/docs/developer/explanations/decisions/0002-switched-to-pip-skeleton.rst b/docs/developer/explanations/decisions/0002-switched-to-pip-skeleton.rst deleted file mode 100644 index 07d02bd4..00000000 --- a/docs/developer/explanations/decisions/0002-switched-to-pip-skeleton.rst +++ /dev/null @@ -1,33 +0,0 @@ -2. Adopt python_copier_template for project structure -===================================================== - -Status ------- - -Accepted - -Context -------- - -We should use the following `python_copier_template `_. -The template will ensure consistency in developer -environments and package management. - -Decision --------- - -We have switched to using the skeleton. - -Consequences ------------- - -This module will use a fixed set of tools as developed in python_copier_template -and can pull from this template to update the packaging to the latest techniques. - -As such, the developer environment may have changed, the following could be -different: - -- linting -- formatting -- pip venv setup -- CI/CD diff --git a/docs/developer/how-to/build-docs.rst b/docs/developer/how-to/build-docs.rst deleted file mode 100644 index 11a5e638..00000000 --- a/docs/developer/how-to/build-docs.rst +++ /dev/null @@ -1,38 +0,0 @@ -Build the docs using sphinx -=========================== - -You can build the `sphinx`_ based docs from the project directory by running:: - - $ tox -e docs - -This will build the static docs on the ``docs`` directory, which includes API -docs that pull in docstrings from the code. - -.. seealso:: - - `documentation_standards` - -The docs will be built into the ``build/html`` directory, and can be opened -locally with a web browser:: - - $ firefox build/html/index.html - -Autobuild ---------- - -You can also run an autobuild process, which will watch your ``docs`` -directory for changes and rebuild whenever it sees changes, reloading any -browsers watching the pages:: - - $ tox -e docs autobuild - -You can view the pages at localhost:: - - $ firefox http://localhost:8000 - -If you are making changes to source code too, you can tell it to watch -changes in this directory too:: - - $ tox -e docs autobuild -- --watch src - -.. _sphinx: https://www.sphinx-doc.org/ diff --git a/docs/developer/how-to/contribute.rst b/docs/developer/how-to/contribute.rst deleted file mode 100644 index 65b992f0..00000000 --- a/docs/developer/how-to/contribute.rst +++ /dev/null @@ -1 +0,0 @@ -.. include:: ../../../.github/CONTRIBUTING.rst diff --git a/docs/developer/how-to/lint.rst b/docs/developer/how-to/lint.rst deleted file mode 100644 index 2df258d8..00000000 --- a/docs/developer/how-to/lint.rst +++ /dev/null @@ -1,39 +0,0 @@ -Run linting using pre-commit -============================ - -Code linting is handled by black_ and ruff_ run under pre-commit_. - -Running pre-commit ------------------- - -You can run the above checks on all files with this command:: - - $ tox -e pre-commit - -Or you can install a pre-commit hook that will run each time you do a ``git -commit`` on just the files that have changed:: - - $ pre-commit install - -It is also possible to `automatically enable pre-commit on cloned repositories `_. -This will result in pre-commits being enabled on every repo your user clones from now on. - -Fixing issues -------------- - -If black reports an issue you can tell it to reformat all the files in the -repository:: - - $ black . - -Likewise with ruff:: - - $ ruff --fix . - -Ruff may not be able to automatically fix all issues; in this case, you will have to fix those manually. - -VSCode support --------------- - -The ``.vscode/settings.json`` will run black formatting as well as -ruff checking on save. Issues will be highlighted in the editor window. diff --git a/docs/developer/how-to/make-release.rst b/docs/developer/how-to/make-release.rst deleted file mode 100644 index df24c340..00000000 --- a/docs/developer/how-to/make-release.rst +++ /dev/null @@ -1,16 +0,0 @@ -Make a release -============== - -To make a new release, please follow this checklist: - -- Choose a new PEP440 compliant release number (see https://peps.python.org/pep-0440/) -- Go to the GitHub release_ page -- Choose ``Draft New Release`` -- Click ``Choose Tag`` and supply the new tag you chose (click create new tag) -- Click ``Generate release notes``, review and edit these notes -- Choose a title and click ``Publish Release`` - -Note that tagging and pushing to the main branch has the same effect except that -you will not get the option to edit the release notes. - -.. _release: https://github.com/PandABlocks/PandABlocks-client/releases diff --git a/docs/developer/how-to/pin-requirements.rst b/docs/developer/how-to/pin-requirements.rst deleted file mode 100644 index 89639623..00000000 --- a/docs/developer/how-to/pin-requirements.rst +++ /dev/null @@ -1,74 +0,0 @@ -Pinning Requirements -==================== - -Introduction ------------- - -By design this project only defines dependencies in one place, i.e. in -the ``requires`` table in ``pyproject.toml``. - -In the ``requires`` table it is possible to pin versions of some dependencies -as needed. For library projects it is best to leave pinning to a minimum so -that your library can be used by the widest range of applications. - -When CI builds the project it will use the latest compatible set of -dependencies available (after applying your pins and any dependencies' pins). - -This approach means that there is a possibility that a future build may -break because an updated release of a dependency has made a breaking change. - -The correct way to fix such an issue is to work out the minimum pinning in -``requires`` that will resolve the problem. However this can be quite hard to -do and may be time consuming when simply trying to release a minor update. - -For this reason we provide a mechanism for locking all dependencies to -the same version as a previous successful release. This is a quick fix that -should guarantee a successful CI build. - -Finding the lock files ----------------------- - -Every release of the project will have a set of requirements files published -as release assets. - -For example take a look at the release page for python3-pip-skeleton-cli here: -https://github.com/DiamondLightSource/python3-pip-skeleton-cli/releases/tag/3.3.0 - -There is a list of requirements*.txt files showing as assets on the release. - -There is one file for each time the CI installed the project into a virtual -environment. There are multiple of these as the CI creates a number of -different environments. - -The files are created using ``pip freeze`` and will contain a full list -of the dependencies and sub-dependencies with pinned versions. - -You can download any of these files by clicking on them. It is best to use -the one that ran with the lowest Python version as this is more likely to -be compatible with all the versions of Python in the test matrix. -i.e. ``requirements-test-ubuntu-latest-3.8.txt`` in this example. - -Applying the lock file ----------------------- - -To apply a lockfile: - -- copy the requirements file you have downloaded to the root of your - repository -- rename it to requirements.txt -- commit it into the repo -- push the changes - -The CI looks for a requirements.txt in the root and will pass it to pip -when installing each of the test environments. pip will then install exactly -the same set of packages as the previous release. - -Removing dependency locking from CI ------------------------------------ - -Once the reasons for locking the build have been resolved it is a good idea -to go back to an unlocked build. This is because you get an early indication -of any incoming problems. - -To restore unlocked builds in CI simply remove requirements.txt from the root -of the repo and push. diff --git a/docs/developer/how-to/run-tests.rst b/docs/developer/how-to/run-tests.rst deleted file mode 100644 index d2e03644..00000000 --- a/docs/developer/how-to/run-tests.rst +++ /dev/null @@ -1,12 +0,0 @@ -Run the tests using pytest -========================== - -Testing is done with pytest_. It will find functions in the project that `look -like tests`_, and run them to check for errors. You can run it with:: - - $ tox -e pytest - -It will also report coverage to the commandline and to ``cov.xml``. - -.. _pytest: https://pytest.org/ -.. _look like tests: https://docs.pytest.org/explanation/goodpractices.html#test-discovery diff --git a/docs/developer/how-to/static-analysis.rst b/docs/developer/how-to/static-analysis.rst deleted file mode 100644 index 065920e1..00000000 --- a/docs/developer/how-to/static-analysis.rst +++ /dev/null @@ -1,8 +0,0 @@ -Run static analysis using mypy -============================== - -Static type analysis is done with mypy_. It checks type definition in source -files without running them, and highlights potential issues where types do not -match. You can run it with:: - - $ tox -e mypy diff --git a/docs/developer/how-to/test-container.rst b/docs/developer/how-to/test-container.rst deleted file mode 100644 index a4a43a6f..00000000 --- a/docs/developer/how-to/test-container.rst +++ /dev/null @@ -1,25 +0,0 @@ -Container Local Build and Test -============================== - -CI builds a runtime container for the project. The local tests -checks available via ``tox -p`` do not verify this because not -all developers will have docker installed locally. - -If CI is failing to build the container, then it is best to fix and -test the problem locally. This would require that you have docker -or podman installed on your local workstation. - -In the following examples the command ``docker`` is interchangeable with -``podman`` depending on which container cli you have installed. - -To build the container and call it ``test``:: - - cd - docker build -t test . - -To verify that the container runs:: - - docker run -it test --help - -You can pass any other command line parameters to your application -instead of --help. diff --git a/docs/developer/how-to/update-tools.rst b/docs/developer/how-to/update-tools.rst deleted file mode 100644 index c1075ee8..00000000 --- a/docs/developer/how-to/update-tools.rst +++ /dev/null @@ -1,16 +0,0 @@ -Update the tools -================ - -This module is merged with the python3-pip-skeleton_. This is a generic -Python project structure which provides a means to keep tools and -techniques in sync between multiple Python projects. To update to the -latest version of the skeleton, run:: - - $ git pull --rebase=false https://github.com/DiamondLightSource/python3-pip-skeleton - -Any merge conflicts will indicate an area where something has changed that -conflicts with the setup of the current module. Check the `closed pull requests -`_ -of the skeleton module for more details. - -.. _python3-pip-skeleton: https://DiamondLightSource.github.io/python3-pip-skeleton diff --git a/docs/developer/index.rst b/docs/developer/index.rst deleted file mode 100644 index 8a6369b9..00000000 --- a/docs/developer/index.rst +++ /dev/null @@ -1,64 +0,0 @@ -Developer Guide -=============== - -Documentation is split into four categories, also accessible from links in the -side-bar. - -.. grid:: 2 - :gutter: 4 - - .. grid-item-card:: :material-regular:`directions_run;3em` - - .. toctree:: - :caption: Tutorials - :maxdepth: 1 - - tutorials/dev-install - - +++ - - Tutorials for getting up and running as a developer. - - .. grid-item-card:: :material-regular:`task;3em` - - .. toctree:: - :caption: How-to Guides - :maxdepth: 1 - - how-to/contribute - how-to/build-docs - how-to/run-tests - how-to/static-analysis - how-to/lint - how-to/update-tools - how-to/make-release - how-to/pin-requirements - how-to/test-container - - +++ - - Practical step-by-step guides for day-to-day dev tasks. - - .. grid-item-card:: :material-regular:`apartment;3em` - - .. toctree:: - :caption: Explanations - :maxdepth: 1 - - explanations/decisions - - +++ - - Explanations of how and why the architecture is why it is. - - .. grid-item-card:: :material-regular:`description;3em` - - .. toctree:: - :caption: Reference - :maxdepth: 1 - - reference/standards - - +++ - - Technical reference material on standards in use. diff --git a/docs/developer/reference/standards.rst b/docs/developer/reference/standards.rst deleted file mode 100644 index 5a1fd478..00000000 --- a/docs/developer/reference/standards.rst +++ /dev/null @@ -1,63 +0,0 @@ -Standards -========= - -This document defines the code and documentation standards used in this -repository. - -Code Standards --------------- - -The code in this repository conforms to standards set by the following tools: - -- black_ for code formatting -- ruff_ for style checks -- mypy_ for static type checking - -.. seealso:: - - How-to guides `../how-to/lint` and `../how-to/static-analysis` - -.. _documentation_standards: - -Documentation Standards ------------------------ - -Docstrings are pre-processed using the Sphinx Napoleon extension. As such, -google-style_ is considered as standard for this repository. Please use type -hints in the function signature for types. For example: - -.. code:: python - - def func(arg1: str, arg2: int) -> bool: - """Summary line. - - Extended description of function. - - Args: - arg1: Description of arg1 - arg2: Description of arg2 - - Returns: - Description of return value - """ - return True - -.. _google-style: https://sphinxcontrib-napoleon.readthedocs.io/en/latest/index.html#google-vs-numpy - -Documentation is contained in the ``docs`` directory and extracted from -docstrings of the API. - -Docs follow the underlining convention:: - - Headling 1 (page title) - ======================= - - Heading 2 - --------- - - Heading 3 - ~~~~~~~~~ - -.. seealso:: - - How-to guide `../how-to/build-docs` diff --git a/docs/developer/tutorials/dev-install.rst b/docs/developer/tutorials/dev-install.rst deleted file mode 100644 index 49ecac74..00000000 --- a/docs/developer/tutorials/dev-install.rst +++ /dev/null @@ -1,68 +0,0 @@ -Developer install -================= - -These instructions will take you through the minimal steps required to get a dev -environment setup, so you can run the tests locally. - -Clone the repository --------------------- - -First clone the repository locally using `Git -`_:: - - $ git clone git://github.com/PandABlocks/PandABlocks-client.git - -Install dependencies --------------------- - -You can choose to either develop on the host machine using a `venv` (which -requires python 3.8 or later) or to run in a container under `VSCode -`_ - -.. tab-set:: - - .. tab-item:: Local virtualenv - - .. code:: - - $ cd pandablocks - $ python3 -m venv venv - $ source venv/bin/activate - $ pip install -e '.[dev]' - - .. tab-item:: VSCode devcontainer - - .. code:: - - $ code pandablocks - # Click on 'Reopen in Container' when prompted - # Open a new terminal - - .. note:: - - See the epics-containers_ documentation for more complex - use cases, such as integration with podman. - -See what was installed ----------------------- - -To see a graph of the python package dependency tree type:: - - $ pipdeptree - -Build and test --------------- - -Now you have a development environment you can run the tests in a terminal:: - - $ tox -p - -This will run in parallel the following checks: - -- `../how-to/build-docs` -- `../how-to/run-tests` -- `../how-to/static-analysis` -- `../how-to/lint` - - -.. _epics-containers: https://epics-containers.github.io/main/user/tutorials/devcontainer.html diff --git a/docs/explanations.md b/docs/explanations.md new file mode 100644 index 00000000..73ab289b --- /dev/null +++ b/docs/explanations.md @@ -0,0 +1,10 @@ +# Explanations + +Explanations of how it works and why it works that way. + +```{toctree} +:maxdepth: 1 +:glob: + +explanations/* +``` diff --git a/docs/explanations/decisions.md b/docs/explanations/decisions.md new file mode 100644 index 00000000..0533b98d --- /dev/null +++ b/docs/explanations/decisions.md @@ -0,0 +1,12 @@ +# Architectural Decision Records + +Architectural decisions are made throughout a project's lifetime. As a way of keeping track of these decisions, we record these decisions in Architecture Decision Records (ADRs) listed below. + +```{toctree} +:glob: true +:maxdepth: 1 + +decisions/* +``` + +For more information on ADRs see this [blog by Michael Nygard](http://thinkrelevance.com/blog/2011/11/15/documenting-architecture-decisions). diff --git a/docs/explanations/decisions/0001-record-architecture-decisions.md b/docs/explanations/decisions/0001-record-architecture-decisions.md new file mode 100644 index 00000000..44d234ef --- /dev/null +++ b/docs/explanations/decisions/0001-record-architecture-decisions.md @@ -0,0 +1,18 @@ +# 1. Record architecture decisions + +## Status + +Accepted + +## Context + +We need to record the architectural decisions made on this project. + +## Decision + +We will use Architecture Decision Records, as [described by Michael Nygard](http://thinkrelevance.com/blog/2011/11/15/documenting-architecture-decisions). + +## Consequences + +See Michael Nygard's article, linked above. To create new ADRs we will copy and +paste from existing ones. diff --git a/docs/explanations/decisions/0002-switched-to-python-copier-template.md b/docs/explanations/decisions/0002-switched-to-python-copier-template.md new file mode 100644 index 00000000..66fe5d8b --- /dev/null +++ b/docs/explanations/decisions/0002-switched-to-python-copier-template.md @@ -0,0 +1,28 @@ +# 2. Adopt python-copier-template for project structure + +## Status + +Accepted + +## Context + +We should use the following [python-copier-template](https://github.com/DiamondLightSource/python-copier-template). +The template will ensure consistency in developer +environments and package management. + +## Decision + +We have switched to using the template. + +## Consequences + +This module will use a fixed set of tools as developed in `python-copier-template` +and can pull from this template to update the packaging to the latest techniques. + +As such, the developer environment may have changed, the following could be +different: + +- linting +- formatting +- pip venv setup +- CI/CD diff --git a/docs/explanations/decisions/COPYME b/docs/explanations/decisions/COPYME new file mode 100644 index 00000000..b466c792 --- /dev/null +++ b/docs/explanations/decisions/COPYME @@ -0,0 +1,19 @@ +# 3. Short descriptive title + +Date: Today's date + +## Status + +Accepted + +## Context + +Background to allow us to make the decision, to show how we arrived at our conclusions. + +## Decision + +What decision we made. + +## Consequences + +What we will do as a result of this decision. diff --git a/docs/genindex.md b/docs/genindex.md new file mode 100644 index 00000000..73f1191b --- /dev/null +++ b/docs/genindex.md @@ -0,0 +1,3 @@ +# Index + + diff --git a/docs/genindex.rst b/docs/genindex.rst deleted file mode 100644 index 93eb8b29..00000000 --- a/docs/genindex.rst +++ /dev/null @@ -1,5 +0,0 @@ -API Index -========= - -.. - https://stackoverflow.com/a/42310803 diff --git a/docs/how-to.md b/docs/how-to.md new file mode 100644 index 00000000..6b161417 --- /dev/null +++ b/docs/how-to.md @@ -0,0 +1,10 @@ +# How-to Guides + +Practical step-by-step guides for the more experienced user. + +```{toctree} +:maxdepth: 1 +:glob: + +how-to/* +``` diff --git a/docs/how-to/contribute.md b/docs/how-to/contribute.md new file mode 100644 index 00000000..f9c4ca1d --- /dev/null +++ b/docs/how-to/contribute.md @@ -0,0 +1,2 @@ +```{include} ../../.github/CONTRIBUTING.md +``` \ No newline at end of file diff --git a/docs/how-to/run-container.md b/docs/how-to/run-container.md new file mode 100644 index 00000000..12de9a6a --- /dev/null +++ b/docs/how-to/run-container.md @@ -0,0 +1,14 @@ +# Run in a container + +Pre-built containers with PandABlocks-client and its dependencies already +installed are available on [Github Container Registry](https://ghcr.io/PandABlocks/PandABlocks-client). + +## Starting the container + +To pull the container from github container registry and run: + +``` +$ docker run ghcr.io/PandABlocks/PandABlocks-client:main --version +``` + +To get a released version, use a numbered release instead of `main`. diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 00000000..730b3fdc --- /dev/null +++ b/docs/index.md @@ -0,0 +1,56 @@ +--- +html_theme.sidebar_secondary.remove: true +--- + +```{include} ../README.md +:end-before: + +::::{grid} 2 +:gutter: 4 + +:::{grid-item-card} {material-regular}`directions_walk;2em` +```{toctree} +:maxdepth: 2 +tutorials +``` ++++ +Tutorials for installation and typical usage. New users start here. +::: + +:::{grid-item-card} {material-regular}`directions;2em` +```{toctree} +:maxdepth: 2 +how-to +``` ++++ +Practical step-by-step guides for the more experienced user. +::: + +:::{grid-item-card} {material-regular}`info;2em` +```{toctree} +:maxdepth: 2 +explanations +``` ++++ +Explanations of how it works and why it works that way. +::: + +:::{grid-item-card} {material-regular}`menu_book;2em` +```{toctree} +:maxdepth: 2 +reference +``` ++++ +Technical reference material including APIs and release notes. +::: + +:::: diff --git a/docs/index.rst b/docs/index.rst deleted file mode 100644 index 0023bc4d..00000000 --- a/docs/index.rst +++ /dev/null @@ -1,29 +0,0 @@ -:html_theme.sidebar_secondary.remove: - -.. include:: ../README.rst - :end-before: when included in index.rst - -How the documentation is structured ------------------------------------ - -The documentation is split into 2 sections: - -.. grid:: 2 - - .. grid-item-card:: :material-regular:`person;4em` - :link: user/index - :link-type: doc - - The User Guide contains documentation on how to install and use pandablocks. - - .. grid-item-card:: :material-regular:`code;4em` - :link: developer/index - :link-type: doc - - The Developer Guide contains documentation on how to develop and contribute changes back to pandablocks. - -.. toctree:: - :hidden: - - user/index - developer/index diff --git a/docs/reference.md b/docs/reference.md new file mode 100644 index 00000000..ac401669 --- /dev/null +++ b/docs/reference.md @@ -0,0 +1,12 @@ +# Reference + +Technical reference material including APIs and release notes. + +```{toctree} +:maxdepth: 1 +:glob: + +reference/* +genindex +Release Notes +``` diff --git a/docs/reference/api.md b/docs/reference/api.md new file mode 100644 index 00000000..d9260995 --- /dev/null +++ b/docs/reference/api.md @@ -0,0 +1,17 @@ +# API + +```{eval-rst} +.. automodule:: pandablocks + + ``pandablocks`` + ----------------------------------- +``` + +This is the internal API reference for {\{package_name}} + +```{eval-rst} +.. data:: pandablocks.__version__ + :type: str + + Version number as calculated by https://github.com/pypa/setuptools_scm +``` diff --git a/docs/tutorials.md b/docs/tutorials.md new file mode 100644 index 00000000..1fe66c54 --- /dev/null +++ b/docs/tutorials.md @@ -0,0 +1,10 @@ +# Tutorials + +Tutorials for installation and typical usage. New users start here. + +```{toctree} +:maxdepth: 1 +:glob: + +tutorials/* +``` diff --git a/docs/tutorials/installation.md b/docs/tutorials/installation.md new file mode 100644 index 00000000..26bf5052 --- /dev/null +++ b/docs/tutorials/installation.md @@ -0,0 +1,48 @@ +# Installation + +## Check your version of python + +You will need python 3.7 or later. You can check your version of python by +typing into a terminal: + +``` +$ python3 --version +``` + +## Create a virtual environment + +It is recommended that you install into a “virtual environment” so this +installation will not interfere with any existing Python software: + +``` +$ python3 -m venv /path/to/venv +$ source /path/to/venv/bin/activate +``` + +## Installing the library + +You can now use `pip` to install the library and its dependencies: + +``` +$ python3 -m pip install pandablocks +``` + +If you need to write HDF files you should install the hdf5 extra: + +``` +$ python3 -m pip install pandablocks[hdf5] +``` + +If you require a feature that is not currently released you can also install +from github: + +``` +$ python3 -m pip install git+https://github.com/PandABlocks/PandABlocks-client.git +``` + +The library should now be installed and the commandline interface on your path. +You can check the version that has been installed by typing: + +``` +$ PandABlocks-client --version +``` diff --git a/docs/user/explanations/docs-structure.rst b/docs/user/explanations/docs-structure.rst deleted file mode 100644 index f25a09ba..00000000 --- a/docs/user/explanations/docs-structure.rst +++ /dev/null @@ -1,18 +0,0 @@ -About the documentation ------------------------ - - :material-regular:`format_quote;2em` - - The Grand Unified Theory of Documentation - - -- David Laing - -There is a secret that needs to be understood in order to write good software -documentation: there isn't one thing called *documentation*, there are four. - -They are: *tutorials*, *how-to guides*, *technical reference* and *explanation*. -They represent four different purposes or functions, and require four different -approaches to their creation. Understanding the implications of this will help -improve most documentation - often immensely. - -`More information on this topic. `_ diff --git a/docs/user/how-to/run-container.rst b/docs/user/how-to/run-container.rst deleted file mode 100644 index 7285ef9b..00000000 --- a/docs/user/how-to/run-container.rst +++ /dev/null @@ -1,15 +0,0 @@ -Run in a container -================== - -Pre-built containers with pandablocks and its dependencies already -installed are available on `Github Container Registry -`_. - -Starting the container ----------------------- - -To pull the container from github container registry and run:: - - $ docker run ghcr.io/PandABlocks/PandABlocks-client:main --version - -To get a released version, use a numbered release instead of ``main``. diff --git a/docs/user/index.rst b/docs/user/index.rst deleted file mode 100644 index c315ff2d..00000000 --- a/docs/user/index.rst +++ /dev/null @@ -1,67 +0,0 @@ -User Guide -========== - -Documentation is split into four categories, also accessible from links in the -side-bar. - -.. grid:: 2 - :gutter: 4 - - .. grid-item-card:: :material-regular:`directions_walk;3em` - - .. toctree:: - :caption: Tutorials - :maxdepth: 1 - - tutorials/installation - tutorials/commandline-hdf - tutorials/control - tutorials/load-save - - +++ - - Tutorials for installation and typical usage. New users start here. - - .. grid-item-card:: :material-regular:`directions;3em` - - .. toctree:: - :caption: How-to Guides - :maxdepth: 1 - - how-to/run-container - how-to/introspect-panda - how-to/library-hdf - how-to/poll-changes - - +++ - - Practical step-by-step guides for the more experienced user. - - .. grid-item-card:: :material-regular:`info;3em` - - .. toctree:: - :caption: Explanations - :maxdepth: 1 - - explanations/docs-structure - explanations/performance - explanations/sans-io - - +++ - - Explanations of how the library works and why it works that way. - - .. grid-item-card:: :material-regular:`menu_book;3em` - - .. toctree:: - :caption: Reference - :maxdepth: 1 - - reference/api - reference/changelog - reference/contributing - ../genindex - - +++ - - Technical reference material including APIs and release notes. diff --git a/docs/user/reference/api.rst b/docs/user/reference/api.rst deleted file mode 100644 index b9071f80..00000000 --- a/docs/user/reference/api.rst +++ /dev/null @@ -1,92 +0,0 @@ -.. _API: - -API -=== - -The top level pandablocks module contains a number of packages that can be used -from code: - -- `pandablocks.commands`: The control commands that can be sent to a PandA -- `pandablocks.responses`: The control and data responses that will be received -- `pandablocks.connections`: Control and data connections that implements the parsing logic -- `pandablocks.asyncio`: An asyncio client that uses the control and data connections -- `pandablocks.blocking`: A blocking client that uses the control and data connections -- `pandablocks.hdf`: Some helpers for efficiently writing data responses to disk -- `pandablocks.utils`: General utility methods for use with pandablocks - - -.. automodule:: pandablocks.commands - :members: - - Commands - -------- - - There is a `Command` subclass for every sort of command that can be sent to - the `ControlConnection` of a PandA. Many common actions can be accomplished - with a simple `Get` or `Put`, but some convenience commands like - `GetBlockInfo`, `GetFieldInfo`, etc. are provided that parse output into - specific classes. - - -.. automodule:: pandablocks.responses - :members: - - Responses - --------- - - Classes used in responses from both the `ControlConnection` and - `DataConnection` of a PandA live in this package. - -.. automodule:: pandablocks.connections - :members: - - Connections - ----------- - - `Sans-IO ` connections for both the Control and Data ports - of PandA TCP server. - -.. automodule:: pandablocks.asyncio - :members: - - Asyncio Client - -------------- - - This is an `asyncio` wrapper to the `ControlConnection` and `DataConnection` - objects, allowing async calls to ``send(command)`` and iterate over - ``data()``. - -.. automodule:: pandablocks.blocking - :members: - - Blocking Client - --------------- - - This is a blocking wrapper to the `ControlConnection` and `DataConnection` - objects, allowing blocking calls to ``send(commands)`` and iterate over - ``data()``. - -.. automodule:: pandablocks.hdf - :members: - - HDF Writing - ----------- - - This package contains components needed to write PCAP data to and HDF file - in the most efficient way. The oneshot `write_hdf_files` is exposed in the - commandline interface. It assembles a short `Pipeline` of: - - `AsyncioClient` -> `FrameProcessor` -> `HDFWriter` - - The FrameProcessor and HDFWriter run in their own threads as most of the - heavy lifting is done by numpy_ and h5py_, so running in their own threads - gives multi-CPU benefits without hitting the limit of the GIL. - - .. seealso:: `library-hdf`, `performance` - -.. automodule:: pandablocks.utils - - Utilities - --------- - - This package contains general methods for working with pandablocks. diff --git a/docs/user/tutorials/installation.rst b/docs/user/tutorials/installation.rst deleted file mode 100644 index 41131e63..00000000 --- a/docs/user/tutorials/installation.rst +++ /dev/null @@ -1,42 +0,0 @@ -Installation -============ - -Check your version of python ----------------------------- - -You will need python 3.7 or later. You can check your version of python by -typing into a terminal:: - - $ python3 --version - - -Create a virtual environment ----------------------------- - -It is recommended that you install into a “virtual environment” so this -installation will not interfere with any existing Python software:: - - $ python3 -m venv /path/to/venv - $ source /path/to/venv/bin/activate - - -Installing the library ----------------------- - -You can now use ``pip`` to install the library:: - - python3 -m pip install pandablocks - -If you need to write HDF files you should install the ``hdf5`` extra:: - - python3 -m pip install pandablocks[hdf5] - -If you require a feature that is not currently released you can also install -from github:: - - python3 -m pip install git+git://github.com/PandABlocks/PandABlocks-client.git - -The library should now be installed and the commandline interface on your path. -You can check the version that has been installed by typing:: - - pandablocks --version diff --git a/pyproject.toml b/pyproject.toml index 00da045e..b53c310b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [build-system] -requires = ["setuptools>=64", "setuptools_scm[toml]>=6.2", "wheel"] +requires = ["setuptools>=64", "setuptools_scm[toml]>=6.2"] build-backend = "setuptools.build_meta" [project] @@ -17,7 +17,7 @@ description = "A Python client to control and data ports of the PandABlocks TCP dependencies = ["typing-extensions;python_version<'3.8'", "numpy", "click"] dynamic = ["version"] license.file = "LICENSE" -readme = "README.rst" +readme = "README.md" requires-python = ">=3.7" [project.optional-dependencies] @@ -31,9 +31,12 @@ dev = [ "types-mock", "atomicwrites", "typed-ast", + "copier", + "myst-parser", "pipdeptree", "pre-commit", "pydata-sphinx-theme>=0.12", + "pyright", "pytest", "pytest-cov", "pytest-asyncio", @@ -85,7 +88,7 @@ legacy_tox_ini = """ [tox] skipsdist=True -[testenv:{pre-commit,mypy,pytest,docs}] +[testenv:{pre-commit,type-checking,tests,docs}] # Don't create a virtualenv for the command, requires tox-direct plugin direct = True passenv = * @@ -99,17 +102,20 @@ commands = pytest: pytest --cov=src/pandablocks --cov-report term --cov-report xml:cov.xml {posargs} mypy: mypy src tests {posargs} pre-commit: pre-commit run --all-files {posargs} + type-checking: mypy src tests {posargs} + tests: pytest --cov=pandablocks --cov-report term --cov-report xml:cov.xml {posargs} docs: sphinx-{posargs:build -EW --keep-going} -T docs build/html """ - [tool.ruff] src = ["src", "tests"] line-length = 88 -select = [ - "C4", # flake8-comprehensions - https://beta.ruff.rs/docs/rules/#flake8-comprehensions-c4 - "E", # pycodestyle errors - https://beta.ruff.rs/docs/rules/#error-e - "F", # pyflakes rules - https://beta.ruff.rs/docs/rules/#pyflakes-f - "W", # pycodestyle warnings - https://beta.ruff.rs/docs/rules/#warning-w - "I001", # isort +lint.select = [ + "B", # flake8-bugbear - https://docs.astral.sh/ruff/rules/#flake8-bugbear-b + "C4", # flake8-comprehensions - https://docs.astral.sh/ruff/rules/#flake8-comprehensions-c4 + "E", # pycodestyle errors - https://docs.astral.sh/ruff/rules/#error-e + "F", # pyflakes rules - https://docs.astral.sh/ruff/rules/#pyflakes-f + "W", # pycodestyle warnings - https://docs.astral.sh/ruff/rules/#warning-w + "I", # isort - https://docs.astral.sh/ruff/rules/#isort-i + "UP", # pyupgrade - https://docs.astral.sh/ruff/rules/#pyupgrade-up ] diff --git a/src/pandablocks/__init__.py b/src/pandablocks/__init__.py index 457ddb1f..26d23bad 100644 --- a/src/pandablocks/__init__.py +++ b/src/pandablocks/__init__.py @@ -1,11 +1,3 @@ -import sys - -if sys.version_info < (3, 8): - from importlib_metadata import version # noqa -else: - from importlib.metadata import version # noqa - -__version__ = version("pandablocks") -del version +from ._version import __version__ __all__ = ["__version__"] From 6274cf574b5dbc4a750c17333e514e29b0c0e2ff Mon Sep 17 00:00:00 2001 From: Man Ting Chan Date: Wed, 28 Feb 2024 17:24:56 +0000 Subject: [PATCH 03/10] removed pyright --- pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index b53c310b..825fa12a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,7 +36,6 @@ dev = [ "pipdeptree", "pre-commit", "pydata-sphinx-theme>=0.12", - "pyright", "pytest", "pytest-cov", "pytest-asyncio", From c345d6c69e9a7a199fc12875577849ec61db2d6d Mon Sep 17 00:00:00 2001 From: Man Ting Chan Date: Wed, 28 Feb 2024 17:25:37 +0000 Subject: [PATCH 04/10] removed black --- pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 825fa12a..95c909f1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,7 +25,6 @@ h5py = ["h5py", "matplotlib"] dev = [ # A dev install will require [h5py] packages too "pandablocks[h5py]", - "black", "mypy", "mock", "types-mock", From 0bd430d0444b4322d52778ad4a811e2549a3683c Mon Sep 17 00:00:00 2001 From: Man Ting Chan Date: Fri, 1 Mar 2024 11:28:26 +0000 Subject: [PATCH 05/10] move user/docs to docs --- docs/{user => }/explanations/performance.rst | 0 docs/{user => }/explanations/sans-io.rst | 0 docs/{user => }/how-to/introspect-panda.rst | 0 docs/{user => }/how-to/library-hdf.rst | 0 docs/{user => }/how-to/poll-changes.rst | 0 docs/{user => }/reference/appendix.rst | 0 docs/{user => }/reference/changelog.rst | 0 docs/{user => }/reference/contributing.rst | 0 docs/{user => }/tutorials/commandline-hdf.rst | 0 docs/{user => }/tutorials/control.rst | 0 docs/{user => }/tutorials/load-save.rst | 0 docs/{user => }/tutorials/tutorial_layout.png | Bin 12 files changed, 0 insertions(+), 0 deletions(-) rename docs/{user => }/explanations/performance.rst (100%) rename docs/{user => }/explanations/sans-io.rst (100%) rename docs/{user => }/how-to/introspect-panda.rst (100%) rename docs/{user => }/how-to/library-hdf.rst (100%) rename docs/{user => }/how-to/poll-changes.rst (100%) rename docs/{user => }/reference/appendix.rst (100%) rename docs/{user => }/reference/changelog.rst (100%) rename docs/{user => }/reference/contributing.rst (100%) rename docs/{user => }/tutorials/commandline-hdf.rst (100%) rename docs/{user => }/tutorials/control.rst (100%) rename docs/{user => }/tutorials/load-save.rst (100%) rename docs/{user => }/tutorials/tutorial_layout.png (100%) diff --git a/docs/user/explanations/performance.rst b/docs/explanations/performance.rst similarity index 100% rename from docs/user/explanations/performance.rst rename to docs/explanations/performance.rst diff --git a/docs/user/explanations/sans-io.rst b/docs/explanations/sans-io.rst similarity index 100% rename from docs/user/explanations/sans-io.rst rename to docs/explanations/sans-io.rst diff --git a/docs/user/how-to/introspect-panda.rst b/docs/how-to/introspect-panda.rst similarity index 100% rename from docs/user/how-to/introspect-panda.rst rename to docs/how-to/introspect-panda.rst diff --git a/docs/user/how-to/library-hdf.rst b/docs/how-to/library-hdf.rst similarity index 100% rename from docs/user/how-to/library-hdf.rst rename to docs/how-to/library-hdf.rst diff --git a/docs/user/how-to/poll-changes.rst b/docs/how-to/poll-changes.rst similarity index 100% rename from docs/user/how-to/poll-changes.rst rename to docs/how-to/poll-changes.rst diff --git a/docs/user/reference/appendix.rst b/docs/reference/appendix.rst similarity index 100% rename from docs/user/reference/appendix.rst rename to docs/reference/appendix.rst diff --git a/docs/user/reference/changelog.rst b/docs/reference/changelog.rst similarity index 100% rename from docs/user/reference/changelog.rst rename to docs/reference/changelog.rst diff --git a/docs/user/reference/contributing.rst b/docs/reference/contributing.rst similarity index 100% rename from docs/user/reference/contributing.rst rename to docs/reference/contributing.rst diff --git a/docs/user/tutorials/commandline-hdf.rst b/docs/tutorials/commandline-hdf.rst similarity index 100% rename from docs/user/tutorials/commandline-hdf.rst rename to docs/tutorials/commandline-hdf.rst diff --git a/docs/user/tutorials/control.rst b/docs/tutorials/control.rst similarity index 100% rename from docs/user/tutorials/control.rst rename to docs/tutorials/control.rst diff --git a/docs/user/tutorials/load-save.rst b/docs/tutorials/load-save.rst similarity index 100% rename from docs/user/tutorials/load-save.rst rename to docs/tutorials/load-save.rst diff --git a/docs/user/tutorials/tutorial_layout.png b/docs/tutorials/tutorial_layout.png similarity index 100% rename from docs/user/tutorials/tutorial_layout.png rename to docs/tutorials/tutorial_layout.png From 7cf64020318459feb284ed45e82719ef56cdce88 Mon Sep 17 00:00:00 2001 From: Eva Lott Date: Mon, 11 Mar 2024 11:40:42 +0000 Subject: [PATCH 06/10] updated to copier template 2.0.1 --- .copier-answers.yml | 3 ++- .github/CONTRIBUTING.md | 12 +----------- .github/workflows/_pypi.yml | 5 ----- .github/workflows/ci.yml | 2 -- .vscode/launch.json | 11 ++++------- README.md | 1 + docs/how-to/run-container.md | 4 ++-- docs/reference/api.md | 2 +- 8 files changed, 11 insertions(+), 29 deletions(-) diff --git a/.copier-answers.yml b/.copier-answers.yml index ec817bcb..107af3b9 100644 --- a/.copier-answers.yml +++ b/.copier-answers.yml @@ -1,5 +1,5 @@ # Changes here will be overwritten by Copier -_commit: 1.3.0 +_commit: 2.0.1 _src_path: gh:DiamondLightSource/python-copier-template author_email: tom.cobb@diamond.ac.uk author_name: Tom Cobb @@ -11,5 +11,6 @@ docs_type: sphinx git_platform: github.com github_org: PandABlocks package_name: pandablocks +pypi: true repo_name: PandABlocks-client type_checker: mypy diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 08119298..2ec8dfe6 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -24,14 +24,4 @@ It is recommended that developers use a [vscode devcontainer](https://code.visua This project was created using the [Diamond Light Source Copier Template](https://github.com/DiamondLightSource/python-copier-template) for Python projects. -For more information on common tasks like setting up a developer environment, running the tests, and setting a pre-commit hook, see the template's [How-to guides](https://diamondlightsource.github.io/python-copier-template/1.3.0/how-to.html). - -## Release Checklist - -Before a new release, please go through the following checklist: - -- Choose a new PEP440 compliant release number -- Add a release note in CHANGELOG.rst -- Git tag the version with message from CHANGELOG -- 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 +For more information on common tasks like setting up a developer environment, running the tests, and setting a pre-commit hook, see the template's [How-to guides](https://diamondlightsource.github.io/python-copier-template/2.0.1/how-to.html). diff --git a/.github/workflows/_pypi.yml b/.github/workflows/_pypi.yml index f2ead1bc..0c5258db 100644 --- a/.github/workflows/_pypi.yml +++ b/.github/workflows/_pypi.yml @@ -1,8 +1,5 @@ on: workflow_call: - secrets: - PYPI_TOKEN: - required: true jobs: upload: @@ -18,5 +15,3 @@ jobs: - name: Publish to PyPI using trusted publishing uses: pypa/gh-action-pypi-publish@release/v1 - with: - password: ${{ secrets.PYPI_TOKEN }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7e17ec93..888eb4d8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -57,8 +57,6 @@ jobs: uses: ./.github/workflows/_pypi.yml permissions: id-token: write - secrets: - PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }} release: if: github.ref_type == 'tag' diff --git a/.vscode/launch.json b/.vscode/launch.json index 3c8a2b5b..15b70fa1 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -17,7 +17,7 @@ }, { "name": "Debug Unit Test", - "type": "python", + "type": "debugpy", "request": "launch", "program": "${file}", "purpose": [ @@ -25,12 +25,9 @@ ], "console": "integratedTerminal", "env": { - // Cannot have coverage and debugging at the same time, - // and the default config in setup.cfg adds coverage - // https://github.com/microsoft/vscode-python/issues/693 - "PYTEST_ADDOPTS": "--no-cov" + // Enable break on exception when debugging tests (see: tests/conftest.py) + "PYTEST_RAISE": "1", }, - "justMyCode": false } ] -} +} \ No newline at end of file diff --git a/README.md b/README.md index 7efd8a43..8ee45994 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ A Python client to control and data ports of the PandABlocks TCP server Source | :---: | :---: PyPI | `pip install pandablocks` +Docker | `docker run ghcr.io/pandablocks/PandABlocks-client:latest` Documentation | Releases | diff --git a/docs/how-to/run-container.md b/docs/how-to/run-container.md index 12de9a6a..5649331d 100644 --- a/docs/how-to/run-container.md +++ b/docs/how-to/run-container.md @@ -8,7 +8,7 @@ installed are available on [Github Container Registry](https://ghcr.io/PandABloc To pull the container from github container registry and run: ``` -$ docker run ghcr.io/PandABlocks/PandABlocks-client:main --version +$ docker run ghcr.io/pandablocks/PandABlocks-client:latest --version ``` -To get a released version, use a numbered release instead of `main`. +To get a released version, use a numbered release instead of `latest`. diff --git a/docs/reference/api.md b/docs/reference/api.md index d9260995..687260d8 100644 --- a/docs/reference/api.md +++ b/docs/reference/api.md @@ -7,7 +7,7 @@ ----------------------------------- ``` -This is the internal API reference for {\{package_name}} +This is the internal API reference for pandablocks ```{eval-rst} .. data:: pandablocks.__version__ From b84ed1c94f39d7e3c44c28fc0eb1f9343483b50f Mon Sep 17 00:00:00 2001 From: Eva Lott Date: Tue, 12 Mar 2024 10:23:09 +0000 Subject: [PATCH 07/10] made pre-commit show diff on failure --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 95c909f1..73539a51 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -99,7 +99,7 @@ allowlist_externals = commands = pytest: pytest --cov=src/pandablocks --cov-report term --cov-report xml:cov.xml {posargs} mypy: mypy src tests {posargs} - pre-commit: pre-commit run --all-files {posargs} + pre-commit: pre-commit run --all-files --show-diff-on-failure {posargs} type-checking: mypy src tests {posargs} tests: pytest --cov=pandablocks --cov-report term --cov-report xml:cov.xml {posargs} docs: sphinx-{posargs:build -EW --keep-going} -T docs build/html From 22775853ef87a4a9c4d7f6e31cdbee951cfcedb9 Mon Sep 17 00:00:00 2001 From: Eva Lott Date: Tue, 27 Feb 2024 10:34:24 +0000 Subject: [PATCH 08/10] adjusted code to allow the server to send either name --- src/pandablocks/commands.py | 15 ++++++++++----- src/pandablocks/connections.py | 5 ++++- src/pandablocks/hdf.py | 17 ++++++++++++----- tests/test_pandablocks.py | 13 ++++++++----- 4 files changed, 34 insertions(+), 16 deletions(-) diff --git a/src/pandablocks/commands.py b/src/pandablocks/commands.py index e387d2bc..559a3f45 100644 --- a/src/pandablocks/commands.py +++ b/src/pandablocks/commands.py @@ -92,31 +92,36 @@ class CommandException(Exception): # zip() because typing does not support variadic type variables. See # typeshed PR #1550 for discussion. @overload -def _execute_commands(c1: Command[T]) -> ExchangeGenerator[Tuple[T]]: ... +def _execute_commands(c1: Command[T]) -> ExchangeGenerator[Tuple[T]]: + ... @overload def _execute_commands( c1: Command[T], c2: Command[T2] -) -> ExchangeGenerator[Tuple[T, T2]]: ... +) -> ExchangeGenerator[Tuple[T, T2]]: + ... @overload def _execute_commands( c1: Command[T], c2: Command[T2], c3: Command[T3] -) -> ExchangeGenerator[Tuple[T, T2, T3]]: ... +) -> ExchangeGenerator[Tuple[T, T2, T3]]: + ... @overload def _execute_commands( c1: Command[T], c2: Command[T2], c3: Command[T3], c4: Command[T4] -) -> ExchangeGenerator[Tuple[T, T2, T3, T4]]: ... +) -> ExchangeGenerator[Tuple[T, T2, T3, T4]]: + ... @overload def _execute_commands( *commands: Command[Any], -) -> ExchangeGenerator[Tuple[Any, ...]]: ... +) -> ExchangeGenerator[Tuple[Any, ...]]: + ... def _execute_commands(*commands): diff --git a/src/pandablocks/connections.py b/src/pandablocks/connections.py index 50e210d7..9645e72a 100644 --- a/src/pandablocks/connections.py +++ b/src/pandablocks/connections.py @@ -28,7 +28,10 @@ "DataConnection", ] -# The name of the samples field used for averaging unscaled fields +# The names of the samples field used for averaging unscaled fields +# In newer versions it's GATE_DURATION but we keep SAMPLES for backwards +# compatibility +GATE_DURATION_FIELD = "PCAP.GATE_DURATION.Value" SAMPLES_FIELD = "PCAP.SAMPLES.Value" diff --git a/src/pandablocks/hdf.py b/src/pandablocks/hdf.py index ebaf7df1..9a6a0fe8 100644 --- a/src/pandablocks/hdf.py +++ b/src/pandablocks/hdf.py @@ -9,7 +9,7 @@ from pandablocks.commands import Arm from .asyncio import AsyncioClient -from .connections import SAMPLES_FIELD +from .connections import GATE_DURATION_FIELD, SAMPLES_FIELD from .responses import EndData, EndReason, FieldCapture, FrameData, ReadyData, StartData # Define the public API of this module @@ -159,11 +159,18 @@ def __init__(self) -> None: def create_processor(self, field: FieldCapture, raw: bool): column_name = f"{field.name}.{field.capture}" + if raw and field.capture == "Mean": - return ( - lambda data: data[column_name] * field.scale / data[SAMPLES_FIELD] - + field.offset - ) + + def mean_callable(data): + if GATE_DURATION_FIELD in data.dtype.names: + gate_duration = data[GATE_DURATION_FIELD] + else: + gate_duration = data[SAMPLES_FIELD] + + return (data[column_name] * field.scale / gate_duration) + field.offset + + return mean_callable elif raw and (field.scale != 1 or field.offset != 0): return lambda data: data[column_name] * field.scale + field.offset else: diff --git a/tests/test_pandablocks.py b/tests/test_pandablocks.py index 19e4ea30..ab588bfa 100644 --- a/tests/test_pandablocks.py +++ b/tests/test_pandablocks.py @@ -344,7 +344,8 @@ def test_get_fields(): ] -def test_get_fields_type_ext_out(): +@pytest.mark.parametrize("gate_duration_name", ["GATE_DURATION", "SAMPLES"]) +def test_get_fields_type_ext_out(gate_duration_name): """Test for field type == ext_out, ensuring we add .CAPTURE to the end of the *ENUMS command""" conn = ControlConnection() @@ -352,10 +353,12 @@ def test_get_fields_type_ext_out(): assert conn.send(cmd) == b"PCAP.*?\n" # First yield, the response to "PCAP.*?" - assert ( - conn.receive_bytes(b"!SAMPLES 9 ext_out samples\n.\n") - == b"*DESC.PCAP.SAMPLES?\n*ENUMS.PCAP.SAMPLES.CAPTURE?\n" + request_str = bytes(f"!{gate_duration_name} 9 ext_out samples\n.\n", "utf-8") + response_str = bytes( + f"*DESC.PCAP.{gate_duration_name}?\n*ENUMS.PCAP.{gate_duration_name}.CAPTURE?\n", + "utf-8", ) + assert conn.receive_bytes(request_str) == response_str # Responses to the *DESC and *ENUM commands responses = [ @@ -371,7 +374,7 @@ def test_get_fields_type_ext_out(): ( cmd, { - "SAMPLES": ExtOutFieldInfo( + gate_duration_name: ExtOutFieldInfo( type="ext_out", subtype="samples", description="Number of gated samples in the current capture", From 7316225cf28dd3adb71a9d5899b6e7d699dce2ac Mon Sep 17 00:00:00 2001 From: Eva Lott Date: Wed, 28 Feb 2024 13:23:48 +0000 Subject: [PATCH 09/10] editied to test panda with both field --- tests/conftest.py | 29 +++++++++-- .../fast_dump.bin} | Bin tests/data_dumps/raw_dump.bin | Bin 0 -> 440881 bytes .../raw_dump_no_duration.bin} | Bin .../slow_dump.bin} | Bin tests/test_cli.py | 49 +++++++++--------- 6 files changed, 50 insertions(+), 28 deletions(-) rename tests/{fast_dump.txt => data_dumps/fast_dump.bin} (100%) create mode 100644 tests/data_dumps/raw_dump.bin rename tests/{raw_dump.txt => data_dumps/raw_dump_no_duration.bin} (100%) rename tests/{slow_dump.txt => data_dumps/slow_dump.bin} (100%) diff --git a/tests/conftest.py b/tests/conftest.py index afb76cf2..1c77d6ad 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -30,28 +30,35 @@ def chunked_read(f: BufferedReader, size: int) -> Iterator[bytes]: @pytest_asyncio.fixture def slow_dump(): - with open(Path(__file__).parent / "slow_dump.txt", "rb") as f: + with open(Path(__file__).parent / "data_dumps/slow_dump.bin", "rb") as f: # Simulate small chunked read, sized so we hit the middle of a "BIN " marker yield chunked_read(f, 44) @pytest_asyncio.fixture def fast_dump(): - with open(Path(__file__).parent / "fast_dump.txt", "rb") as f: + with open(Path(__file__).parent / "data_dumps/fast_dump.bin", "rb") as f: # Simulate larger chunked read yield chunked_read(f, 500) @pytest_asyncio.fixture def raw_dump(): - with open(Path(__file__).parent / "raw_dump.txt", "rb") as f: + with open(Path(__file__).parent / "data_dumps/raw_dump.bin", "rb") as f: + # Simulate largest chunked read + yield chunked_read(f, 200000) + + +@pytest_asyncio.fixture +def raw_dump_no_duration(): + with open(Path(__file__).parent / "data_dumps/raw_dump_no_duration.bin", "rb") as f: # Simulate largest chunked read yield chunked_read(f, 200000) @pytest.fixture def overrun_dump(): - with open(Path(__file__).parent / "raw_dump.txt", "rb") as f: + with open(Path(__file__).parent / "data_dumps/raw_dump.bin", "rb") as f: # All in one go return f.read().replace(b"Disarmed", b"Data overrun") @@ -116,6 +123,20 @@ def overrun_dump(): ] +def assert_all_data_in_hdf_file(hdf_file, samples_name): + def multiples(num, offset=0): + return pytest.approx(np.arange(1, 10001) * num + offset) + + assert hdf_file["/COUNTER1.OUT.Max"][:] == multiples(1) + assert hdf_file["/COUNTER1.OUT.Mean"][:] == multiples(1) + assert hdf_file["/COUNTER1.OUT.Min"][:] == multiples(1) + assert hdf_file["/COUNTER2.OUT.Mean"][:] == multiples(2) + assert hdf_file["/COUNTER3.OUT.Value"][:] == multiples(3) + assert hdf_file["/PCAP.BITS2.Value"][:] == multiples(0) + assert hdf_file[f"/{samples_name}"][:] == multiples(0, offset=125) + assert hdf_file["/PCAP.TS_START.Value"][:] == multiples(2e-6, offset=7.2e-8 - 2e-6) + + class Rows: def __init__(self, *rows): self.rows = rows diff --git a/tests/fast_dump.txt b/tests/data_dumps/fast_dump.bin similarity index 100% rename from tests/fast_dump.txt rename to tests/data_dumps/fast_dump.bin diff --git a/tests/data_dumps/raw_dump.bin b/tests/data_dumps/raw_dump.bin new file mode 100644 index 0000000000000000000000000000000000000000..6baaaf6a7de84f892e38cf289b35c8d02b5aeeed GIT binary patch literal 440881 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 uTs@{q8`!Q`sT%A=%j&+A>OQPV8`#^i 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 Date: Mon, 11 Mar 2024 15:54:29 +0000 Subject: [PATCH 10/10] converted docs and made ruff complient --- docs/CHANGELOG.rst | 17 --- docs/conf.py | 1 + .../decisions/0003-make-library-sans-io.md} | 17 +-- .../{performance.rst => performance.md} | 46 +++--- docs/explanations/{sans-io.rst => sans-io.md} | 73 ++++----- ...trospect-panda.rst => introspect-panda.md} | 19 ++- .../{library-hdf.rst => library-hdf.md} | 36 ++--- docs/how-to/poll-changes.md | 3 + docs/how-to/poll-changes.rst | 4 - docs/reference/api.md | 17 --- docs/reference/api.rst | 92 +++++++++++ docs/reference/appendix.rst | 15 -- docs/reference/changelog.rst | 1 - docs/reference/contributing.rst | 1 - docs/tutorials/commandline-hdf.md | 144 ++++++++++++++++++ docs/tutorials/commandline-hdf.rst | 126 --------------- docs/tutorials/control.md | 68 +++++++++ docs/tutorials/control.rst | 61 -------- .../tutorials/{load-save.rst => load-save.md} | 55 ++++--- src/pandablocks/commands.py | 15 +- src/pandablocks/connections.py | 4 +- tests/test_asyncio.py | 2 +- tests/test_cli.py | 4 +- 23 files changed, 432 insertions(+), 389 deletions(-) delete mode 100644 docs/CHANGELOG.rst rename docs/{developer/explanations/decisions/0003-make-library-sans-io.rst => explanations/decisions/0003-make-library-sans-io.md} (62%) rename docs/explanations/{performance.rst => performance.md} (77%) rename docs/explanations/{sans-io.rst => sans-io.md} (52%) rename docs/how-to/{introspect-panda.rst => introspect-panda.md} (56%) rename docs/how-to/{library-hdf.rst => library-hdf.md} (67%) create mode 100644 docs/how-to/poll-changes.md delete mode 100644 docs/how-to/poll-changes.rst delete mode 100644 docs/reference/api.md create mode 100644 docs/reference/api.rst delete mode 100644 docs/reference/appendix.rst delete mode 100644 docs/reference/changelog.rst delete mode 100644 docs/reference/contributing.rst create mode 100644 docs/tutorials/commandline-hdf.md delete mode 100644 docs/tutorials/commandline-hdf.rst create mode 100644 docs/tutorials/control.md delete mode 100644 docs/tutorials/control.rst rename docs/tutorials/{load-save.rst => load-save.md} (51%) diff --git a/docs/CHANGELOG.rst b/docs/CHANGELOG.rst deleted file mode 100644 index 8bfb306d..00000000 --- a/docs/CHANGELOG.rst +++ /dev/null @@ -1,17 +0,0 @@ -Change Log -========== -All notable changes to this project will be documented in this file. -This project adheres to `Semantic Versioning `_. - -Unreleased_ ------------ - -- Nothing yet - - -0.1 - 2020-07-09 ----------------- - -- Initial release - -.. _Unreleased: https://github.com/PandABlocks/PandABlocks-client/compare/0.1...HEAD diff --git a/docs/conf.py b/docs/conf.py index a3b457b5..5f555b79 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -73,6 +73,7 @@ ("py:class", "'id'"), ("py:class", "typing_extensions.Literal"), ("py:func", "int"), + ("py:class", "pandablocks.commands.T"), ] # Both the class’ and the __init__ method’s docstring are concatenated and diff --git a/docs/developer/explanations/decisions/0003-make-library-sans-io.rst b/docs/explanations/decisions/0003-make-library-sans-io.md similarity index 62% rename from docs/developer/explanations/decisions/0003-make-library-sans-io.rst rename to docs/explanations/decisions/0003-make-library-sans-io.md index a1a634ff..ad9f2e5c 100644 --- a/docs/developer/explanations/decisions/0003-make-library-sans-io.rst +++ b/docs/explanations/decisions/0003-make-library-sans-io.md @@ -1,24 +1,19 @@ -3. Sans-IO pandABlocks-client -============================= +# 3. Sans-IO pandABlocks-client Date: 2021-08-02 (ADR created retroactively) -Status ------- +## Status Accepted -Context -------- +## Context Ensure PandABlocks-client works sans-io. -Decision --------- +## Decision We will ensure pandablocks works sans-io `sans-io `. -Consequences ------------- +## Consequences -We have the option to use an asyncio client or a blocking client. \ No newline at end of file +We have the option to use an asyncio client or a blocking client. diff --git a/docs/explanations/performance.rst b/docs/explanations/performance.md similarity index 77% rename from docs/explanations/performance.rst rename to docs/explanations/performance.md index f2952d96..4e7a22f2 100644 --- a/docs/explanations/performance.rst +++ b/docs/explanations/performance.md @@ -1,14 +1,13 @@ -.. _performance: +(performance)= -How fast can we write HDF files? -================================ +# How fast can we write HDF files? There are many factors that affect the speed we can write HDF files. This article discusses how this library addresses them and what the maximum data rate of a PandA is. -Factors to consider -------------------- +## Factors to consider +```{eval-rst} .. list-table:: :widths: 10 50 @@ -32,50 +31,44 @@ Factors to consider or panda-webcontrol will reduce throughput * - Flush rate - Flushing data to disk to often will slow write speed +``` -Strategies to help ------------------- +## Strategies to help There are a number of strategies that help increase performance. These can be combined to give the greatest benefit -Average the data -~~~~~~~~~~~~~~~~ +### Average the data -Selecting the ``Mean`` capture mode will activate on-FPGA averaging of the -captured value. ``Min`` and ``Max`` can also be captured at the same time. -Capturing these rather than ``Value`` may allow you to lower the trigger +Selecting the `Mean` capture mode will activate on-FPGA averaging of the +captured value. `Min` and `Max` can also be captured at the same time. +Capturing these rather than `Value` may allow you to lower the trigger frequency while still providing enough information for data analysis -Scale the data on the client -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +### Scale the data on the client -`AsyncioClient.data` and `BlockingClient.data` accept a ``scaled`` argument. +`AsyncioClient.data` and `BlockingClient.data` accept a `scaled` argument. Setting this to False will transfer the raw unscaled data, allowing for up to 50% more data to be sent depending on the datatype of the field. You can use the `StartData.fields` information to scale the data on the client. The `write_hdf_files` function uses this approach. -Remove the panda-webcontrol package -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +### Remove the panda-webcontrol package The measures above should get you to about 50MBytes/s, but if more clients connect to the web GUI then this will drop. To increase the data rate to 60MBytes/s and improve stability you may want to remove the panda-webcontrol zpkg. -Flush about 1Hz -~~~~~~~~~~~~~~~ +### Flush about 1Hz -`AsyncioClient.data` accepts a ``flush_period`` argument. If given, it will +`AsyncioClient.data` accepts a `flush_period` argument. If given, it will squash intermediate data frames together until this period expires, and only then produce them. This means the numpy data blocks are larger and can be more efficiently written to disk then flushed. The `write_hdf_files` function uses this approach. - -Performance Achieved --------------------- +## Performance Achieved Tests were run with the following conditions: @@ -100,15 +93,14 @@ When panda-webcontrol was not installed, the following results were achieved: Increasing above these throughputs failed most scans with `DATA_OVERRUN`. -Data overruns -------------- +## Data overruns If there is a `DATA_OVERRUN`, the server will stop sending data. The most recently received `FrameData` from either `AsyncioClient.data` or `BlockingClient.data` may -be corrupt. This is the case if the ``scaled`` argument is set to False. The mechanism +be corrupt. This is the case if the `scaled` argument is set to False. The mechanism the server uses to send raw unscaled data is only able to detect the corrupt frame after it has already been sent. Conversely, the mechanism used to send scaled data aborts prior to sending a corrupt frame. -The `write_hdf_files` function uses ``scaled=False``, so your HDF file may include some +The `write_hdf_files` function uses `scaled=False`, so your HDF file may include some corrupt data in the event of an overrun. diff --git a/docs/explanations/sans-io.rst b/docs/explanations/sans-io.md similarity index 52% rename from docs/explanations/sans-io.rst rename to docs/explanations/sans-io.md index e8f75968..07c2357f 100644 --- a/docs/explanations/sans-io.rst +++ b/docs/explanations/sans-io.md @@ -1,9 +1,8 @@ -.. _sans-io: +(sans-io)= -Why write a Sans-IO library? -============================ +# Why write a Sans-IO library? -As the reference_ says: *Reusability*. The protocol can be coded in a separate +As the [reference] says: *Reusability*. The protocol can be coded in a separate class to the I/O allowing integration into a number of different concurrency frameworks. @@ -12,69 +11,61 @@ coded the protocol in either of them it would not be usable in the other. Much better to put it in a separate class and feed it bytes off the wire. We call this protocol encapsulation a Connection. -Connections ------------ +## Connections The PandA TCP server exposes a Control port and a Data port, so there are -corresponding `ControlConnection` and `DataConnection` objects: +corresponding [](ControlConnection) and objects: -.. currentmodule:: pandablocks.connections +The [](ControlConnection) class has the following methods: -.. autoclass:: ControlConnection - :noindex: - - The :meth:`~ControlConnection.send` method takes a `Command` subclass and +- The [`send()`](ControlConnection.send) method takes a `Command` subclass and returns the bytes that should be sent to the PandA. Whenever bytes are - received from the socket they can be passed to - :meth:`~ControlConnection.receive_bytes` which will return any subsequent - bytes that should be send back. The :meth:`~ControlConnection.responses` - method returns an iterator of ``(command, response)`` tuples that have now + received from the socket they can be passed to this method which will return any subsequent + bytes that should be send back. +- The [`responses()`](ControlConnection.responses) method returns an iterator of ``(command, response)`` tuples that have now completed. The response type will depend on the command. For instance `Get` returns `bytes` or a `list` of `bytes` of the field value, and `GetFieldInfo` returns a `dict` mapping `str` field name to `FieldInfo`. -.. autoclass:: DataConnection - :noindex: +The [](DataConnection) class has the following methods: - The :meth:`~DataConnection.connect` method takes any connection arguments +- The [`connect()`](DataConnection.connect) method takes any connection arguments and returns the bytes that should be sent to the PandA to make the initial connection. Whenever bytes are received from the socket they can be passed - to :meth:`~DataConnection.receive_bytes` which will return an iterator of - `Data` objects. Intermediate `FrameData` can be squashed together by passing + to this method which will return an iterator of + `Data` objects. +- Intermediate `FrameData` can be squashed together by passing ``flush_every_frame=False``, then explicitly calling - :meth:`~DataConnection.flush` when they are required. + [`flush()`](DataConnection.flush) when they are required. -Wrappers --------- +## Wrappers Of course, these Connections are useless without connecting some I/O. To aid with this, wrappers are included for use in `asyncio ` and blocking programs. They expose slightly different APIs to make best use of the features of their respective concurrency frameworks. -For example, to send multiple commands in fields with the `blocking` wrapper:: +For example, to send multiple commands in fields with the `blocking` wrapper: - with BlockingClient("hostname") as client: - resp1, resp2 = client.send([cmd1, cmd2]) +``` +with BlockingClient("hostname") as client: + resp1, resp2 = client.send([cmd1, cmd2]) +``` -while with the `asyncio` wrapper we would:: +while with the `asyncio` wrapper we would: - async with AsyncioClient("hostname") as client: - resp1, resp2 = await asyncio.gather( - client.send(cmd1), - client.send(cmd2) - ) +``` +async with AsyncioClient("hostname") as client: + resp1, resp2 = await asyncio.gather( + client.send(cmd1), + client.send(cmd2) + ) +``` The first has the advantage of simplicity, but blocks while waiting for data. The second allows multiple co-routines to use the client at the same time at the expense of a more verbose API. -The wrappers do not guarantee feature parity, for instance the ``flush_period`` +The wrappers do not guarantee feature parity, for instance the `flush_period` option is only available in the asyncio wrapper. - - - - - - -.. _reference: https://sans-io.readthedocs.io/ \ No newline at end of file +[reference]: https://sans-io.readthedocs.io/ diff --git a/docs/how-to/introspect-panda.rst b/docs/how-to/introspect-panda.md similarity index 56% rename from docs/how-to/introspect-panda.rst rename to docs/how-to/introspect-panda.md index 2bd12914..64ca6cd5 100644 --- a/docs/how-to/introspect-panda.rst +++ b/docs/how-to/introspect-panda.md @@ -1,22 +1,21 @@ -How to introspect a PandA -=========================== +# How to introspect a PandA Using a combination of `commands ` it is straightforward to query the PandA -to list all blocks, and all fields inside each block, that exist. +to list all blocks, and all fields inside each block, that exist. Call the following script, with the address of the PandA as the first and only command line argument: +```{literalinclude} ../../examples/introspect_panda.py +``` -.. literalinclude:: ../../../examples/introspect_panda.py - -This script can be found in ``examples/introspect_panda.py``. +This script can be found in `examples/introspect_panda.py`. By examining the `BlockInfo` structure returned from `GetBlockInfo` for each Block the number and description may be acquired for every block. -By examining the `FieldInfo` structure (which is fully printed in this example) the ``type``, -``sub-type``, ``description`` and ``label`` may all be found for every field. +By examining the `FieldInfo` structure (which is fully printed in this example) the `type`, +`sub-type`, `description` and `label` may all be found for every field. -Lastly the complete list of every ``BITS`` field in the ``PCAP`` block are gathered and -printed. See the documentation in the `Field Types `_ +Lastly the complete list of every `BITS` field in the `PCAP` block are gathered and +printed. See the documentation in the [Field Types](https://pandablocks-server.readthedocs.io/en/latest/fields.html?#field-types) section of the PandA Server documentation. diff --git a/docs/how-to/library-hdf.rst b/docs/how-to/library-hdf.md similarity index 67% rename from docs/how-to/library-hdf.rst rename to docs/how-to/library-hdf.md index 54ebd97a..b24befe8 100644 --- a/docs/how-to/library-hdf.rst +++ b/docs/how-to/library-hdf.md @@ -1,34 +1,34 @@ -.. _library-hdf: +(library-hdf)= -How to use the library to capture HDF files -=========================================== +# How to use the library to capture HDF files The `commandline-hdf` introduced how to use the commandline to capture HDF files. The `write_hdf_files` function that is called to do this can also be integrated into custom Python applications. This guide shows how to do this. -Approach 1: Call the function directly --------------------------------------- +## Approach 1: Call the function directly If you need a one-shot configure and run application, you can use the function directly: -.. literalinclude:: ../../../examples/arm_and_hdf.py +```{literalinclude} ../../examples/arm_and_hdf.py +``` With the `AsyncioClient` as a `Context Manager `, this code sets up some fields of a PandA before taking a single acquisition. The code in `write_hdf_files` is responsible for arming the PandA. -.. note:: +:::{note} +There are no log messages emitted like in `commandline-hdf`. This is because +we have not configured the logging framework in this example. You can get +these messages by adding a call to `logging.basicConfig` like this: - There are no log messages emitted like in `commandline-hdf`. This is because - we have not configured the logging framework in this example. You can get - these messages by adding a call to `logging.basicConfig` like this:: +``` +logging.basicConfig(level=logging.INFO) +``` +::: - logging.basicConfig(level=logging.INFO) - -Approach 2: Create the pipeline yourself ----------------------------------------- +## Approach 2: Create the pipeline yourself If you need more control over the pipeline, for instance to display progress, you can create the pipeline yourself, and feed it data from the PandA. This @@ -36,7 +36,8 @@ means you can make decisions about when to start and stop acquisitions based on the `Data` objects that go past. For example, if we want to make a progress bar we could: -.. literalinclude:: ../../../examples/hdf_queue_reporting.py +```{literalinclude} ../../examples/hdf_queue_reporting.py +``` This time, after setting up the PandA, we create the `AsyncioClient.data` iterator ourselves. Each `Data` object we get is queued on the first `Pipeline` @@ -46,9 +47,8 @@ update a progress bar, or return as acquisition is complete. In a `finally ` block we stop the pipeline, which will wait for all data to flow through the pipeline and close the HDF file. -Performance ------------ +## Performance The commandline client and both these approaches use the same core code, so will give the same performance. The steps to consider in optimising performance are -outlined in `performance` \ No newline at end of file +outlined in `performance` diff --git a/docs/how-to/poll-changes.md b/docs/how-to/poll-changes.md new file mode 100644 index 00000000..bfab9b0e --- /dev/null +++ b/docs/how-to/poll-changes.md @@ -0,0 +1,3 @@ +# How to efficiently poll for changes + +Write something about using `*CHANGES` like Malcolm does. diff --git a/docs/how-to/poll-changes.rst b/docs/how-to/poll-changes.rst deleted file mode 100644 index 6be37625..00000000 --- a/docs/how-to/poll-changes.rst +++ /dev/null @@ -1,4 +0,0 @@ -How to efficiently poll for changes -=================================== - -Write something about using ``*CHANGES`` like Malcolm does. diff --git a/docs/reference/api.md b/docs/reference/api.md deleted file mode 100644 index 687260d8..00000000 --- a/docs/reference/api.md +++ /dev/null @@ -1,17 +0,0 @@ -# API - -```{eval-rst} -.. automodule:: pandablocks - - ``pandablocks`` - ----------------------------------- -``` - -This is the internal API reference for pandablocks - -```{eval-rst} -.. data:: pandablocks.__version__ - :type: str - - Version number as calculated by https://github.com/pypa/setuptools_scm -``` diff --git a/docs/reference/api.rst b/docs/reference/api.rst new file mode 100644 index 00000000..9b395740 --- /dev/null +++ b/docs/reference/api.rst @@ -0,0 +1,92 @@ +.. _API: + +API +=== + +The top level pandablocks module contains a number of packages that can be used +from code: + +- `pandablocks.commands`: The control commands that can be sent to a PandA +- `pandablocks.responses`: The control and data responses that will be received +- `pandablocks.connections`: Control and data connections that implements the parsing logic +- `pandablocks.asyncio`: An asyncio client that uses the control and data connections +- `pandablocks.blocking`: A blocking client that uses the control and data connections +- `pandablocks.hdf`: Some helpers for efficiently writing data responses to disk +- `pandablocks.utils`: General utility methods for use with pandablocks + + +.. automodule:: pandablocks.commands + :members: + + Commands + -------- + + There is a `Command` subclass for every sort of command that can be sent to + the `ControlConnection` of a PandA. Many common actions can be accomplished + with a simple `Get` or `Put`, but some convenience commands like + `GetBlockInfo`, `GetFieldInfo`, etc. are provided that parse output into + specific classes. + + +.. automodule:: pandablocks.responses + :members: + + Responses + --------- + + Classes used in responses from both the `ControlConnection` and + `DataConnection` of a PandA live in this package. + +.. automodule:: pandablocks.connections + :members: + + Connections + ----------- + + `Sans-IO ` connections for both the Control and Data ports + of PandA TCP server. + +.. automodule:: pandablocks.asyncio + :members: + + Asyncio Client + -------------- + + This is an `asyncio` wrapper to the `ControlConnection` and `DataConnection` + objects, allowing async calls to ``send(command)`` and iterate over + ``data()``. + +.. automodule:: pandablocks.blocking + :members: + + Blocking Client + --------------- + + This is a blocking wrapper to the `ControlConnection` and `DataConnection` + objects, allowing blocking calls to ``send(commands)`` and iterate over + ``data()``. + +.. automodule:: pandablocks.hdf + :members: + + HDF Writing + ----------- + + This package contains components needed to write PCAP data to and HDF file + in the most efficient way. The oneshot `write_hdf_files` is exposed in the + commandline interface. It assembles a short `Pipeline` of: + + `AsyncioClient` -> `FrameProcessor` -> `HDFWriter` + + The FrameProcessor and HDFWriter run in their own threads as most of the + heavy lifting is done by numpy_ and h5py_, so running in their own threads + gives multi-CPU benefits without hitting the limit of the GIL. + + .. seealso:: `library-hdf`, `performance` + +.. automodule:: pandablocks.utils + + Utilities + --------- + + This package contains general methods for working with pandablocks. \ No newline at end of file diff --git a/docs/reference/appendix.rst b/docs/reference/appendix.rst deleted file mode 100644 index 9c10c37e..00000000 --- a/docs/reference/appendix.rst +++ /dev/null @@ -1,15 +0,0 @@ -:orphan: - -Appendix -======== - -These definitions are needed to quell sphinx warnings. - -.. py:class:: T - :canonical: pandablocks.commands.T - - Parameter for Generic class `Command`, indicating its response type - -.. py:class:: socket.socket - - The docs for this are `here ` \ No newline at end of file diff --git a/docs/reference/changelog.rst b/docs/reference/changelog.rst deleted file mode 100644 index 09929fe4..00000000 --- a/docs/reference/changelog.rst +++ /dev/null @@ -1 +0,0 @@ -.. include:: ../../CHANGELOG.rst diff --git a/docs/reference/contributing.rst b/docs/reference/contributing.rst deleted file mode 100644 index 65b992f0..00000000 --- a/docs/reference/contributing.rst +++ /dev/null @@ -1 +0,0 @@ -.. include:: ../../../.github/CONTRIBUTING.rst diff --git a/docs/tutorials/commandline-hdf.md b/docs/tutorials/commandline-hdf.md new file mode 100644 index 00000000..7c835620 --- /dev/null +++ b/docs/tutorials/commandline-hdf.md @@ -0,0 +1,144 @@ +(commandline-hdf)= + +# Commandline Capture of HDF Files Tutorial + +This tutorial shows how to use the commandline tool to save an HDF file from the PandA +for each PCAP acquisition. It assumes that you have followed the `tutorial-load-save` tutorial +to setup the PandA. + +## Capturing some data + +In one terminal launch the HDF writer client, and tell it to capture 3 frames in a +location of your choosing: + +``` +pandablocks hdf --num=3 /tmp/panda-capture-%d.h5 +``` + +Where `` is the hostname or ip address of your PandA. This will connect +to the data port of the PandA and start listening for up to 3 acquisitions. It will +then write these into files: + +``` +/tmp/panda-capture-1.h5 +/tmp/panda-capture-2.h5 +/tmp/panda-capture-3.h5 +``` + +In a second terminal you can launch the acquisition: + +``` +$ pandablocks control +< *PCAP.ARM= +OK +``` + +This should write 1000 frames at 500Hz, printing in the first terminal window: + +``` +INFO:Opened '/tmp/panda-capture-1.h5' with 60 byte samples stored in 11 datasets +INFO:Closed '/tmp/panda-capture-1.h5' after writing 1000 samples. End reason is 'Ok' +``` + +You can then do `PCAP.ARM=` twice more to make the other files. + +## Examining the data + +You can use your favourite HDF reader to examine the data. It is written in `swmr` +mode so that you can read partial acquisitions before they are complete. + +:::{note} +Reading SWMR HDF5 files while they are being written to require the use of a +Posix compliant filesystem like a local disk or GPFS native client. NFS +mounts are *not* Posix compliant. +::: + +In the repository `examples/plot_counter_hdf.py` is an example of reading the +file, listing the datasets, and plotting the counters: + +```{literalinclude} ../../examples/plot_counter_hdf.py +``` + +Running it on `/tmp/panda-capture-1.h5` will show the three counter values: + +```{eval-rst} +.. plot:: + + for i in range(1, 4): + plt.plot(np.arange(1, 1001) * i, label=f"Counter {i}") + plt.legend() + plt.show() +``` + +You should see that they are all the same size: + +``` +$ ls -s --si /tmp/panda-capture-*.h5 +74k /tmp/panda-capture-1.h5 +74k /tmp/panda-capture-2.h5 +74k /tmp/panda-capture-3.h5 +``` + +If you have h5diff you can check the contents are the same: + +``` +$ h5diff /tmp/panda-capture-1.h5 /tmp/panda-capture-2.h5 +$ h5diff /tmp/panda-capture-1.h5 /tmp/panda-capture-3.h5 +``` + +## Collecting more data faster + +The test data is produced by a SEQ Block, configured to produce a high level +for 1 prescaled tick, then a low level for 1 prescaled tick. The default +setting is to produce 1000 repeats of these, with a prescale of 1ms and hence +a period of 2ms. Each sample is 11 fields, totalling 60 bytes, which means +that it will produce data at a modest 30kBytes/s for a total of 2s. +We can increase this to a more taxing 30MBytes/s by reducing the +prescaler to 1us. If we increase the prescaler to 10 million then we will +sustain this data rate for 20s and write 600MByte files each time: + +``` +$ pandablocks control +< SEQ1.REPEATS? +OK =1000 # It was doing 1k samples, change to 10M +< SEQ1.REPEATS=10000000 +OK +< SEQ1.PRESCALE? +OK =1000 +< SEQ1.PRESCALE.UNITS? +OK =us # It was doing 1ms ticks, change to 1us +< SEQ1.PRESCALE=1 +OK +``` + +Lets write a single file this time, telling the command to also arm the PandA: + +``` +pandablocks hdf --arm /tmp/biggerfile-%d.h5 +``` + +Twenty seconds later we will get a file: + +``` +$ ls -s --si /tmp/biggerfile-*.h5 +602M /tmp/biggerfile-1.h5 +``` + +Which looks very similar when plotted with the code above, just a bit bigger: + +```{eval-rst} +.. plot:: + + for i in range(1, 4): + plt.plot(np.arange(1, 10000001) * i, label=f"Counter {i}") + plt.legend() + plt.show() +``` + +## Conclusion + +This tutorial has shown how to capture data to an HDF file using the commandline +client. It is possible to use this commandline interface in production, but it is +more likely to be integrated in an application that controls the acquisition as well +as writing the data. This is covered in `library-hdf`. You can explore strategies +on getting the maximum performance out of a PandA in `performance`. diff --git a/docs/tutorials/commandline-hdf.rst b/docs/tutorials/commandline-hdf.rst deleted file mode 100644 index 28b5f0ce..00000000 --- a/docs/tutorials/commandline-hdf.rst +++ /dev/null @@ -1,126 +0,0 @@ -.. _commandline-hdf: - -Commandline Capture of HDF Files Tutorial -========================================= - -This tutorial shows how to use the commandline tool to save an HDF file from the PandA -for each PCAP acquisition. It assumes that you have followed the `tutorial-load-save` tutorial -to setup the PandA. - -Capturing some data -------------------- - -In one terminal launch the HDF writer client, and tell it to capture 3 frames in a -location of your choosing:: - - pandablocks hdf --num=3 /tmp/panda-capture-%d.h5 - -Where ```` is the hostname or ip address of your PandA. This will connect -to the data port of the PandA and start listening for up to 3 acquisitions. It will -then write these into files:: - - /tmp/panda-capture-1.h5 - /tmp/panda-capture-2.h5 - /tmp/panda-capture-3.h5 - -In a second terminal you can launch the acquisition:: - - $ pandablocks control - < *PCAP.ARM= - OK - -This should write 1000 frames at 500Hz, printing in the first terminal window:: - - INFO:Opened '/tmp/panda-capture-1.h5' with 60 byte samples stored in 11 datasets - INFO:Closed '/tmp/panda-capture-1.h5' after writing 1000 samples. End reason is 'Ok' - -You can then do ``PCAP.ARM=`` twice more to make the other files. - -Examining the data ------------------- - -You can use your favourite HDF reader to examine the data. It is written in `swmr` -mode so that you can read partial acquisitions before they are complete. - -.. note:: - - Reading SWMR HDF5 files while they are being written to require the use of a - Posix compliant filesystem like a local disk or GPFS native client. NFS - mounts are *not* Posix compliant. - -In the repository ``examples/plot_counter_hdf.py`` is an example of reading the -file, listing the datasets, and plotting the counters: - -.. literalinclude:: ../../../examples/plot_counter_hdf.py - -Running it on ``/tmp/panda-capture-1.h5`` will show the three counter values: - -.. plot:: - - for i in range(1, 4): - plt.plot(np.arange(1, 1001) * i, label=f"Counter {i}") - plt.legend() - plt.show() - -You should see that they are all the same size:: - - $ ls -s --si /tmp/panda-capture-*.h5 - 74k /tmp/panda-capture-1.h5 - 74k /tmp/panda-capture-2.h5 - 74k /tmp/panda-capture-3.h5 - -If you have h5diff you can check the contents are the same:: - - $ h5diff /tmp/panda-capture-1.h5 /tmp/panda-capture-2.h5 - $ h5diff /tmp/panda-capture-1.h5 /tmp/panda-capture-3.h5 - -Collecting more data faster ---------------------------- - -The test data is produced by a SEQ Block, configured to produce a high level -for 1 prescaled tick, then a low level for 1 prescaled tick. The default -setting is to produce 1000 repeats of these, with a prescale of 1ms and hence -a period of 2ms. Each sample is 11 fields, totalling 60 bytes, which means -that it will produce data at a modest 30kBytes/s for a total of 2s. -We can increase this to a more taxing 30MBytes/s by reducing the -prescaler to 1us. If we increase the prescaler to 10 million then we will -sustain this data rate for 20s and write 600MByte files each time:: - - $ pandablocks control - < SEQ1.REPEATS? - OK =1000 # It was doing 1k samples, change to 10M - < SEQ1.REPEATS=10000000 - OK - < SEQ1.PRESCALE? - OK =1000 - < SEQ1.PRESCALE.UNITS? - OK =us # It was doing 1ms ticks, change to 1us - < SEQ1.PRESCALE=1 - OK - -Lets write a single file this time, telling the command to also arm the PandA:: - - pandablocks hdf --arm /tmp/biggerfile-%d.h5 - -Twenty seconds later we will get a file:: - - $ ls -s --si /tmp/biggerfile-*.h5 - 602M /tmp/biggerfile-1.h5 - -Which looks very similar when plotted with the code above, just a bit bigger: - -.. plot:: - - for i in range(1, 4): - plt.plot(np.arange(1, 10000001) * i, label=f"Counter {i}") - plt.legend() - plt.show() - -Conclusion ----------- - -This tutorial has shown how to capture data to an HDF file using the commandline -client. It is possible to use this commandline interface in production, but it is -more likely to be integrated in an application that controls the acquisition as well -as writing the data. This is covered in `library-hdf`. You can explore strategies -on getting the maximum performance out of a PandA in `performance`. diff --git a/docs/tutorials/control.md b/docs/tutorials/control.md new file mode 100644 index 00000000..bf8cbf59 --- /dev/null +++ b/docs/tutorials/control.md @@ -0,0 +1,68 @@ +# Interactive Control Tutorial + +This tutorial shows how to use the commandline tool to open an interactive terminal +to control a PandA. + +## Connect + +Open a terminal, and type: + +``` +pandablocks control +``` + +Where `` is the hostname or ip address of your PandA. + +## Type Commands + +You should be presented with a prompt where you can type PandABlocks-server +[commands]. If you are on Linux you can tab complete commands with the TAB key: + +``` +< PCAP. # Hit TAB key... +PCAP.ACTIVE PCAP.BITS1 PCAP.BITS3 PCAP.GATE PCAP.SAMPLES PCAP.TRIG PCAP.TS_END PCAP.TS_TRIG +PCAP.BITS0 PCAP.BITS2 PCAP.ENABLE PCAP.HEALTH PCAP.SHIFT_SUM PCAP.TRIG_EDGE PCAP.TS_START +``` + +Pressing return will send the command to the server and display the response. + +## Control an acquisition + +You can check if an acquisition is currently in progress by getting the value of the +`PCAP.ACTIVE` field: + +``` +< PCAP.ACTIVE? +OK =0 +``` + +You can start and stop acquisitions with special "star" commands. To start an acquisition: + +``` +< *PCAP.ARM= +OK +``` + +You can now use the up arrow to recall the previous command, then press return: + +``` +< PCAP.ACTIVE? +OK =1 +``` + +This means that acquisition is in progress. You can stop it by disarming: + +``` +< *PCAP.DISARM= +OK +< PCAP.ACTIVE? +OK =0 +``` + +## Conclusion + +This tutorial has shown how to start and stop an acquisition from the commandline +client. It can also be used to send any other control [commands] to query and set +variables on the PandA. + +[commands]: https://pandablocks-server.readthedocs.io/en/latest/commands.html diff --git a/docs/tutorials/control.rst b/docs/tutorials/control.rst deleted file mode 100644 index 339d1cc9..00000000 --- a/docs/tutorials/control.rst +++ /dev/null @@ -1,61 +0,0 @@ -Interactive Control Tutorial -============================ - -This tutorial shows how to use the commandline tool to open an interactive terminal -to control a PandA. - -Connect -------- - -Open a terminal, and type:: - - pandablocks control - -Where ```` is the hostname or ip address of your PandA. - -Type Commands -------------- - -You should be presented with a prompt where you can type PandABlocks-server -commands_. If you are on Linux you can tab complete commands with the TAB key:: - - < PCAP. # Hit TAB key... - PCAP.ACTIVE PCAP.BITS1 PCAP.BITS3 PCAP.GATE PCAP.SAMPLES PCAP.TRIG PCAP.TS_END PCAP.TS_TRIG - PCAP.BITS0 PCAP.BITS2 PCAP.ENABLE PCAP.HEALTH PCAP.SHIFT_SUM PCAP.TRIG_EDGE PCAP.TS_START - -Pressing return will send the command to the server and display the response. - -Control an acquisition ----------------------- - -You can check if an acquisition is currently in progress by getting the value of the -``PCAP.ACTIVE`` field:: - - < PCAP.ACTIVE? - OK =0 - -You can start and stop acquisitions with special "star" commands. To start an acquisition:: - - < *PCAP.ARM= - OK - -You can now use the up arrow to recall the previous command, then press return:: - - < PCAP.ACTIVE? - OK =1 - -This means that acquisition is in progress. You can stop it by disarming:: - - < *PCAP.DISARM= - OK - < PCAP.ACTIVE? - OK =0 - -Conclusion ----------- - -This tutorial has shown how to start and stop an acquisition from the commandline -client. It can also be used to send any other control commands_ to query and set -variables on the PandA. - -.. _commands: https://pandablocks-server.readthedocs.io/en/latest/commands.html diff --git a/docs/tutorials/load-save.rst b/docs/tutorials/load-save.md similarity index 51% rename from docs/tutorials/load-save.rst rename to docs/tutorials/load-save.md index 5134c40f..1147cc06 100644 --- a/docs/tutorials/load-save.rst +++ b/docs/tutorials/load-save.md @@ -1,19 +1,19 @@ -.. _tutorial-load-save: +(tutorial-load-save)= -Commandline Load/Save Tutorial -============================== +# Commandline Load/Save Tutorial This tutorial shows how to use the commandline tool to save the state of all the Blocks and Fields in a PandA, and load a new state from file. It assumes that you know the basic concepts of a PandA as outlined in the PandABlocks-FPGA -blinking LEDs tutorial_. +blinking LEDs [tutorial]. -Save ----- +## Save -You can save the current state using the save command as follows:: +You can save the current state using the save command as follows: - $ pandablocks save +``` +$ pandablocks save +``` The save file is a text file containing the sequence of pandablocks control commands that will set up the PandA to match its state at the time of the save. @@ -22,35 +22,40 @@ fields. e.g. the first few lines of the tutorial save file look like this: -.. literalinclude:: ../../../src/pandablocks/saves/tutorial.sav - :lines: 1-12 +```{literalinclude} ../../src/pandablocks/saves/tutorial.sav +:lines: 1-12 +``` -Load ----- +## Load -To restore a PandA to a previously saved state use the load command as follows:: +To restore a PandA to a previously saved state use the load command as follows: - $ pandablocks load +``` +$ pandablocks load +``` -This is equivalent to typing the sequence of commands in into the +This is equivalent to typing the sequence of commands in \ into the pandablocks control command line. -To load the preconfigured tutorial state:: +To load the preconfigured tutorial state: - $ pandablocks load --tutorial +``` +$ pandablocks load --tutorial +``` The tutorial sets up a Seqencer block driving 3 Counter blocks and a Position Capture block. This configuration is the starting point for the next tutorial: -:ref:`commandline-hdf` +{ref}`commandline-hdf` -.. note:: - - The Web UI will not change the Blocks visible on the screen when you use - ``pandablocks load``. If you want all the connected Blocks to appear in the - UI then restart the services on the PandA (Admin > System > Reboot/Restart) +:::{note} +The Web UI will not change the Blocks visible on the screen when you use +`pandablocks load`. If you want all the connected Blocks to appear in the +UI then restart the services on the PandA (Admin > System > Reboot/Restart) +::: The tutorial blocks are wired up as shown in the following Web UI layout. -.. image:: tutorial_layout.png +```{image} tutorial_layout.png +``` -.. _tutorial: https://pandablocks-fpga.readthedocs.io/en/latest/tutorials/tutorial1_blinking_leds.html +[tutorial]: https://pandablocks-fpga.readthedocs.io/en/latest/tutorials/tutorial1_blinking_leds.html diff --git a/src/pandablocks/commands.py b/src/pandablocks/commands.py index 559a3f45..e387d2bc 100644 --- a/src/pandablocks/commands.py +++ b/src/pandablocks/commands.py @@ -92,36 +92,31 @@ class CommandException(Exception): # zip() because typing does not support variadic type variables. See # typeshed PR #1550 for discussion. @overload -def _execute_commands(c1: Command[T]) -> ExchangeGenerator[Tuple[T]]: - ... +def _execute_commands(c1: Command[T]) -> ExchangeGenerator[Tuple[T]]: ... @overload def _execute_commands( c1: Command[T], c2: Command[T2] -) -> ExchangeGenerator[Tuple[T, T2]]: - ... +) -> ExchangeGenerator[Tuple[T, T2]]: ... @overload def _execute_commands( c1: Command[T], c2: Command[T2], c3: Command[T3] -) -> ExchangeGenerator[Tuple[T, T2, T3]]: - ... +) -> ExchangeGenerator[Tuple[T, T2, T3]]: ... @overload def _execute_commands( c1: Command[T], c2: Command[T2], c3: Command[T3], c4: Command[T4] -) -> ExchangeGenerator[Tuple[T, T2, T3, T4]]: - ... +) -> ExchangeGenerator[Tuple[T, T2, T3, T4]]: ... @overload def _execute_commands( *commands: Command[Any], -) -> ExchangeGenerator[Tuple[Any, ...]]: - ... +) -> ExchangeGenerator[Tuple[Any, ...]]: ... def _execute_commands(*commands): diff --git a/src/pandablocks/connections.py b/src/pandablocks/connections.py index 9645e72a..0e2cc829 100644 --- a/src/pandablocks/connections.py +++ b/src/pandablocks/connections.py @@ -105,8 +105,8 @@ def __iter__(self): def __next__(self) -> bytes: try: return self.read_line() - except NeedMoreData: - raise StopIteration() + except NeedMoreData as err: + raise StopIteration() from err @dataclass diff --git a/tests/test_asyncio.py b/tests/test_asyncio.py index 9e3902a8..fd1881ed 100644 --- a/tests/test_asyncio.py +++ b/tests/test_asyncio.py @@ -54,7 +54,7 @@ async def test_asyncio_data_timeout(dummy_server_async, fast_dump): dummy_server_async.data = fast_dump async with AsyncioClient("localhost") as client: with pytest.raises(asyncio.TimeoutError, match="No data received for 0.1s"): - async for data in client.data(frame_timeout=0.1): + async for _ in client.data(frame_timeout=0.1): "This goes forever, when it runs out of data we will get our timeout" diff --git a/tests/test_cli.py b/tests/test_cli.py index e8559b46..1e1ed9c2 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -73,8 +73,8 @@ def __call__(self, prompt): assert prompt == cli.PROMPT try: return self._commands.popleft() - except IndexError: - raise EOFError() + except IndexError as err: + raise EOFError() from err def test_interactive_simple(dummy_server_in_thread, capsys):