diff --git a/.copier-answers.yml b/.copier-answers.yml new file mode 100644 index 0000000000..77a40d2c61 --- /dev/null +++ b/.copier-answers.yml @@ -0,0 +1,15 @@ +# Changes here will be overwritten by Copier +_commit: 1.0.0 +_src_path: gh:DiamondLightSource/python-copier-template +author_email: tom.cobb@diamond.ac.uk +author_name: Tom Cobb +component_owner: '' +description: Asynchronous Bluesky hardware abstraction code, compatible with control + systems like EPICS and Tango +distribution_name: ophyd-async +docker: false +docs_type: sphinx +git_platform: github.com +github_org: bluesky +package_name: ophyd_async +repo_name: ophyd-async diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 3536f89164..44de8d36af 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,44 +1,54 @@ // For format details, see https://containers.dev/implementors/json_reference/ { - "name": "Python 3 Developer Container", - "build": { - "dockerfile": "Dockerfile", - "target": "build", - // Only upgrade pip, we will install the project below - "args": { - "PIP_OPTIONS": "--upgrade pip" - } - }, - "remoteEnv": { - "DISPLAY": "${localEnv:DISPLAY}" - }, - "customizations": { - "vscode": { - // Set *default* container specific settings.json values on container create. - // "settings": { - // "python.defaultInterpreterPath": "/venv/bin/python" - // }, - // Add the IDs of extensions you want installed when the container is created. - "extensions": [ - "ms-python.python", - "tamasfe.even-better-toml", - "redhat.vscode-yaml", - "ryanluker.vscode-coverage-gutters" - ] - } - }, - // Make sure the files we are mapping into the container exist on the host - "initializeCommand": "bash -c 'for i in $HOME/.inputrc; do [ -f $i ] || touch $i; done'", - "runArgs": ["--net=host", "--security-opt=label=type:container_runtime_t"], - "mounts": [ - "source=${localEnv:HOME}/.ssh,target=/root/.ssh,type=bind", - "source=${localEnv:HOME}/.inputrc,target=/root/.inputrc,type=bind", - // map in home directory - not strictly necessary but useful - "source=${localEnv:HOME},target=${localEnv:HOME},type=bind,consistency=cached" - ], - // make the workspace folder the same inside and outside of the container - "workspaceMount": "source=${localWorkspaceFolder},target=${localWorkspaceFolder},type=bind", - "workspaceFolder": "${localWorkspaceFolder}", - // After the container is created, install the python project in editable form - "postCreateCommand": "pip install -e .[dev] --config-settings editable_mode=compat" + "name": "Python 3 Developer Container", + "build": { + "dockerfile": "../Dockerfile", + "target": "build", + // Only upgrade pip, we will install the project below + "args": { + "PIP_OPTIONS": "--upgrade pip" + } + }, + "remoteEnv": { + "DISPLAY": "${localEnv:DISPLAY}" + }, + // Add the URLs of features you want added when the container is built. + "features": { + "ghcr.io/devcontainers/features/common-utils:1": { + "username": "none", + "upgradePackages": false + } + }, + // Set *default* container specific settings.json values on container create. + "settings": { + "python.defaultInterpreterPath": "/venv/bin/python" + }, + "customizations": { + "vscode": { + // Add the IDs of extensions you want installed when the container is created. + "extensions": [ + "ms-python.python", + "tamasfe.even-better-toml", + "redhat.vscode-yaml", + "ryanluker.vscode-coverage-gutters" + ] + } + }, + // Make sure the files we are mapping into the container exist on the host + "initializeCommand": "bash -c 'for i in $HOME/.inputrc; do [ -f $i ] || touch $i; done'", + "runArgs": [ + "--net=host", + "--security-opt=label=type:container_runtime_t" + ], + "mounts": [ + "source=${localEnv:HOME}/.ssh,target=/root/.ssh,type=bind", + "source=${localEnv:HOME}/.inputrc,target=/root/.inputrc,type=bind", + // map in home directory - not strictly necessary but useful + "source=${localEnv:HOME},target=${localEnv:HOME},type=bind,consistency=cached" + ], + // make the workspace folder the same inside and outside of the container + "workspaceMount": "source=${localWorkspaceFolder},target=${localWorkspaceFolder},type=bind", + "workspaceFolder": "${localWorkspaceFolder}", + // After the container is created, install the python project in editable form + "postCreateCommand": "pip install -e '.[dev]'" } diff --git a/.github/CONTRIBUTING.rst b/.github/CONTRIBUTING.rst index fe19bae11b..36ea1e20b3 100644 --- a/.github/CONTRIBUTING.rst +++ b/.github/CONTRIBUTING.rst @@ -24,6 +24,7 @@ Code coverage While 100% code coverage does not make a library bug-free, it significantly reduces the number of easily caught bugs! Please make sure coverage remains the same or is improved by a pull request! + Developer guide --------------- diff --git a/.github/actions/install_requirements/action.yml b/.github/actions/install_requirements/action.yml index 84be1b910f..79d1a71eef 100644 --- a/.github/actions/install_requirements/action.yml +++ b/.github/actions/install_requirements/action.yml @@ -7,16 +7,19 @@ inputs: install_options: description: Parameters to pass to pip install required: true + artifact_name: + description: A user friendly name to give the produced artifacts + required: true python_version: description: Python version to install - default: "3.10" + default: "3.x" runs: using: composite steps: - name: Setup python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ inputs.python_version }} @@ -36,9 +39,9 @@ runs: shell: bash - name: Upload lockfiles - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4.0.0 with: - name: lockfiles + name: lockfiles-${{ inputs.python_version }}-${{ inputs.artifact_name }}-${{ github.sha }} path: lockfiles # This eliminates the class of problems where the requirements being given no @@ -55,4 +58,3 @@ runs: fi fi shell: bash - diff --git a/.github/dependabot.yml b/.github/dependabot.yml index fb7c6ee671..2d1af8738d 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -9,6 +9,10 @@ updates: directory: "/" schedule: interval: "weekly" + groups: + github-artifacts: + patterns: + - actions/*-artifact - package-ecosystem: "pip" directory: "/" diff --git a/.github/pages/index.html b/.github/pages/index.html index 80f0a00912..c495f39f2f 100644 --- a/.github/pages/index.html +++ b/.github/pages/index.html @@ -8,4 +8,4 @@ - \ No newline at end of file + diff --git a/.github/pages/make_switcher.py b/.github/pages/make_switcher.py index d70367aec5..ae227ab7fd 100755 --- a/.github/pages/make_switcher.py +++ b/.github/pages/make_switcher.py @@ -59,7 +59,7 @@ def get_versions(ref: str, add: Optional[str], remove: Optional[str]) -> List[st 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/code.yml b/.github/workflows/code.yml index 41381549fb..3753400f88 100644 --- a/.github/workflows/code.yml +++ b/.github/workflows/code.yml @@ -3,6 +3,10 @@ name: Code CI on: push: pull_request: +env: + # The target python version, which must match the Dockerfile version + CONTAINER_PYTHON: "3.11" + DIST_WHEEL_PATH: dist-${{ github.sha }} jobs: lint: @@ -19,6 +23,7 @@ jobs: with: requirements_file: requirements-dev-3.x.txt install_options: -e .[dev] + artifact_name: lint - name: Lint run: tox -e pre-commit,mypy @@ -55,6 +60,7 @@ jobs: python_version: ${{ matrix.python }} requirements_file: requirements-test-${{ matrix.os }}-${{ matrix.python }}.txt install_options: ${{ matrix.install }} + artifact_name: tests - name: List dependency tree run: pipdeptree @@ -63,12 +69,10 @@ jobs: run: tox -e pytest - name: Upload coverage to Codecov - uses: codecov/codecov-action@v4 + uses: codecov/codecov-action@v3 with: name: ${{ matrix.python }}/${{ matrix.os }} files: cov.xml - env: - CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} dist: if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository @@ -87,9 +91,9 @@ jobs: pipx run build - name: Upload sdist and wheel as artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4.0.0 with: - name: dist + name: ${{ env.DIST_WHEEL_PATH }} path: dist - name: Check for packaging errors @@ -98,31 +102,126 @@ jobs: - name: Install python packages uses: ./.github/actions/install_requirements with: - python_version: "3.11" + python_version: ${{env.CONTAINER_PYTHON}} requirements_file: requirements.txt install_options: dist/*.whl + artifact_name: dist - name: Test module --version works using the installed wheel # If more than one module in src/ replace with module name to test - run: python -m $(ls src | head -1) --version + run: python -m $(ls --hide='*.egg-info' src | head -1) --version + + container: + needs: [lint, dist, test] + runs-on: ubuntu-latest + + permissions: + contents: read + packages: write + + env: + TEST_TAG: "testing" + + steps: + - name: Checkout + uses: actions/checkout@v4 + + # image names must be all lower case + - name: Generate image repo name + run: echo IMAGE_REPOSITORY=ghcr.io/$(tr '[:upper:]' '[:lower:]' <<< "${{ github.repository }}") >> $GITHUB_ENV + + - name: Set lockfile location in environment + run: | + echo "DIST_LOCKFILE_PATH=lockfiles-${{ env.CONTAINER_PYTHON }}-dist-${{ github.sha }}" >> $GITHUB_ENV + + - name: Download wheel and lockfiles + uses: actions/download-artifact@v4.1.0 + with: + path: artifacts/ + pattern: "*dist*" + + - name: Log in to GitHub Docker Registry + if: github.event_name != 'pull_request' + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Set up Docker Buildx + id: buildx + uses: docker/setup-buildx-action@v3 + + - name: Build and export to Docker local cache + uses: docker/build-push-action@v5 + with: + # Note build-args, context, file, and target must all match between this + # step and the later build-push-action, otherwise the second build-push-action + # will attempt to build the image again + build-args: | + PIP_OPTIONS=-r ${{ env.DIST_LOCKFILE_PATH }}/requirements.txt ${{ env.DIST_WHEEL_PATH }}/*.whl + context: artifacts/ + file: ./Dockerfile + target: runtime + load: true + tags: ${{ env.TEST_TAG }} + # If you have a long docker build (2+ minutes), uncomment the + # following to turn on caching. For short build times this + # makes it a little slower + #cache-from: type=gha + #cache-to: type=gha,mode=max + + - name: Test cli works in cached runtime image + run: docker run docker.io/library/${{ env.TEST_TAG }} --version + + - name: Create tags for publishing image + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.IMAGE_REPOSITORY }} + tags: | + type=ref,event=tag + type=raw,value=latest, enable=${{ github.ref_type == 'tag' }} + # type=edge,branch=main + # Add line above to generate image for every commit to given branch, + # and uncomment the end of if clause in next step + + - name: Push cached image to container registry + if: github.ref_type == 'tag' # || github.ref_name == 'main' + uses: docker/build-push-action@v5 + # This does not build the image again, it will find the image in the + # Docker cache and publish it + with: + # Note build-args, context, file, and target must all match between this + # step and the previous build-push-action, otherwise this step will + # attempt to build the image again + build-args: | + PIP_OPTIONS=-r ${{ env.DIST_LOCKFILE_PATH }}/requirements.txt ${{ env.DIST_WHEEL_PATH }}/*.whl + context: artifacts/ + file: ./Dockerfile + target: runtime + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} release: # upload to PyPI and make a release on every tag needs: [lint, dist, test] - if: ${{ github.event_name == 'push' && startsWith(github.ref, 'refs/tags') }} + if: ${{ github.event_name == 'push' && github.ref_type == 'tag' }} runs-on: ubuntu-latest - permissions: - id-token: write - contents: write env: HAS_PYPI_TOKEN: ${{ secrets.PYPI_TOKEN != '' }} steps: - - uses: actions/download-artifact@v3 + - name: Download wheel and lockfiles + uses: actions/download-artifact@v4.1.0 + with: + path: artifacts/ + pattern: "*dist*" - name: Fixup blank lockfiles # Github release artifacts can't be blank - run: for f in lockfiles/*; do [ -s $f ] || echo '# No requirements' >> $f; done + run: for f in ${{ env.DIST_LOCKFILE_PATH }}/*; do [ -s $f ] || echo '# No requirements' >> $f; done - name: Github Release # We pin to the SHA, not the tag, for security reasons. @@ -131,13 +230,14 @@ jobs: with: prerelease: ${{ contains(github.ref_name, 'a') || contains(github.ref_name, 'b') || contains(github.ref_name, 'rc') }} files: | - dist/* - lockfiles/* + ${{ env.DIST_WHEEL_PATH }}/* + ${{ env.DIST_LOCKFILE_PATH }}/* generate_release_notes: true env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Publish wheels to PyPI + - name: Publish to PyPI + if: ${{ env.HAS_PYPI_TOKEN }} uses: pypa/gh-action-pypi-publish@release/v1 with: - packages-dir: ./dist/ + password: ${{ secrets.PYPI_TOKEN }} diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 1d8a687a98..3c29ff94d9 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -29,6 +29,7 @@ jobs: with: requirements_file: requirements-dev-3.x.txt install_options: -e .[dev] + artifact_name: docs - name: Build docs run: tox -e docs @@ -46,7 +47,7 @@ jobs: if: github.event_name == 'push' && github.actor != 'dependabot[bot]' # We pin to the SHA, not the tag, for security reasons. # https://docs.github.com/en/actions/learn-github-actions/security-hardening-for-github-actions#using-third-party-actions - uses: peaceiris/actions-gh-pages@373f7f263a76c20808c831209c920827a82a2847 # v3.9.3 + uses: peaceiris/actions-gh-pages@64b46b4226a4a12da2239ba3ea5aa73e3163c75b # v3.9.1 with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: .github/pages diff --git a/.github/workflows/linkcheck.yml b/.github/workflows/linkcheck.yml index 3b24af5584..7f651a270b 100644 --- a/.github/workflows/linkcheck.yml +++ b/.github/workflows/linkcheck.yml @@ -19,9 +19,10 @@ jobs: with: requirements_file: requirements-dev-3.x.txt install_options: -e .[dev] + artifact_name: link_check - name: Check links run: tox -e docs build -- -b linkcheck - name: Keepalive Workflow - uses: gautamkrishnar/keepalive-workflow@v1 \ No newline at end of file + uses: gautamkrishnar/keepalive-workflow@v1 diff --git a/.gitignore b/.gitignore index 1ce3f7a583..6cacaedbfc 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ __pycache__/ # Distribution / packaging .Python env/ +.venv build/ develop-eggs/ dist/ @@ -66,7 +67,7 @@ venv* # further build artifacts lockfiles/ -# Origional File +# Original File *.pyc *.swp *.bak @@ -85,3 +86,7 @@ docs/savefig # generated version number ophyd_async/_version.py + + +# ruff cache +.ruff_cache/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index aa2a4cb2c8..5bc9f001c0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -15,9 +15,9 @@ repos: entry: black --check --diff types: [python] - - id: flake8 - name: Run flake8 + - id: ruff + name: Run ruff stages: [commit] language: system - entry: flake8 + entry: ruff types: [python] diff --git a/README.rst b/README.rst index fdb3d76136..a5173f79e8 100644 --- a/README.rst +++ b/README.rst @@ -1,5 +1,5 @@ -Ophyd Async -=========== +ophyd_async +============================================================================= |code_ci| |docs_ci| |coverage| |pypi_version| |license| @@ -9,6 +9,7 @@ Asynchronous device abstraction framework, building on `Ophyd`_. PyPI ``pip install ophyd-async`` Source code https://github.com/bluesky/ophyd-async Documentation https://blueskyproject.io/ophyd-async +Releases https://github.com/bluesky/ophyd-async/releases ============== ============================================================== Python library for asynchronously interfacing with hardware, intended to @@ -43,7 +44,7 @@ See the tutorials for usage examples. :target: https://github.com/bluesky/ophyd-async/actions/workflows/docs.yml :alt: Docs CI -.. |coverage| image:: https://codecov.io/gh/bluesky/ophyd-async/branch/master/graph/badge.svg +.. |coverage| image:: https://codecov.io/gh/bluesky/ophyd-async/branch/main/graph/badge.svg :target: https://codecov.io/gh/bluesky/ophyd-async :alt: Test Coverage diff --git a/docs/conf.py b/docs/conf.py index 4a0b72fb7f..a69ab5d272 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -4,7 +4,6 @@ # list see the documentation: # https://www.sphinx-doc.org/en/master/usage/configuration.html -import os import sys from pathlib import Path from subprocess import check_output @@ -14,8 +13,6 @@ import ophyd_async # -- General configuration ------------------------------------------------ -# Source code dir relative to this file -sys.path.insert(0, os.path.abspath("../../src")) # General information about the project. project = "ophyd-async" @@ -125,14 +122,13 @@ } # A dictionary of graphviz graph attributes for inheritance diagrams. -inheritance_graph_attrs = dict(rankdir="TB") +inheritance_graph_attrs = {"rankdir": "TB"} # Common links that should be available on every page rst_epilog = """ .. _NSLS: https://www.bnl.gov/nsls2 .. _black: https://github.com/psf/black -.. _flake8: https://flake8.pycqa.org/en/latest/ -.. _isort: https://github.com/PyCQA/isort +.. _ruff: https://beta.ruff.rs/docs/ .. _mypy: http://mypy-lang.org/ .. _pre-commit: https://pre-commit.com/ """ @@ -151,12 +147,11 @@ # a list of builtin themes. # html_theme = "pydata_sphinx_theme" -github_repo = project +github_repo = "ophyd-async" github_user = "bluesky" switcher_json = f"https://{github_user}.github.io/{github_repo}/switcher.json" -# Don't check switcher if it doesn't exist, but warn in a non-failing way -check_switcher = requests.get(switcher_json).ok -if not check_switcher: +switcher_exists = requests.get(switcher_json).ok +if not switcher_exists: print( "*** Can't read version switcher, is GitHub pages enabled? \n" " Once Docs CI job has successfully run once, set the " @@ -166,41 +161,58 @@ ) # Theme options for pydata_sphinx_theme -html_theme_options = dict( - use_edit_page_button=True, - github_url=f"https://github.com/{github_user}/{github_repo}", - icon_links=[ - dict( - name="PyPI", - url=f"https://pypi.org/project/{project}", - icon="fas fa-cube", - ), - dict( - name="Gitter", - url="https://gitter.im/NSLS-II/DAMA", - icon="fas fa-person-circle-question", - ), +# We don't check switcher because there are 3 possible states for a repo: +# 1. New project, docs are not published so there is no switcher +# 2. Existing project with latest skeleton, switcher exists and works +# 3. Existing project with old skeleton that makes broken switcher, +# switcher exists but is broken +# Point 3 makes checking switcher difficult, because the updated skeleton +# will fix the switcher at the end of the docs workflow, but never gets a chance +# to complete as the docs build warns and fails. +html_theme_options = { + "logo": { + "text": project, + }, + "use_edit_page_button": True, + "github_url": f"https://github.com/{github_user}/{github_repo}", + "icon_links": [ + { + "name": "PyPI", + "url": f"https://pypi.org/project/{project}", + "icon": "fas fa-cube", + }, + { + "name": "Gitter", + "url": "https://gitter.im/NSLS-II/DAMA", + "icon": "fas fa-person-circle-question", + }, ], - external_links=[ - dict( - name="Bluesky Project", - url="https://blueskyproject.io", - ) + "switcher": { + "json_url": switcher_json, + "version_match": version, + }, + "check_switcher": False, + "navbar_end": ["theme-switcher", "icon-links", "version-switcher"], + "external_links": [ + { + "name": "Bluesky Project", + "url": "https://blueskyproject.io", + }, + { + "name": "Release Notes", + "url": f"https://github.com/{github_user}/{github_repo}/releases", + }, ], - navigation_with_keys=False, -) - + "navigation_with_keys": False, +} # A dictionary of values to pass into the template engine’s context for all pages -html_context = dict( - github_user=github_user, - github_repo=project, - github_version="master", - doc_path="docs", -) - -html_logo = "images/bluesky_ophyd_logo.svg" -html_favicon = "images/ophyd_favicon.svg" +html_context = { + "github_user": github_user, + "github_repo": project, + "github_version": version, + "doc_path": "docs", +} # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. html_show_sphinx = False @@ -208,6 +220,10 @@ # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. html_show_copyright = False +# Logo +html_logo = "images/bluesky_ophyd_logo.svg" +html_favicon = "images/ophyd_favicon.svg" + # If False and a module has the __all__ attribute set, autosummary documents # every member listed in __all__ and no others. Default is True autosummary_ignore_module_all = False diff --git a/docs/developer/explanations/decisions/0002-switched-to-pip-skeleton.rst b/docs/developer/explanations/decisions/0002-switched-to-pip-skeleton.rst index d0cd738402..33d5698143 100644 --- a/docs/developer/explanations/decisions/0002-switched-to-pip-skeleton.rst +++ b/docs/developer/explanations/decisions/0002-switched-to-pip-skeleton.rst @@ -1,5 +1,5 @@ -2. Adopt ophyd-async for project structure -========================================== +2. Adopt python_copier_template for project structure +===================================================== Date: 2022-02-18 @@ -11,8 +11,8 @@ Accepted Context ------- -We should use the following `pip-skeleton `_. -The skeleton will ensure consistency in developer +We should use the following `python_copier_template `_. +The template will ensure consistency in developer environments and package management. Decision @@ -23,8 +23,8 @@ We have switched to using the skeleton. Consequences ------------ -This module will use a fixed set of tools as developed in ophyd-async -and can pull from this skeleton to update the packaging to the latest techniques. +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: diff --git a/docs/developer/how-to/build-docs.rst b/docs/developer/how-to/build-docs.rst index 0174fc82dd..11a5e6386d 100644 --- a/docs/developer/how-to/build-docs.rst +++ b/docs/developer/how-to/build-docs.rst @@ -35,4 +35,4 @@ changes in this directory too:: $ tox -e docs autobuild -- --watch src -.. _sphinx: https://www.sphinx-doc.org/ \ No newline at end of file +.. _sphinx: https://www.sphinx-doc.org/ diff --git a/docs/developer/how-to/lint.rst b/docs/developer/how-to/lint.rst index 8f4e92dbbf..2df258d8fc 100644 --- a/docs/developer/how-to/lint.rst +++ b/docs/developer/how-to/lint.rst @@ -1,7 +1,7 @@ Run linting using pre-commit ============================ -Code linting is handled by black_, flake8_ and isort_ run under pre-commit_. +Code linting is handled by black_ and ruff_ run under pre-commit_. Running pre-commit ------------------ @@ -26,16 +26,14 @@ repository:: $ black . -Likewise with isort:: +Likewise with ruff:: - $ isort . + $ ruff --fix . -If you get any flake8 issues you will have to fix those manually. +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 and isort formatters as well as -flake8 checking on save. Issues will be highlighted in the editor window. - - +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/pin-requirements.rst b/docs/developer/how-to/pin-requirements.rst index 278bdeed18..91ed5535d0 100644 --- a/docs/developer/how-to/pin-requirements.rst +++ b/docs/developer/how-to/pin-requirements.rst @@ -32,7 +32,7 @@ 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/bluesky/python3-pip-skeleton-cli/releases/tag/3.3.0 +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. diff --git a/docs/developer/how-to/update-tools.rst b/docs/developer/how-to/update-tools.rst index 7c78f94a65..c1075ee8c1 100644 --- a/docs/developer/how-to/update-tools.rst +++ b/docs/developer/how-to/update-tools.rst @@ -6,11 +6,11 @@ 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/bluesky/python3-pip-skeleton + $ 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://blueskyproject.io/python3-pip-skeleton +.. _python3-pip-skeleton: https://DiamondLightSource.github.io/python3-pip-skeleton diff --git a/docs/developer/reference/standards.rst b/docs/developer/reference/standards.rst index a5eb405ffb..5a1fd47825 100644 --- a/docs/developer/reference/standards.rst +++ b/docs/developer/reference/standards.rst @@ -10,8 +10,7 @@ Code Standards 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 +- ruff_ for style checks - mypy_ for static type checking .. seealso:: @@ -24,104 +23,26 @@ Documentation Standards ----------------------- Docstrings are pre-processed using the Sphinx Napoleon extension. As such, -numpydoc-style_ is considered as standard for this repository. Please use type +google-style_ is considered as standard for this repository. Please use type hints in the function signature for types. For example: .. code:: python - def foo(var1, var2, *args, long_var_name="hi", only_seldom_used_keyword=0, **kwargs): - r"""Summarize the function in one line. - - Several sentences providing an extended description. Refer to - variables using back-ticks, e.g. `var`. - - Parameters - ---------- - var1 : array_like - Array_like means all those objects -- lists, nested lists, etc. -- - that can be converted to an array. We can also refer to - variables like `var1`. - var2 : int - The type above can either refer to an actual Python type - (e.g. ``int``), or describe the type of the variable in more - detail, e.g. ``(N,) ndarray`` or ``array_like``. - *args : iterable - Other arguments. - long_var_name : {'hi', 'ho'}, optional - Choices in brackets, default first when optional. - - Returns - ------- - type - Explanation of anonymous return value of type ``type``. - describe : type - Explanation of return value named `describe`. - out : type - Explanation of `out`. - type_without_description - - Other Parameters - ---------------- - only_seldom_used_keyword : int, optional - Infrequently used parameters can be described under this optional - section to prevent cluttering the Parameters section. - **kwargs : dict - Other infrequently used keyword arguments. Note that all keyword - arguments appearing after the first parameter specified under the - Other Parameters section, should also be described under this - section. - - Raises - ------ - BadException - Because you shouldn't have done that. - - See Also - -------- - numpy.array : Relationship (optional). - numpy.ndarray : Relationship (optional), which could be fairly long, in - which case the line wraps here. - numpy.dot, numpy.linalg.norm, numpy.eye - - Notes - ----- - Notes about the implementation algorithm (if needed). - - This can have multiple paragraphs. - - You may include some math: - - .. math:: X(e^{j\omega } ) = x(n)e^{ - j\omega n} - - And even use a Greek symbol like :math:`\omega` inline. - - References - ---------- - Cite the relevant literature, e.g. [1]_. You may also cite these - references in the notes section above. - - .. [1] O. McNoleg, "The integration of GIS, remote sensing, - expert systems and adaptive co-kriging for environmental habitat - modelling of the Highland Haggis using object-oriented, fuzzy-logic - and neural-network techniques," Computers & Geosciences, vol. 22, - pp. 585-588, 1996. - - Examples - -------- - These are written in doctest format, and should illustrate how to - use the function. - - >>> a = [1, 2, 3] - >>> print([x + 3 for x in a]) - [4, 5, 6] - >>> print("a\nb") - a - b - - """ - pass - -.. _numpydoc-style: https://numpydoc.readthedocs.io/en/latest/format.html + 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. @@ -139,4 +60,4 @@ Docs follow the underlining convention:: .. seealso:: - How-to guide `../how-to/build-docs` \ No newline at end of file + How-to guide `../how-to/build-docs` diff --git a/docs/developer/tutorials/dev-install.rst b/docs/developer/tutorials/dev-install.rst index 0a32bd19a2..7d696f870e 100644 --- a/docs/developer/tutorials/dev-install.rst +++ b/docs/developer/tutorials/dev-install.rst @@ -34,10 +34,15 @@ requires python 3.10 or later) or to run in a container under `VSCode .. code:: - $ vscode ophyd-async + $ code ophyd-async # 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 ---------------------- @@ -58,3 +63,6 @@ This will run in parallel the following checks: - `../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/user/how-to/run-container.rst b/docs/user/how-to/run-container.rst index 2639fb919e..14261a1efb 100644 --- a/docs/user/how-to/run-container.rst +++ b/docs/user/how-to/run-container.rst @@ -1,9 +1,9 @@ Run in a container ================== -Pre-built containers with ophyd-epics-devices and its dependencies already +Pre-built containers with ophyd-async and its dependencies already installed are available on `Github Container Registry -`_. +`_. Starting the container ---------------------- diff --git a/pyproject.toml b/pyproject.toml index e007a4435c..33962e4221 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,7 +22,6 @@ dependencies = [ "p4p", "pyyaml", ] - dynamic = ["version"] license.file = "LICENSE" readme = "README.rst" @@ -43,7 +42,6 @@ dev = [ "ipython", "ipywidgets", "matplotlib", - "mypy", "myst-parser", "numpydoc", "ophyd", @@ -59,6 +57,7 @@ dev = [ "pytest-faulthandler", "pytest-rerunfailures", "pytest-timeout", + "ruff", "sphinx-autobuild", "sphinx-copybutton", "sphinx-design", @@ -82,33 +81,15 @@ name = "Tom Cobb" write_to = "src/ophyd_async/_version.py" [tool.mypy] -ignore_missing_imports = true # Ignore missing stubs in imported modules -plugins = ["numpy.typing.mypy_plugin"] - -[tool.isort] -float_to_top = true -profile = "black" - -[tool.flake8] -extend-ignore = [ - "E203", # See https://github.com/PyCQA/pycodestyle/issues/373 - "F811", # support typing.overload decorator - "F722", # allow Annotated[typ, some_func("some string")] - "W504", - "W503", -] -max-line-length = 88 # Respect black's line length (default 88), -exclude = [".tox", "venv"] +ignore_missing_imports = true # Ignore missing stubs in imported modules [tool.pytest.ini_options] # Run pytest with all our checkers, and don't spam us with massive tracebacks on error addopts = """ - --tb=native -vv --strict-markers --doctest-modules --doctest-glob="*.rst" - --ignore=docs/user/examples --ignore=docs/examples - --cov=src/ophyd_async --cov-report term --cov-report xml:cov.xml -""" + --tb=native -vv --doctest-modules --doctest-glob="*.rst" + """ # https://iscinumpy.gitlab.io/post/bound-version-constraints/#watch-for-warnings -filterwarnings = ["error", "ignore::DeprecationWarning:pkg_resources"] +filterwarnings = "error" # Doctest python code in docs, python code in src docstrings, test functions in tests testpaths = "docs src tests" log_format = "%(asctime)s,%(msecs)03d %(levelname)s (%(threadName)s) %(message)s" @@ -138,8 +119,8 @@ skipsdist=True # Don't create a virtualenv for the command, requires tox-direct plugin direct = True passenv = * -allowlist_externals = - pytest +allowlist_externals = + pytest pre-commit mypy sphinx-build @@ -150,3 +131,15 @@ commands = pre-commit: pre-commit run --all-files {posargs} docs: sphinx-{posargs:build -EW --keep-going} -T docs build/html """ + + +[tool.ruff] +src = ["src", "tests"] +line-length = 88 +select = [ + "C4", # flake8-comprehensions - https://beta.ruff.rs/docs/rules/#flake8-comprehensions-c4 + "E", # pycodestyle errors - https://beta.ruff.rs/docs/rules/#error-e + "F", # pyflakes rules - https://beta.ruff.rs/docs/rules/#pyflakes-f + "W", # pycodestyle warnings - https://beta.ruff.rs/docs/rules/#warning-w + "I001", # isort +] diff --git a/src/ophyd_async/__init__.py b/src/ophyd_async/__init__.py index 4100754a36..ea76f482f4 100644 --- a/src/ophyd_async/__init__.py +++ b/src/ophyd_async/__init__.py @@ -1,4 +1,9 @@ -from importlib.metadata import version # noqa +import sys + +if sys.version_info < (3, 8): + from importlib_metadata import version # noqa +else: + from importlib.metadata import version # noqa __version__ = version("ophyd-async") del version