diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 8d4d7d26a..d029b25ec 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -2,7 +2,7 @@ Please include a summary of the changes and any links to related issues. Please also include relevant motivation and context. -Addresses #\< fill in issue number here > +Addresses #< fill in issue number here > ## Types of changes diff --git a/.github/workflows/package-build.yml b/.github/workflows/package-build.yml index 61f794b26..abb9cce45 100644 --- a/.github/workflows/package-build.yml +++ b/.github/workflows/package-build.yml @@ -15,7 +15,7 @@ jobs: uses: tektronix/python-package-ci-cd/.github/workflows/_reusable-package-build.yml@v1.7.4 with: package-name: tm_devices - python-versions-array: '["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]' # when updating this, make sure to update all workflows that use this strategy + python-versions-array: '["3.9", "3.10", "3.11", "3.12", "3.13"]' # when updating this, make sure to update all workflows that use this strategy operating-systems-array: '["ubuntu", "windows", "macos"]' permissions: contents: read diff --git a/.github/workflows/package-release.yml b/.github/workflows/package-release.yml index 36a6f257d..c5daa7c8e 100644 --- a/.github/workflows/package-release.yml +++ b/.github/workflows/package-release.yml @@ -24,7 +24,7 @@ jobs: commit-user-email: ${{ vars.TEK_OPENSOURCE_EMAIL }} release-level: ${{ inputs.release-level }} build-and-publish-python-package: true - python-versions-array: '["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]' # when updating this, make sure to update all workflows that use this strategy + python-versions-array: '["3.9", "3.10", "3.11", "3.12", "3.13"]' # when updating this, make sure to update all workflows that use this strategy operating-systems-array: '["ubuntu", "windows", "macos"]' previous-changelog-filepath: python_semantic_release_templates/.previous_changelog_for_template.md previous-release-notes-filepath: python_semantic_release_templates/.previous_release_notes_for_template.md diff --git a/.github/workflows/test-code.yml b/.github/workflows/test-code.yml index 070592c43..9b53f5521 100644 --- a/.github/workflows/test-code.yml +++ b/.github/workflows/test-code.yml @@ -14,7 +14,7 @@ jobs: with: repo-name: tektronix/tm_devices operating-systems-array: '["ubuntu", "windows", "macos"]' - python-versions-array: '["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]' # when updating this, make sure to update all workflows that use this strategy + python-versions-array: '["3.9", "3.10", "3.11", "3.12", "3.13"]' # when updating this, make sure to update all workflows that use this strategy upload-to-codecov: true enable-retry-os-array: '["macos"]' secrets: diff --git a/.github/workflows/update-python-and-pre-commit-dependencies.yml b/.github/workflows/update-python-and-pre-commit-dependencies.yml index 6f8d5631a..4c8edd68f 100644 --- a/.github/workflows/update-python-and-pre-commit-dependencies.yml +++ b/.github/workflows/update-python-and-pre-commit-dependencies.yml @@ -14,7 +14,7 @@ jobs: update-pre-commit: true run-pre-commit: true pre-commit-hook-skip-list: pylint,pyright,pyright-verifytypes,pyroma,poetry-audit - pre-commit-repo-update-skip-list: https://github.com/executablebooks/mdformat,https://github.com/pappasam/toml-sort,https://github.com/python-jsonschema/check-jsonschema + pre-commit-repo-update-skip-list: '' export-dependency-groups: docs,tests permissions: contents: write diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 68105fe99..342def3e3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -39,7 +39,7 @@ repos: - id: remove-tabs - id: forbid-tabs - repo: https://github.com/python-jsonschema/check-jsonschema - rev: cb3c2be894b151dff143b1baf6acbd55f2b7faed # frozen: 0.30.0 + rev: 06e4cc849d03f3a59ca223a4046f4bb5bb2aba6d # frozen: 0.33.0 hooks: - id: check-readthedocs - id: check-dependabot @@ -65,7 +65,7 @@ repos: hooks: - id: curlylint - repo: https://github.com/executablebooks/mdformat - rev: 08fba30538869a440b5059de90af03e3502e35fb # frozen: 0.7.17 + rev: ff29be1a1ba8029d9375882aa2c812b62112a593 # frozen: 0.7.22 hooks: - id: mdformat args: [--number, --end-of-line, keep, --ignore-missing-references] @@ -91,7 +91,7 @@ repos: hooks: - id: check-poetry - repo: https://github.com/pappasam/toml-sort - rev: b9b6210da457c38122995e434b314f4c4a4a923e # frozen: v0.23.1 + rev: 4ec24891e200ae663aa2a7cecd19516080777133 # frozen: v0.24.2 hooks: - id: toml-sort-fix - repo: local @@ -139,9 +139,8 @@ repos: - id: ruff args: [--fix, --exit-non-zero-on-fix] - id: ruff-format - # TODO: Re-enable this once https://github.com/PyCQA/docformatter/issues/293 is resolved - # - repo: https://github.com/PyCQA/docformatter - # rev: dfefe062799848234b4cd60b04aa633c0608025e # frozen: v1.7.5 - # hooks: - # - id: docformatter - # additional_dependencies: [tomli] + - repo: https://github.com/PyCQA/docformatter + rev: 4cf1ea547d79f6e15ce51eae0eb5f986053ab65c # frozen: v1.7.6 + hooks: + - id: docformatter + additional_dependencies: [tomli] diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a117a187..8d406e27f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,10 @@ Valid subsections within a version are: Things to be included in the next release go here. +### Removed + +- Python 3.8 support has been removed from the package. The minimum supported version is now Python 3.9. + --- ## v3.2.0 (2025-05-07) @@ -50,7 +54,7 @@ Things to be included in the next release go here. ### Merged Pull Requests -- Set offset after impedance so that it is properly adjusted ([#411](https://github.com/tektronix/tm_devices/pull/411)) +- Set offset after impedance so that it is properly adjusted ([#411](https://github.com/tektronix/tm_devices/pull/411)) ### Fixed @@ -688,7 +692,7 @@ However, please read through all changes to be aware of what may potentially imp ### Merged Pull Requests - fix: Removed unused command files. ([#143](https://github.com/tektronix/tm_devices/issues/143)) -- fix: modified API under MSO 2,4,5,6 modules ([#142](https://github.com/tektronix/tm_devices/issues/142)) +- fix: modified API under MSO 2,4,5,6 modules ([#142](https://github.com/tektronix/tm_devices/issues/142)) - Update PI Device close method to catch VisaIOErrors ([#141](https://github.com/tektronix/tm_devices/issues/141)) - ci: Update pre-commit hooks and linter versions. ([#139](https://github.com/tektronix/tm_devices/issues/139)) - gh-actions(deps): Bump the gh-actions-dependencies group with 1 update ([#123](https://github.com/tektronix/tm_devices/issues/123)) diff --git a/docs/_templates/mkdocstrings/python/readthedocs/class.html.jinja b/docs/_templates/mkdocstrings/python/readthedocs/class.html.jinja index 42b2c3149..563f3c41a 100644 --- a/docs/_templates/mkdocstrings/python/readthedocs/class.html.jinja +++ b/docs/_templates/mkdocstrings/python/readthedocs/class.html.jinja @@ -1,4 +1,3 @@ -{# TODO: Drop Python 3.8: remove this file after updating to newer versions of mkdocstrings-python #} {% extends "_base/class.html.jinja" %} {% block inheritance_diagram %} {#- Inheritance diagram block. diff --git a/docs/configuration.md b/docs/configuration.md index 7aca3cd0d..7a74ce4eb 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -191,7 +191,7 @@ devices: - Different products may support ranges outside the commonly used rates listed here. - `data_bits:` The number of data bits in each character. - - One of \[5, 6, 7, 8\]. + - One of [5, 6, 7, 8]. - `flow_control:` Control for pausing/resuming data stream between slower devices. - Valid options: `none`, `xon_xoff`, `dtr_dsr`, or `rts_cts` diff --git a/docs/glossary.md b/docs/glossary.md index 4a5516071..f6a49b91b 100644 --- a/docs/glossary.md +++ b/docs/glossary.md @@ -33,7 +33,7 @@ DMM : Digital Multimeter DPOJET -: A jitter, noise, timing, and eye diagram analysis tool for Tektronix Performance Digital Oscilloscopes +: A jitter, noise, timing, and eye diagram analysis tool for Tektronix Performance Digital Oscilloscopes GPIB : General Purpose Interface Bus diff --git a/docs/macros.py b/docs/macros.py index ee89104a8..de1047e4e 100644 --- a/docs/macros.py +++ b/docs/macros.py @@ -6,8 +6,9 @@ import pathlib import re +from collections.abc import Generator from importlib import import_module -from typing import Any, Generator, Optional, Set, Tuple +from typing import Any, Optional, Set, Tuple import tomli diff --git a/docs/requirements.txt b/docs/requirements.txt index ff7bdae3d..6c3b6ea80 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,61 +1,59 @@ -astunparse==1.6.3 ; python_version >= "3.8" and python_version < "3.9" -beautifulsoup4==4.13.4 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -black==24.8.0 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -bracex==2.5.post1 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -certifi==2025.4.26 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -charset-normalizer==3.4.2 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -click==8.1.8 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -codespell==2.4.1 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -colorama==0.4.6 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -editdistpy==0.1.5 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -editorconfig==0.17.0 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -ghp-import==2.1.0 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -griffe==1.4.0 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -hjson==3.1.0 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -idna==3.10 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -importlib-metadata==8.5.0 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "3.10" -inflect==7.4.0 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -jinja2==3.1.6 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -jsbeautifier==1.15.4 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -markdown==3.7 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -markupsafe==2.1.5 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -mergedeep==1.3.4 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -mkdocs==1.6.1 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -mkdocs-autorefs==1.2.0 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -mkdocs-ezglossary-plugin==1.7.1 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -mkdocs-gen-files==0.5.0 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -mkdocs-get-deps==0.2.0 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -mkdocs-include-markdown-plugin==6.2.2 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -mkdocs-literate-nav==0.6.1 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -mkdocs-macros-plugin==1.3.7 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -mkdocs-mermaid2-plugin==1.2.1 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -mkdocs-section-index==0.3.9 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -mkdocs-spellcheck==1.1.0 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -mkdocstrings==0.26.1 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -mkdocstrings-python==1.11.1 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -more-itertools==10.5.0 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -mypy-extensions==1.1.0 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -nodeenv==1.9.1 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -packaging==25.0 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -pathspec==0.12.1 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -platformdirs==4.3.6 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -pygments==2.19.1 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -pymdown-extensions==10.15 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -python-dateutil==2.9.0.post0 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -pyyaml==6.0.2 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -pyyaml-env-tag==0.1 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -requests==2.32.3 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -setuptools==75.3.2 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -six==1.17.0 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -soupsieve==2.7 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -super-collections==0.5.3 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -symspellpy==6.7.8 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -termcolor==2.4.0 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -tomli==2.2.1 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -typeguard==4.4.0 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -typing-extensions==4.13.2 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -urllib3==2.2.3 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -watchdog==4.0.2 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -wcmatch==10.0 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -wheel==0.45.1 ; python_version >= "3.8" and python_version < "3.9" -zipp==3.20.2 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "3.10" +beautifulsoup4==4.13.4 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +black==24.10.0 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +bracex==2.5.post1 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +certifi==2025.4.26 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +charset-normalizer==3.4.2 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +click==8.1.8 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +codespell==2.4.1 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +colorama==0.4.6 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +editdistpy==0.1.5 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +editorconfig==0.17.0 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +ghp-import==2.1.0 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +griffe==1.7.3 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +hjson==3.1.0 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +idna==3.10 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +importlib-metadata==8.7.0 ; python_full_version >= "3.9.2" and python_version < "3.10" +inflect==7.5.0 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +jinja2==3.1.6 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +jsbeautifier==1.15.4 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +markdown==3.8 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +markupsafe==3.0.2 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +mergedeep==1.3.4 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +mkdocs==1.6.1 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +mkdocs-autorefs==1.4.1 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +mkdocs-ezglossary-plugin==1.7.1 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +mkdocs-gen-files==0.5.0 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +mkdocs-get-deps==0.2.0 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +mkdocs-include-markdown-plugin==6.2.2 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +mkdocs-literate-nav==0.6.2 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +mkdocs-macros-plugin==1.3.7 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +mkdocs-mermaid2-plugin==1.2.1 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +mkdocs-section-index==0.3.10 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +mkdocs-spellcheck==1.1.1 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +mkdocstrings==0.26.2 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +mkdocstrings-python==1.13.0 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +more-itertools==10.7.0 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +mypy-extensions==1.1.0 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +nodeenv==1.9.1 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +packaging==25.0 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +pathspec==0.12.1 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +platformdirs==4.3.7 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +pygments==2.19.1 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +pymdown-extensions==10.15 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +python-dateutil==2.9.0.post0 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +pyyaml==6.0.2 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +pyyaml-env-tag==0.1 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +requests==2.32.3 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +setuptools==80.3.1 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +six==1.17.0 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +soupsieve==2.7 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +super-collections==0.5.3 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +symspellpy==6.9.0 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +termcolor==3.1.0 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +tomli==2.2.1 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +typeguard==4.4.2 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +typing-extensions==4.13.2 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +urllib3==2.4.0 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +watchdog==6.0.0 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +wcmatch==10.0 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +zipp==3.21.0 ; python_full_version >= "3.9.2" and python_version < "3.10" diff --git a/pyproject.toml b/pyproject.toml index ab4e5f83d..ecaba9413 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,18 +11,18 @@ py-lt-39 = "sys_version_info < (3, 9)" [tool.coverage.paths] source = [ ".tox/**/site-packages/tm_devices", - "src/tm_devices" + "src/tm_devices", ] [tool.coverage.report] exclude_lines = [ "if TYPE_CHECKING:", "pragma: no cover", - "raise NotImplementedError" + "raise NotImplementedError", ] fail_under = 100 omit = [ - "**/tm_devices/commands/**" # TODO: remove this exclusion + "**/tm_devices/commands/**", # TODO: remove this exclusion ] show_missing = true skip_empty = true @@ -43,7 +43,7 @@ wrap-summaries = 0 [tool.poetry] authors = [ "Tektronix ", - "Nicholas Felt " + "Nicholas Felt ", ] classifiers = [ "Development Status :: 5 - Production/Stable", @@ -52,7 +52,7 @@ classifiers = [ "Operating System :: OS Independent", "Topic :: Scientific/Engineering :: Interface Engine/Protocol Translator", "Topic :: Scientific/Engineering", - "Topic :: System :: Hardware :: Hardware Drivers" + "Topic :: System :: Hardware :: Hardware Drivers", ] description = "Manage connections and interactions with Test & Measurement devices." documentation = "https://tm-devices.readthedocs.io/stable/" @@ -63,12 +63,12 @@ keywords = [ "TSP", "Tektronix", "Test & Measurement", - "VISA" + "VISA", ] license = "Apache-2.0" maintainers = [ "Tektronix ", - "Nicholas Felt " + "Nicholas Felt ", ] name = "tm_devices" readme = "README.md" @@ -82,7 +82,7 @@ libusb-package = "^1.0.26.0,!=1.0.26.2" # 1.0.26.2 doesn't work with Python 3.1 packaging = ">=24.0" psutil = ">=6.0.0" pyserial = "^3.5" -python = "^3.8,!=3.9.0,!=3.9.1" # This is the main Python version requirement, the excluded versions of Python had major bugs and shouldn't be used +python = "^3.9.2" # This is the main Python version requirement python-dateutil = "^2.8.2" pyusb = "^1.2.1" pyvicp = "^1.1.0" @@ -99,18 +99,14 @@ urllib3 = "^2.0" zeroconf = "^0.136.0" [tool.poetry.group.dev.dependencies] -docutils = "^0.20" # TODO: Drop Python 3.8: remove this when the minimum Python version is >=3.9 nodeenv = "^1.9.1" pip = "^25.0" poetry = "^1.8.0" poetry-audit-plugin = "^0.4.0" poetry-plugin-export = "^1.7.1" poetry-pre-commit-plugin = "^0.2.2" -pre-commit = [ - {python = ">=3.9", version = "^3.7"}, - {python = "3.8", version = "^3.5"} -] -pylint = "3.2.7" +pre-commit = "^3.7" +pylint = "3.3.7" pyright = {extras = ["nodejs"], version = "1.1.400"} pyroma = "^4.2" tox = "^4.0" @@ -174,7 +170,7 @@ max-args = 7 [tool.pylint.dunder] good-dunder-names = [ - "__isabstractmethod__" + "__isabstractmethod__", ] [tool.pylint.main] @@ -189,7 +185,7 @@ ignore-patterns = [ "^\\.tox", "^\\.venv.*", "^\\.vscode", - "^temp_.*\\..*" + "^temp_.*\\..*", ] jobs = 0 load-plugins = """ @@ -210,13 +206,14 @@ pylint.extensions.set_membership, pylint.extensions.typing, pylint.extensions.while_used """ -py-version = "3.8" +py-version = "3.9" recursive = true [tool.pylint."messages control"] enable = ["all"] disable = [ "broad-exception-caught", # caught by ruff + "deprecated-typing-alias", # caught by ruff "duplicate-code", "fixme", # caught by ruff "global-statement", # caught by ruff @@ -236,14 +233,16 @@ disable = [ "too-many-arguments", # caught by ruff "too-many-branches", # caught by ruff "too-many-lines", # not necessary to check for + "too-many-positional-arguments", # not necessary to check for "too-many-statements", # caught by ruff "too-many-statements", # caught by ruff "unexpected-keyword-arg", # caught by pyright + "unnecessary-default-type-args", # not necessary to check for "unused-argument", # caught by ruff "unused-import", # caught by ruff "use-implicit-booleaness-not-comparison-to-string", # caught by ruff "while-used", # using while loops in example scripts - "wrong-import-order" # caught by ruff + "wrong-import-order", # caught by ruff ] [tool.pylint.reports] @@ -260,10 +259,10 @@ output-format = "text" # colorized could be another option ignore = [ "**/output_*/**", "site/**", - "temp_*.py" + "temp_*.py", ] pythonPlatform = "All" -pythonVersion = "3.8" +pythonVersion = "3.9" reportCallInDefaultInitializer = "error" reportImplicitOverride = "none" # this check is not needed reportImplicitStringConcatenation = "none" # this is allowed by this project's formatting standard @@ -287,7 +286,7 @@ filterwarnings = [ "ignore:'xdrlib' is deprecated:DeprecationWarning", "ignore::DeprecationWarning:pkg_resources", "ignore:GPIB library not found:UserWarning", - "ignore:pkg_resources is deprecated:DeprecationWarning" + "ignore:pkg_resources is deprecated:DeprecationWarning", ] junit_family = "xunit2" junit_logging = "all" @@ -295,7 +294,7 @@ log_format = "[%(asctime)s] [%(levelname)8s] %(message)s" markers = [ 'docs', 'order', - 'slow' + 'slow', ] xfail_strict = true @@ -304,7 +303,7 @@ pytest_report_title = {skip_if_set = true, value = "Test Results"} [tool.rstcheck] ignore_directives = [ - "autoapisummary" # This is added by sphinx-autoapi + "autoapisummary", # This is added by sphinx-autoapi ] [tool.ruff] @@ -312,7 +311,7 @@ line-length = 100 namespace-packages = ["docs/**", "examples/**", "scripts/**", "tests/**"] output-format = "concise" src = ["docs", "examples", "scripts", "src", "tests"] -target-version = "py38" # always generate Python 3.8 compatible code +target-version = "py39" # always generate Python 3.9 compatible code [tool.ruff.lint] allowed-confusables = ["¸", "×"] @@ -329,16 +328,17 @@ ignore = [ "PYI021", # Docstrings should not be included in stubs (allowed in this package) "TD002", # Missing author in TO DO (allowed in this package) "TD003", # Missing issue link on the line following this TO DO (allowed in this package) - "UP006", # Use {to} instead of {from} for type annotation (allowed in this package) - "UP007", # Use `X | Y` for type annotations (allowed in this package) + "UP006", # Use {to} instead of {from} for type annotation # TODO: Drop Python 3.9: enable this check + "UP007", # Use `X | Y` for type annotations # TODO: Drop Python 3.9: enable this check "UP024", # Replace aliased errors with `OSError` (allowed in this package) - "UP037" # Remove quotes from type annotation (allowed in this package) + "UP035", # `X` is deprecated, use `Y` instead # TODO: Drop Python 3.9: enable this check + "UP037", # Remove quotes from type annotation # TODO: Drop Python 3.9: enable this check ] pydocstyle = {convention = "google"} pylint = {max-args = 7} # https://beta.ruff.rs/docs/rules/ select = [ - "ALL" + "ALL", ] task-tags = ["FIXME", "FUTURE", "RELIC", "TODO"] @@ -350,7 +350,7 @@ force-sort-within-sections = false known-first-party = [ "conftest", "mock_server", - "tm_devices" + "tm_devices", ] lines-between-types = 1 order-by-type = false @@ -359,11 +359,11 @@ order-by-type = false "examples/**" = [ "FBT", # flake8-boolean-trap "S101", # Use of assert detected - "T201" # `print` found + "T201", # `print` found ] "examples/miscellaneous/custom_device_driver_support.py" = [ "ARG002", # Unused method argument - "D102" # Missing docstring in public method + "D102", # Missing docstring in public method ] "src/tm_devices/commands/**" = [ "A003", # Class attribute is shadowing a python builtin @@ -371,24 +371,24 @@ order-by-type = false "D107", # Missing docstring in `__init__` "ERA001", # Found commented-out code "S105", # Possible hardcoded password - "TID252" # Relative imports from parent modules are banned + "TID252", # Relative imports from parent modules are banned ] "tests/**" = [ "PLC1901", # compare-to-empty-string "PLR2004", # Magic value used in comparison "PTH107", # `os.remove()` should be replaced by `Path.unlink()` - "S101" # Use of assert detected + "S101", # Use of assert detected ] "tests/samples/golden_stubs/**" = [ "D100", # Missing docstring in public module "D101", # Missing docstring in public class "D107", # Missing docstring in `__init__` - "PYI053" # String and bytes literals longer than 50 characters are not permitted + "PYI053", # String and bytes literals longer than 50 characters are not permitted ] [tool.semantic_release] version_toml = [ - "pyproject.toml:tool.poetry.version" + "pyproject.toml:tool.poetry.version", ] [tool.semantic_release.changelog] @@ -408,6 +408,7 @@ patch_tags = [] all = true in_place = true spaces_before_inline_comment = 2 +trailing_comma_inline_array = true overrides."tool.poetry.*".inline_arrays = false overrides."tool.pylint.*".table_keys = false @@ -417,15 +418,14 @@ legacy_tox_ini = """ [tox] requires = tox>4 isolated_build = True -envlist = py38,py39,py310,py311,py312,py313,tests,docs,doctests +envlist = py39,py310,py311,py312,py313,tests,docs,doctests skip_missing_interpreters = True labels = - basic = py38,py39,py310,py311,py312,py313,tests + basic = py39,py310,py311,py312,py313,tests documentation = docs,doctests [gh-actions] python = - 3.8: py38 3.9: py39 3.10: py310 3.11: py311 @@ -442,12 +442,12 @@ passenv = setenv = COVERAGE_FILE = .coverage_{envname} DOC_PYTHON_VERSION = python3.11 # Keep this in sync with .readthedocs.yml and any CI scripts - # Skip pre-commit checks that are not needed (yamlfix should be removed from this list once Python 3.8 support is dropped) - SKIP = file-contents-sorter,yamlfix + # Skip pre-commit checks that are not needed + SKIP = file-contents-sorter commands_pre = python -m poetry install --no-root --without=main commands = - !tests: python -c "import shutil; shutil.rmtree('dist_{envname}', ignore_errors=True)" + !tests: python -c "import shutil; shutil.rmtree('.dist/{envname}', ignore_errors=True)" !tests: poetry build --output=.dist/{envname} !tests: twine check --strict .dist/{envname}/* !tests: pre-commit run --all-files diff --git a/scripts/contributor_setup.py b/scripts/contributor_setup.py index a5bdca28b..df37e54e2 100644 --- a/scripts/contributor_setup.py +++ b/scripts/contributor_setup.py @@ -51,10 +51,10 @@ def main() -> None: raise IndexError # noqa: TRY301 # This requires contributors to use newer versions of Python even # though the package supports older versions. - if sys.version_info < (3, 9): + if sys.version_info < (3, 10): msg = ( "Unable to set up the environment. " - "Please use a Python version greater than 3.8 for " + "Please use a Python version greater than 3.9 for " "local development on this package." ) raise SystemExit(msg) diff --git a/src/tm_devices/commands/gen_7s2p1p_smu/smux.py b/src/tm_devices/commands/gen_7s2p1p_smu/smux.py index 36c3efda5..758b4c974 100644 --- a/src/tm_devices/commands/gen_7s2p1p_smu/smux.py +++ b/src/tm_devices/commands/gen_7s2p1p_smu/smux.py @@ -123,7 +123,8 @@ ``` """ -from typing import Any, Dict, Optional, Sequence, TYPE_CHECKING, Union +from collections.abc import Sequence +from typing import Any, Dict, Optional, TYPE_CHECKING, Union from ..helpers import BaseTSPCmd, NoDeviceProvidedError, ValidatedChannel diff --git a/src/tm_devices/commands/gen_8ojdkz_smu/smux.py b/src/tm_devices/commands/gen_8ojdkz_smu/smux.py index 85c63d5f0..f0476b085 100644 --- a/src/tm_devices/commands/gen_8ojdkz_smu/smux.py +++ b/src/tm_devices/commands/gen_8ojdkz_smu/smux.py @@ -112,7 +112,8 @@ ``` """ -from typing import Any, Dict, Optional, Sequence, TYPE_CHECKING, Union +from collections.abc import Sequence +from typing import Any, Dict, Optional, TYPE_CHECKING, Union from ..helpers import BaseTSPCmd, NoDeviceProvidedError, ValidatedChannel diff --git a/src/tm_devices/commands/gen_8wm55i_smu/smux.py b/src/tm_devices/commands/gen_8wm55i_smu/smux.py index 6a65909d5..bae14334e 100644 --- a/src/tm_devices/commands/gen_8wm55i_smu/smux.py +++ b/src/tm_devices/commands/gen_8wm55i_smu/smux.py @@ -114,7 +114,8 @@ ``` """ -from typing import Any, Dict, Optional, Sequence, TYPE_CHECKING, Union +from collections.abc import Sequence +from typing import Any, Dict, Optional, TYPE_CHECKING, Union from ..helpers import BaseTSPCmd, NoDeviceProvidedError, ValidatedChannel diff --git a/src/tm_devices/commands/gen_9kezla_smu/smux.py b/src/tm_devices/commands/gen_9kezla_smu/smux.py index 1535f3837..385f3110f 100644 --- a/src/tm_devices/commands/gen_9kezla_smu/smux.py +++ b/src/tm_devices/commands/gen_9kezla_smu/smux.py @@ -114,7 +114,8 @@ ``` """ -from typing import Any, Dict, Optional, Sequence, TYPE_CHECKING, Union +from collections.abc import Sequence +from typing import Any, Dict, Optional, TYPE_CHECKING, Union from ..helpers import BaseTSPCmd, NoDeviceProvidedError, ValidatedChannel diff --git a/src/tm_devices/commands/gen_ak4990_smu/smux.py b/src/tm_devices/commands/gen_ak4990_smu/smux.py index e7576662b..4aab0d233 100644 --- a/src/tm_devices/commands/gen_ak4990_smu/smux.py +++ b/src/tm_devices/commands/gen_ak4990_smu/smux.py @@ -114,7 +114,8 @@ ``` """ -from typing import Any, Dict, Optional, Sequence, TYPE_CHECKING, Union +from collections.abc import Sequence +from typing import Any, Dict, Optional, TYPE_CHECKING, Union from ..helpers import BaseTSPCmd, NoDeviceProvidedError, ValidatedChannel diff --git a/src/tm_devices/commands/gen_am6pcr_smu/smux.py b/src/tm_devices/commands/gen_am6pcr_smu/smux.py index 136eb0926..73e1d7824 100644 --- a/src/tm_devices/commands/gen_am6pcr_smu/smux.py +++ b/src/tm_devices/commands/gen_am6pcr_smu/smux.py @@ -115,7 +115,8 @@ ``` """ -from typing import Any, Dict, Optional, Sequence, TYPE_CHECKING, Union +from collections.abc import Sequence +from typing import Any, Dict, Optional, TYPE_CHECKING, Union from ..helpers import BaseTSPCmd, NoDeviceProvidedError, ValidatedChannel diff --git a/src/tm_devices/commands/gen_as1ejq_smu/smux.py b/src/tm_devices/commands/gen_as1ejq_smu/smux.py index 526a42c34..5a8fa6791 100644 --- a/src/tm_devices/commands/gen_as1ejq_smu/smux.py +++ b/src/tm_devices/commands/gen_as1ejq_smu/smux.py @@ -115,7 +115,8 @@ ``` """ -from typing import Any, Dict, Optional, Sequence, TYPE_CHECKING, Union +from collections.abc import Sequence +from typing import Any, Dict, Optional, TYPE_CHECKING, Union from ..helpers import BaseTSPCmd, NoDeviceProvidedError, ValidatedChannel diff --git a/src/tm_devices/commands/helpers/generic_commands.py b/src/tm_devices/commands/helpers/generic_commands.py index 9d1d15575..b9e548744 100644 --- a/src/tm_devices/commands/helpers/generic_commands.py +++ b/src/tm_devices/commands/helpers/generic_commands.py @@ -6,20 +6,13 @@ """ import re -import sys from collections import defaultdict from functools import total_ordering -from typing import Any, Callable, DefaultDict, Optional, Type, Union +from typing import Any, Callable, Optional, Union END_OF_STRING_DIGITS = re.compile(r"([-\d]+)]?$") MIDDLE_OF_STRING_DIGITS = re.compile(r"([-\d]+)]?") -# TODO: Drop Python 3.8: Once Python 3.8 is no longer supported, -# the dynamic parent class can be removed -# pylint: disable=unsubscriptable-object,useless-suppression -ParentDefaultDictClass: Type[DefaultDict[Any, Any]] = ( - defaultdict if sys.version_info < (3, 9) else defaultdict[Any, Any] -) #################################################################################################### @@ -32,7 +25,7 @@ class NoDeviceProvidedError(Exception): #################################################################################################### # Classes #################################################################################################### -class DefaultDictPassKeyToFactory(ParentDefaultDictClass): +class DefaultDictPassKeyToFactory(defaultdict[Any, Any]): """A custom defaultdict. This custom defaultdict passes the key used to access a missing value into the stored diff --git a/src/tm_devices/commands/helpers/scpi_commands.py b/src/tm_devices/commands/helpers/scpi_commands.py index d00edce04..d82e4fb64 100644 --- a/src/tm_devices/commands/helpers/scpi_commands.py +++ b/src/tm_devices/commands/helpers/scpi_commands.py @@ -7,10 +7,9 @@ import re import string -import sys from collections import defaultdict -from typing import Any, cast, DefaultDict, Optional, Set, Tuple, Type, TYPE_CHECKING, Union +from typing import Any, cast, Optional, Set, Tuple, TYPE_CHECKING, Union from .generic_commands import BaseCmd, END_OF_STRING_DIGITS, NoDeviceProvidedError @@ -20,12 +19,6 @@ MAX_CHANNELS = 8 MAX_DIGITAL_BITS = 16 END_OF_STRING_NUMBER = re.compile(r"(\d+)$") -# TODO: Drop Python 3.8: Once Python 3.8 is no longer supported, -# the dynamic parent class can be removed -# pylint: disable=unsubscriptable-object,useless-suppression -ParentDefaultDictClass: Type[DefaultDict[Any, Any]] = ( - defaultdict if sys.version_info < (3, 9) else defaultdict[Any, Any] -) #################################################################################################### @@ -273,7 +266,7 @@ def __init__(self, device: Optional["Any"], cmd_syntax: str) -> None: raise ValueError(msg) -class DefaultDictDeviceCommunication(ParentDefaultDictClass): +class DefaultDictDeviceCommunication(defaultdict[Any, Any]): """A custom default dictionary that can be used to send/receive commands to/from a device. The ``.query()`` method is used when ``__getitem__()`` is called and the result of the query is diff --git a/src/tm_devices/components/dm_config_parser.py b/src/tm_devices/components/dm_config_parser.py index 733b05e9d..6af1f3641 100644 --- a/src/tm_devices/components/dm_config_parser.py +++ b/src/tm_devices/components/dm_config_parser.py @@ -14,12 +14,12 @@ Dict, get_type_hints, List, - Mapping, Optional, Protocol, runtime_checkable, Tuple, Type, + TYPE_CHECKING, Union, ) @@ -40,6 +40,9 @@ CONFIG_CLASS_STR_PREFIX_MAPPING, ) +if TYPE_CHECKING: + from collections.abc import Mapping + @runtime_checkable @dataclasses.dataclass @@ -452,7 +455,7 @@ def __pre_process_env_devices(self, devices_list: List[Dict[str, Any]]) -> None: for entry in devices_list: # Bundle any prefixed pairs into a nested kwarg-style dict. for to_class, prefix in CONFIG_CLASS_STR_PREFIX_MAPPING.items(): - config_dict: Dict[str, str] + config_dict: Dict[str, str] = {} if config_dict := { key.replace(prefix, ""): entry.pop(key) for key in list(entry.keys()) diff --git a/src/tm_devices/device_manager.py b/src/tm_devices/device_manager.py index 89f312130..5d315fad7 100644 --- a/src/tm_devices/device_manager.py +++ b/src/tm_devices/device_manager.py @@ -13,7 +13,7 @@ import warnings from types import FrameType, MappingProxyType, TracebackType -from typing import cast, Dict, Mapping, Optional, Tuple, Type, TYPE_CHECKING, Union +from typing import cast, Dict, Optional, Tuple, Type, TYPE_CHECKING, Union from typing_extensions import TypeVar @@ -58,6 +58,8 @@ if TYPE_CHECKING: + from collections.abc import Mapping + from pyvisa.resources import MessageBasedResource from typing_extensions import Self diff --git a/src/tm_devices/driver_mixins/device_control/pi_control.py b/src/tm_devices/driver_mixins/device_control/pi_control.py index 08308f831..775299665 100644 --- a/src/tm_devices/driver_mixins/device_control/pi_control.py +++ b/src/tm_devices/driver_mixins/device_control/pi_control.py @@ -8,8 +8,9 @@ import warnings from abc import ABC +from collections.abc import Generator, Sequence from pathlib import Path -from typing import final, Generator, List, Optional, Sequence, Tuple, Union +from typing import final, List, Optional, Tuple, Union import pyvisa as visa diff --git a/src/tm_devices/driver_mixins/device_control/rest_api_control.py b/src/tm_devices/driver_mixins/device_control/rest_api_control.py index 9315dcead..82539d5dc 100644 --- a/src/tm_devices/driver_mixins/device_control/rest_api_control.py +++ b/src/tm_devices/driver_mixins/device_control/rest_api_control.py @@ -5,8 +5,9 @@ import time from abc import ABC, abstractmethod +from collections.abc import Mapping from types import MappingProxyType -from typing import Any, cast, Dict, Mapping, Optional, Tuple, Union +from typing import Any, cast, Dict, Optional, Tuple, Union import requests diff --git a/src/tm_devices/drivers/_device_driver_mapping.py b/src/tm_devices/drivers/_device_driver_mapping.py index 34a1297e4..b25332111 100644 --- a/src/tm_devices/drivers/_device_driver_mapping.py +++ b/src/tm_devices/drivers/_device_driver_mapping.py @@ -1,7 +1,7 @@ """The mapping for all device drivers.""" from types import MappingProxyType -from typing import Mapping, Type, TYPE_CHECKING +from typing import Type, TYPE_CHECKING from tm_devices.drivers.afgs.afg3k import AFG3K from tm_devices.drivers.afgs.afg3kb import AFG3KB @@ -105,6 +105,8 @@ from tm_devices.helpers.enums import SupportedModels if TYPE_CHECKING: + from collections.abc import Mapping + from tm_devices.drivers.device import Device #################################################################################################### diff --git a/src/tm_devices/drivers/device.py b/src/tm_devices/drivers/device.py index 5313fa4be..fb40573c7 100644 --- a/src/tm_devices/drivers/device.py +++ b/src/tm_devices/drivers/device.py @@ -6,12 +6,12 @@ import time from abc import ABC, abstractmethod +from collections.abc import Generator from contextlib import contextmanager, suppress from functools import cached_property as functools_cached_property from typing import ( Any, final, - Generator, Optional, Tuple, TypeVar, diff --git a/src/tm_devices/drivers/margin_testers/margin_tester.py b/src/tm_devices/drivers/margin_testers/margin_tester.py index 11fba7ad8..b3e6c3684 100644 --- a/src/tm_devices/drivers/margin_testers/margin_tester.py +++ b/src/tm_devices/drivers/margin_testers/margin_tester.py @@ -7,7 +7,7 @@ from abc import ABC, abstractmethod from pathlib import Path -from typing import Any, Dict, Mapping, MutableMapping, Tuple, TYPE_CHECKING, Union +from typing import Any, Dict, Tuple, TYPE_CHECKING, Union from requests.structures import CaseInsensitiveDict @@ -17,6 +17,8 @@ from tm_devices.helpers import ReadOnlyCachedProperty as cached_property # noqa: N813 if TYPE_CHECKING: + from collections.abc import Mapping, MutableMapping + from packaging.version import Version diff --git a/src/tm_devices/drivers/margin_testers/tmt4.py b/src/tm_devices/drivers/margin_testers/tmt4.py index 9f85023cf..88ac41a21 100644 --- a/src/tm_devices/drivers/margin_testers/tmt4.py +++ b/src/tm_devices/drivers/margin_testers/tmt4.py @@ -2,8 +2,9 @@ import time +from collections.abc import Mapping from types import MappingProxyType -from typing import Any, cast, Dict, Mapping, Optional, Tuple +from typing import Any, cast, Dict, Optional, Tuple from packaging.version import Version diff --git a/src/tm_devices/helpers/alias_dict.py b/src/tm_devices/helpers/alias_dict.py index 4ff3bfec1..920ad3e3c 100644 --- a/src/tm_devices/helpers/alias_dict.py +++ b/src/tm_devices/helpers/alias_dict.py @@ -1,18 +1,12 @@ """A Module containing a custom dictionary class that works with alias keys.""" -import sys +from typing import Any, TYPE_CHECKING -from typing import Any, Dict, MutableMapping, Type +if TYPE_CHECKING: + from collections.abc import MutableMapping -# TODO: Drop Python 3.8: Once Python 3.8 is no longer supported, -# the dynamic parent class can be removed -# pylint: disable=unsubscriptable-object,useless-suppression -ParentDictClass: Type[Dict[Any, Any]] = dict if sys.version_info < (3, 9) else dict[Any, Any] - -# TODO: Drop Python 3.8: Once Python 3.8 is no longer supported, -# replace the parent class with `dict[Any, Any]` -class AliasDict(ParentDictClass): +class AliasDict(dict[Any, Any]): """A custom dictionary class that supports aliases as secondary keys. Each alias and key that is added must be unique, no duplicate aliases or keys are allowed. diff --git a/src/tm_devices/helpers/constants_and_dataclasses.py b/src/tm_devices/helpers/constants_and_dataclasses.py index 6eefa68aa..4790e4827 100644 --- a/src/tm_devices/helpers/constants_and_dataclasses.py +++ b/src/tm_devices/helpers/constants_and_dataclasses.py @@ -2,9 +2,10 @@ import re +from collections.abc import Mapping from dataclasses import dataclass from types import MappingProxyType -from typing import Dict, Final, FrozenSet, List, Mapping, Optional, Tuple, Union +from typing import Dict, Final, FrozenSet, List, Optional, Tuple, Union from pyvisa import constants as pyvisa_constants @@ -441,8 +442,8 @@ class DMConfigOptions(AsDictionaryMixin): disable_command_verification: Optional[bool] = None """A flag that disables command verification for all devices. - This can have the effect of speeding up automation scripts by no longer checking each - command after it is sent via the `.set_and_check()` method. + This can have the effect of speeding up automation scripts by no longer checking each command + after it is sent via the `.set_and_check()` method. """ verbose_visa: Optional[bool] = None """A verbosity flag to enable extremely verbose VISA logging to stdout.""" diff --git a/src/tm_devices/helpers/functions.py b/src/tm_devices/helpers/functions.py index c1b4c25d3..2b3a59909 100644 --- a/src/tm_devices/helpers/functions.py +++ b/src/tm_devices/helpers/functions.py @@ -13,7 +13,7 @@ import warnings from enum import EnumMeta -from functools import lru_cache +from functools import cache from typing import Any, Dict, Optional, Tuple, Type import requests @@ -758,7 +758,7 @@ def _configure_visa_object( return visa_object -@lru_cache(maxsize=None) +@cache def _get_system_visa_info() -> Dict[str, Any]: """Get the VISA information for the current system. diff --git a/src/tm_devices/helpers/read_only_cached_property.py b/src/tm_devices/helpers/read_only_cached_property.py index e5279e0cc..704fca98e 100644 --- a/src/tm_devices/helpers/read_only_cached_property.py +++ b/src/tm_devices/helpers/read_only_cached_property.py @@ -7,85 +7,42 @@ _T = TypeVar("_T") -# TODO: Drop Python 3.8: Remove pragmas and exception block when support for Python 3.8 is dropped -try: # pragma: py-lt-39 - # pylint: disable=unsubscriptable-object,useless-suppression - class ReadOnlyCachedProperty(cached_property[_T]): # pyright: ignore[reportRedeclaration] - """An implementation of cached_property that is read-only. - Notes: - In order for the PyCharm IDE to properly provide auto-complete hints, this class must be - imported in the following way: +class ReadOnlyCachedProperty(cached_property[_T]): + """An implementation of cached_property that is read-only. - ``from tm_devices.helpers import ReadOnlyCachedProperty as cached_property``. + Notes: + In order for the PyCharm IDE to properly provide auto-complete hints, this class must be + imported in the following way: - Examples: - >>> from tm_devices.helpers import ReadOnlyCachedProperty - >>> class ClassWithReadOnlyCachedProperty: - ... @ReadOnlyCachedProperty - ... def c(self) -> str: - ... return "cached value" - """ - - def __set__(self, instance: object, value: _T) -> None: - """Raise an AttributeError when trying to set the property since it is read-only. - - Raises: - AttributeError: Indicates that the property is read-only. - """ - msg = f"{self.attrname} is a read-only attribute" - raise AttributeError(msg) - - def __delete__(self, instance: object) -> None: - """Implement the __delete__ method to enable resetting the cache.""" - cache = instance.__dict__ - # Delete the attribute from the cache, ignoring KeyErrors since that just means the - # attribute already doesn't exist and therefore doesn't need to be deleted. - with contextlib.suppress(KeyError): - del cache[self.attrname] # pyright: ignore[reportArgumentType] - - @property - def __isabstractmethod__(self) -> bool: - """Provide a way to check if the decorated method is an abstract method.""" - return getattr(self.func, "__isabstractmethod__", False) + ``from tm_devices.helpers import ReadOnlyCachedProperty as cached_property``. -except TypeError: # pragma: py-gte-39 - # pylint: disable=unsubscriptable-object,useless-suppression - class ReadOnlyCachedProperty(cached_property): # pyright: ignore[reportMissingTypeArgument] - """An implementation of cached_property that is read-only. + Examples: + >>> from tm_devices.helpers import ReadOnlyCachedProperty + >>> class ClassWithReadOnlyCachedProperty: + ... @ReadOnlyCachedProperty + ... def c(self) -> str: + ... return "cached value" + """ - Notes: - In order for the PyCharm IDE to properly provide auto-complete hints, this class must be - imported in the following way: + def __set__(self, instance: object, value: _T) -> None: + """Raise an AttributeError when trying to set the property since it is read-only. - ``from tm_devices.helpers import ReadOnlyCachedProperty as cached_property``. - - Examples: - >>> from tm_devices.helpers import ReadOnlyCachedProperty - >>> class ClassWithReadOnlyCachedProperty: - ... @ReadOnlyCachedProperty - ... def c(self) -> str: - ... return "cached value" + Raises: + AttributeError: Indicates that the property is read-only. """ - - def __set__(self, instance: object, value: _T) -> None: # pyright: ignore[reportInvalidTypeVarUse] - """Raise an AttributeError when trying to set the property since it is read-only. - - Raises: - AttributeError: Indicates that the property is read-only. - """ - msg = f"{self.attrname} is a read-only attribute" - raise AttributeError(msg) - - def __delete__(self, instance: object) -> None: - """Implement the __delete__ method to enable resetting the cache.""" - cache = instance.__dict__ - # Delete the attribute from the cache, ignoring KeyErrors since that just means the - # attribute already doesn't exist and therefore doesn't need to be deleted. - with contextlib.suppress(KeyError): - del cache[self.attrname] # pyright: ignore[reportArgumentType] - - @property - def __isabstractmethod__(self) -> bool: - """Provide a way to check if the decorated method is an abstract method.""" - return getattr(self.func, "__isabstractmethod__", False) + msg = f"{self.attrname} is a read-only attribute" + raise AttributeError(msg) + + def __delete__(self, instance: object) -> None: + """Implement the __delete__ method to enable resetting the cache.""" + cache = instance.__dict__ + # Delete the attribute from the cache, ignoring KeyErrors since that just means the + # attribute already doesn't exist and therefore doesn't need to be deleted. + with contextlib.suppress(KeyError): + del cache[self.attrname] # pyright: ignore[reportArgumentType] + + @property + def __isabstractmethod__(self) -> bool: + """Provide a way to check if the decorated method is an abstract method.""" + return getattr(self.func, "__isabstractmethod__", False) diff --git a/src/tm_devices/helpers/singleton_metaclass.py b/src/tm_devices/helpers/singleton_metaclass.py index c5dad32eb..ca54d9a1b 100644 --- a/src/tm_devices/helpers/singleton_metaclass.py +++ b/src/tm_devices/helpers/singleton_metaclass.py @@ -3,7 +3,8 @@ import logging import warnings -from typing import Any, MutableMapping +from collections.abc import MutableMapping +from typing import Any from weakref import WeakValueDictionary _logger: logging.Logger = logging.getLogger(__name__) diff --git a/tests/conftest.py b/tests/conftest.py index 23d2eeb6a..bf5da818e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -5,8 +5,9 @@ import socket import sys +from collections.abc import Generator from pathlib import Path -from typing import Generator, List, Tuple +from typing import List, Tuple from unittest import mock import pytest @@ -98,20 +99,24 @@ def fixture_device_manager() -> Generator[DeviceManager, None, None]: The DeviceManager instance. """ print() # noqa: T201 - with mock.patch( - "socket.gethostbyname", mock.MagicMock(side_effect=mock_gethostbyname) - ), mock.patch( - "socket.gethostbyaddr", - mock.MagicMock(side_effect=mock_gethostbyaddr), - ), mock.patch( - "pyvisa.resources.messagebased.MessageBasedResource.read_stb", - mock.MagicMock(return_value=0), - ), mock.patch( - "pyvisa.resources.messagebased.MessageBasedResource.clear", - mock.MagicMock(return_value=pyvisa.constants.StatusCode.success), - ), DeviceManager( - verbose=True, config_options=DMConfigOptions(default_visa_timeout=UNIT_TEST_TIMEOUT) - ) as dev_manager: + with ( + mock.patch("socket.gethostbyname", mock.MagicMock(side_effect=mock_gethostbyname)), + mock.patch( + "socket.gethostbyaddr", + mock.MagicMock(side_effect=mock_gethostbyaddr), + ), + mock.patch( + "pyvisa.resources.messagebased.MessageBasedResource.read_stb", + mock.MagicMock(return_value=0), + ), + mock.patch( + "pyvisa.resources.messagebased.MessageBasedResource.clear", + mock.MagicMock(return_value=pyvisa.constants.StatusCode.success), + ), + DeviceManager( + verbose=True, config_options=DMConfigOptions(default_visa_timeout=UNIT_TEST_TIMEOUT) + ) as dev_manager, + ): dev_manager.visa_library = SIMULATED_VISA_LIB yield dev_manager diff --git a/tests/requirements.txt b/tests/requirements.txt index e8cbf90fb..8f04a273c 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -1,53 +1,53 @@ -beautifulsoup4==4.13.4 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -blinker==1.8.2 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -certifi==2025.4.26 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -chardet==5.2.0 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -charset-normalizer==3.4.2 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -click==8.1.8 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -colorama==0.4.6 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -coverage==7.6.1 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -coverage-conditional-plugin==0.9.0 ; python_version >= "3.8" and python_version < "4.0" and python_full_version != "3.9.0" and python_full_version != "3.9.1" -coverage[toml]==7.6.1 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -dataproperty==1.0.1 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -dnspython==2.6.1 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -exceptiongroup==1.3.0 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "3.11" -flask==3.0.3 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -future-fstrings==1.2.0 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -http-server-mock==1.7 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -idna==3.10 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -importlib-metadata==8.5.0 ; python_version >= "3.8" and python_version < "3.10" and python_full_version != "3.9.0" and python_full_version != "3.9.1" -iniconfig==2.1.0 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -itsdangerous==2.2.0 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -jinja2==3.1.6 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -linkchecker==10.3.0 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -markupsafe==2.1.5 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -mbstrdecoder==1.1.3 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -networkx==3.1 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -packaging==25.0 ; python_version >= "3.8" and python_version < "4.0" and python_full_version != "3.9.0" and python_full_version != "3.9.1" -pathvalidate==3.2.1 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -pluggy==1.5.0 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -pytablewriter==1.2.0 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -pytest==8.3.5 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -pytest-cov==5.0.0 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -pytest-depends==1.0.1 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -pytest-env==1.1.5 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -pytest-github-report==0.0.1 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -pytest-html==4.1.1 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -pytest-metadata==3.1.1 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -pytest-order==1.3.0 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -python-dateutil==2.9.0.post0 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -pytz==2025.2 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -requests==2.32.3 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -ruff==0.11.9 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -setuptools==75.3.2 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -six==1.17.0 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -soupsieve==2.7 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -tabledata==1.3.3 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -tcolorpy==0.1.6 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -tomli==2.2.1 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_full_version <= "3.11.0a6" -typepy==1.3.2 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -typepy[datetime]==1.3.2 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -typing-extensions==4.13.2 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -urllib3==2.2.3 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -werkzeug==3.0.6 ; python_version >= "3.8" and python_full_version != "3.9.0" and python_full_version != "3.9.1" and python_version < "4.0" -zipp==3.20.2 ; python_version >= "3.8" and python_version < "3.10" and python_full_version != "3.9.0" and python_full_version != "3.9.1" +beautifulsoup4==4.13.4 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +blinker==1.9.0 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +certifi==2025.4.26 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +chardet==5.2.0 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +charset-normalizer==3.4.2 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +click==8.1.8 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +colorama==0.4.6 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +coverage==7.8.0 ; python_full_version >= "3.9.2" and python_version < "4.0" +coverage-conditional-plugin==0.9.0 ; python_full_version >= "3.9.2" and python_version < "4.0" +coverage[toml]==7.8.0 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +dataproperty==1.1.0 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +dnspython==2.7.0 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +exceptiongroup==1.3.0 ; python_full_version >= "3.9.2" and python_version < "3.11" +flask==3.1.0 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +future-fstrings==1.2.0 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +http-server-mock==1.7 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +idna==3.10 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +importlib-metadata==8.7.0 ; python_full_version >= "3.9.2" and python_version < "3.10" +iniconfig==2.1.0 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +itsdangerous==2.2.0 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +jinja2==3.1.6 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +linkchecker==10.5.0 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +markupsafe==3.0.2 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +mbstrdecoder==1.1.4 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +networkx==3.2.1 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +packaging==25.0 ; python_full_version >= "3.9.2" and python_version < "4.0" +pathvalidate==3.2.3 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +pluggy==1.5.0 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +pytablewriter==1.2.1 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +pytest==8.3.5 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +pytest-cov==5.0.0 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +pytest-depends==1.0.1 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +pytest-env==1.1.5 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +pytest-github-report==0.0.1 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +pytest-html==4.1.1 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +pytest-metadata==3.1.1 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +pytest-order==1.3.0 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +python-dateutil==2.9.0.post0 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +pytz==2025.2 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +requests==2.32.3 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +ruff==0.11.9 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +setuptools==80.3.1 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +six==1.17.0 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +soupsieve==2.7 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +tabledata==1.3.4 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +tcolorpy==0.1.7 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +tomli==2.2.1 ; python_full_version >= "3.9.2" and python_full_version <= "3.11.0a6" +typepy==1.3.4 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +typepy[datetime]==1.3.4 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +typing-extensions==4.13.2 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +urllib3==2.4.0 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +werkzeug==3.1.3 ; python_full_version >= "3.9.2" and python_full_version < "4.0.0" +zipp==3.21.0 ; python_full_version >= "3.9.2" and python_version < "3.10" diff --git a/tests/test_afgs.py b/tests/test_afgs.py index 906931cea..5a9429dad 100644 --- a/tests/test_afgs.py +++ b/tests/test_afgs.py @@ -51,10 +51,13 @@ def test_afg3k(device_manager: DeviceManager) -> None: # noqa: PLR0915 # pylin assert afg3252c.expect_esr(0) with mock.patch("pyvisa.highlevel.VisaLibraryBase.clear", mock.MagicMock(return_value=None)): assert afg3252c.query_expect_timeout("INVALID?", timeout_ms=1) == "" - with mock.patch( - "pyvisa.resources.messagebased.MessageBasedResource.query", - mock.MagicMock(side_effect=visa.errors.Error("custom error")), - ), pytest.raises(visa.errors.Error): + with ( + mock.patch( + "pyvisa.resources.messagebased.MessageBasedResource.query", + mock.MagicMock(side_effect=visa.errors.Error("custom error")), + ), + pytest.raises(visa.errors.Error), + ): afg3252c.query_expect_timeout("INVALID?", timeout_ms=1) assert afg3252c.expect_esr(32, ("1, Command error", '0,"No error"')) with pytest.raises(AssertionError): diff --git a/tests/test_all_device_drivers.py b/tests/test_all_device_drivers.py index 11896b4e7..cdde3e8a6 100644 --- a/tests/test_all_device_drivers.py +++ b/tests/test_all_device_drivers.py @@ -5,8 +5,9 @@ import sys from collections import Counter +from collections.abc import Generator from pathlib import Path -from typing import Generator, List, Optional +from typing import List, Optional import pytest diff --git a/tests/test_config_parser.py b/tests/test_config_parser.py index 8fa50a12e..1df8544f1 100644 --- a/tests/test_config_parser.py +++ b/tests/test_config_parser.py @@ -3,7 +3,7 @@ from pathlib import Path from types import MappingProxyType -from typing import Dict, Mapping, Optional, Type +from typing import Dict, Optional, Type, TYPE_CHECKING from unittest import mock import pytest @@ -19,6 +19,9 @@ ) from tm_devices.helpers.constants_and_dataclasses import CONFIG_CLASS_STR_PREFIX_MAPPING +if TYPE_CHECKING: + from collections.abc import Mapping + def test_nested_config_prefix_mapping() -> None: """Test the DMConfigParser knows about all the things in CONFIG_CLASS_STR_PREFIX_MAPPING.""" @@ -188,9 +191,11 @@ def test_file_config_default_path() -> None: alias=None, ), } - with mock.patch.dict("os.environ", {}, clear=True), mock.patch( - "pathlib.Path.is_file", mock.MagicMock(return_value=True) - ), mock.patch("pathlib.Path.open", mock.mock_open(read_data=file_contents)): + with ( + mock.patch.dict("os.environ", {}, clear=True), + mock.patch("pathlib.Path.is_file", mock.MagicMock(return_value=True)), + mock.patch("pathlib.Path.open", mock.mock_open(read_data=file_contents)), + ): config = DMConfigParser() assert expected_options == config.options @@ -450,8 +455,9 @@ def test_sequential_env_var_devices() -> None: def test_add_device() -> None: """Verify a device can be added to an empty config.""" - with mock.patch.dict("os.environ", {}, clear=True), mock.patch( - "os.path.isfile", mock.MagicMock(return_value=False) + with ( + mock.patch.dict("os.environ", {}, clear=True), + mock.patch("os.path.isfile", mock.MagicMock(return_value=False)), ): config = DMConfigParser() expected_devices_1: Mapping[str, DeviceConfigEntry] = MappingProxyType({}) @@ -580,11 +586,14 @@ def test_get_visa_resource_expression( def test_get_visa_resource_expression_errors() -> None: """Test creating a resource expression throws the proper errors.""" # Test trying to connect to a USB device that doesn't exist - with mock.patch.dict( - "os.environ", - {"TM_DEVICES": "device_type=SCOPE,connection_type=USB,address=MSO123456-3000260000"}, - clear=True, - ), pytest.warns(UserWarning): + with ( + mock.patch.dict( + "os.environ", + {"TM_DEVICES": "device_type=SCOPE,connection_type=USB,address=MSO123456-3000260000"}, + clear=True, + ), + pytest.warns(UserWarning), + ): config = DMConfigParser() device = config.devices["SCOPE 1"] with pytest.raises(NotImplementedError): diff --git a/tests/test_device_manager.py b/tests/test_device_manager.py index 6a88d5d83..367a6a3e1 100644 --- a/tests/test_device_manager.py +++ b/tests/test_device_manager.py @@ -195,24 +195,30 @@ def test_warnings(self, device_manager: DeviceManager) -> None: # Remove all previous devices device_manager.remove_all_devices() # Test the warning logged with bad read_stb() call - with mock.patch( - "pyvisa.resources.messagebased.MessageBasedResource.read_stb", - mock.MagicMock(side_effect=visa.VisaIOError(pyvisa.constants.VI_ERROR_INV_SETUP)), - ), pytest.warns( - UserWarning, - match="A VISA IO error occurred when attempting to read the status byte or " - "clear the output buffer of the resource", + with ( + mock.patch( + "pyvisa.resources.messagebased.MessageBasedResource.read_stb", + mock.MagicMock(side_effect=visa.VisaIOError(pyvisa.constants.VI_ERROR_INV_SETUP)), + ), + pytest.warns( + UserWarning, + match="A VISA IO error occurred when attempting to read the status byte or " + "clear the output buffer of the resource", + ), ): device_manager.add_afg("afg3252c-hostname", alias="warning_bad_read_stb") device_manager.remove_device(alias="warning_bad_read_stb") # Test the warning logged with message available (MAV) bit set # patched the stb to return 16 (message available) - with mock.patch( - "pyvisa.resources.messagebased.MessageBasedResource.read_stb", - mock.MagicMock(return_value=16), - ), pytest.warns( - UserWarning, match="had data sitting in the VISA Output Buffer on first connection." + with ( + mock.patch( + "pyvisa.resources.messagebased.MessageBasedResource.read_stb", + mock.MagicMock(return_value=16), + ), + pytest.warns( + UserWarning, match="had data sitting in the VISA Output Buffer on first connection." + ), ): device_manager.add_afg("afg3252c-hostname", alias="warning_mav") device_manager.remove_device(alias="warning_mav") @@ -234,15 +240,19 @@ def test_exceptions(self, device_manager: DeviceManager) -> None: # Test the error raised with bad *idn? return data # patched the stb to return 80 = 64 (Service Request) + 16 (message available) - with mock.patch( - "pyvisa.resources.messagebased.MessageBasedResource.read_stb", - mock.MagicMock(return_value=80), - ), mock.patch( - "pyvisa.resources.messagebased.MessageBasedResource.write", - mock.MagicMock(return_value=5), - ), mock.patch( - "pyvisa.resources.messagebased.MessageBasedResource.read", - mock.MagicMock(return_value="data"), + with ( + mock.patch( + "pyvisa.resources.messagebased.MessageBasedResource.read_stb", + mock.MagicMock(return_value=80), + ), + mock.patch( + "pyvisa.resources.messagebased.MessageBasedResource.write", + mock.MagicMock(return_value=5), + ), + mock.patch( + "pyvisa.resources.messagebased.MessageBasedResource.read", + mock.MagicMock(return_value="data"), + ), ): with pytest.warns(UserWarning), pytest.raises(SystemError) as error: device_manager.add_afg("afg3252c-hostname") @@ -253,15 +263,19 @@ def test_exceptions(self, device_manager: DeviceManager) -> None: ) # Test the error raised with timeout on *idn? query - with mock.patch( - "pyvisa.resources.messagebased.MessageBasedResource.read_stb", - mock.MagicMock(return_value=16), - ), mock.patch( - "pyvisa.resources.messagebased.MessageBasedResource.write", - mock.MagicMock(return_value=5), - ), mock.patch( - "pyvisa.resources.messagebased.MessageBasedResource.read", - mock.MagicMock(side_effect=visa.VisaIOError(pyvisa.constants.VI_ERROR_TMO)), + with ( + mock.patch( + "pyvisa.resources.messagebased.MessageBasedResource.read_stb", + mock.MagicMock(return_value=16), + ), + mock.patch( + "pyvisa.resources.messagebased.MessageBasedResource.write", + mock.MagicMock(return_value=5), + ), + mock.patch( + "pyvisa.resources.messagebased.MessageBasedResource.read", + mock.MagicMock(side_effect=visa.VisaIOError(pyvisa.constants.VI_ERROR_TMO)), + ), ): # patched the stb to return 16 (message available) with pytest.warns(UserWarning), pytest.raises(SystemError) as error: @@ -305,14 +319,18 @@ def test_exceptions(self, device_manager: DeviceManager) -> None: with pytest.raises(SystemError): device_manager.add_afg("BAD-IDN") - with mock.patch( - "pyvisa.ResourceManager", mock.MagicMock(side_effect=visa.Error()) - ), pytest.raises(AssertionError): + with ( + mock.patch("pyvisa.ResourceManager", mock.MagicMock(side_effect=visa.Error())), + pytest.raises(AssertionError), + ): device_manager.get_available_devices() - with mock.patch( - "pyvisa.ResourceManager.list_resources", mock.MagicMock(side_effect=visa.Error()) - ), pytest.raises(AssertionError): + with ( + mock.patch( + "pyvisa.ResourceManager.list_resources", mock.MagicMock(side_effect=visa.Error()) + ), + pytest.raises(AssertionError), + ): device_manager.get_available_devices() with pytest.raises(TypeError): diff --git a/tests/test_devices_legacy_tsp_ieee_cmds.py b/tests/test_devices_legacy_tsp_ieee_cmds.py index a3c06f925..db4d0ee34 100644 --- a/tests/test_devices_legacy_tsp_ieee_cmds.py +++ b/tests/test_devices_legacy_tsp_ieee_cmds.py @@ -24,10 +24,13 @@ def test_dmm6500(device_manager: DeviceManager) -> None: with mock.patch("pyvisa.highlevel.VisaLibraryBase.clear", mock.MagicMock(return_value=None)): assert dmm.query_expect_timeout("INVALID?", timeout_ms=1) == "" - with mock.patch( - "pyvisa.resources.messagebased.MessageBasedResource.query", - mock.MagicMock(side_effect=visa.errors.Error("custom error")), - ), pytest.raises(visa.errors.Error): + with ( + mock.patch( + "pyvisa.resources.messagebased.MessageBasedResource.query", + mock.MagicMock(side_effect=visa.errors.Error("custom error")), + ), + pytest.raises(visa.errors.Error), + ): dmm.query_expect_timeout("INVALID?", timeout_ms=1) assert dmm.expect_esr(32, ("Command error", "No Error")) @@ -42,10 +45,13 @@ def test_dmm75xx(device_manager: DeviceManager) -> None: with mock.patch("pyvisa.highlevel.VisaLibraryBase.clear", mock.MagicMock(return_value=None)): assert dmm.query_expect_timeout("INVALID?", timeout_ms=1) == "" - with mock.patch( - "pyvisa.resources.messagebased.MessageBasedResource.query", - mock.MagicMock(side_effect=visa.errors.Error("custom error")), - ), pytest.raises(visa.errors.Error): + with ( + mock.patch( + "pyvisa.resources.messagebased.MessageBasedResource.query", + mock.MagicMock(side_effect=visa.errors.Error("custom error")), + ), + pytest.raises(visa.errors.Error), + ): dmm.query_expect_timeout("INVALID?", timeout_ms=1) assert dmm.expect_esr(32, ("Command error", "No Error")) @@ -62,10 +68,13 @@ def test_daq6510(device_manager: DeviceManager) -> None: with mock.patch("pyvisa.highlevel.VisaLibraryBase.clear", mock.MagicMock(return_value=None)): assert daq.query_expect_timeout("INVALID?", timeout_ms=1) == "" - with mock.patch( - "pyvisa.resources.messagebased.MessageBasedResource.query", - mock.MagicMock(side_effect=visa.errors.Error("custom error")), - ), pytest.raises(visa.errors.Error): + with ( + mock.patch( + "pyvisa.resources.messagebased.MessageBasedResource.query", + mock.MagicMock(side_effect=visa.errors.Error("custom error")), + ), + pytest.raises(visa.errors.Error), + ): daq.query_expect_timeout("INVALID?", timeout_ms=1) assert daq.expect_esr(32, ("Command error", "No Error")) assert daq.total_channels == 1 diff --git a/tests/test_docs.py b/tests/test_docs.py index f9713c9e1..d7ed5f28b 100644 --- a/tests/test_docs.py +++ b/tests/test_docs.py @@ -6,9 +6,9 @@ import sys import time +from collections.abc import Generator from importlib.util import find_spec from pathlib import Path -from typing import Generator import pytest diff --git a/tests/test_extension_mixin.py b/tests/test_extension_mixin.py index 31fd4229a..3d273bb2c 100644 --- a/tests/test_extension_mixin.py +++ b/tests/test_extension_mixin.py @@ -11,8 +11,9 @@ import sys import warnings +from collections.abc import Iterator from pathlib import Path -from typing import Any, Iterator, List +from typing import Any, List from unittest import mock import pytest diff --git a/tests/test_helpers.py b/tests/test_helpers.py index 2a230ecda..dde43ea14 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -136,9 +136,11 @@ def test_check_network_connection() -> None: def test_check_port_connection() -> None: """Test checking a port connection.""" stdout = StringIO() - with redirect_stdout(stdout), mock.patch( - "socket.socket.connect", mock.MagicMock(return_value=None) - ), mock.patch("socket.socket.shutdown", mock.MagicMock(return_value=None)): + with ( + redirect_stdout(stdout), + mock.patch("socket.socket.connect", mock.MagicMock(return_value=None)), + mock.patch("socket.socket.shutdown", mock.MagicMock(return_value=None)), + ): assert check_port_connection("name", "127.0.0.1", 80, timeout_seconds=1) message = stdout.getvalue() assert "(name) >> checking if port 80 is open on 127.0.0.1" in message @@ -323,12 +325,15 @@ def test_get_visa_backend() -> None: try: _get_system_visa_info.cache_clear() - with mock.patch( - "pyvisa.util.get_system_details", - mock.MagicMock(return_value=testing_system_details), - ), mock.patch( - "platform.system", - mock.MagicMock(return_value="windows"), + with ( + mock.patch( + "pyvisa.util.get_system_details", + mock.MagicMock(return_value=testing_system_details), + ), + mock.patch( + "platform.system", + mock.MagicMock(return_value="windows"), + ), ): assert get_visa_backend("tests/sim_devices/devices.yaml") == "PyVISA-sim" assert get_visa_backend("py") == "PyVISA-py" diff --git a/tests/test_logging.py b/tests/test_logging.py index 96a949459..9a29e3f6b 100644 --- a/tests/test_logging.py +++ b/tests/test_logging.py @@ -5,8 +5,9 @@ import shutil import sys +from collections.abc import Generator from pathlib import Path -from typing import Generator, TYPE_CHECKING +from typing import TYPE_CHECKING import colorlog import pytest diff --git a/tests/test_pi_device.py b/tests/test_pi_device.py index 2923200fd..c920252b9 100644 --- a/tests/test_pi_device.py +++ b/tests/test_pi_device.py @@ -47,10 +47,13 @@ def test_pi_control( # noqa: PLR0915 with pytest.raises(AssertionError): scope.query_less_than("*OPC?", 1) - with mock.patch( - "pyvisa.resources.messagebased.MessageBasedResource.write_raw", - mock.MagicMock(side_effect=visa.VisaIOError(123)), - ), pytest.raises(visa.Error): + with ( + mock.patch( + "pyvisa.resources.messagebased.MessageBasedResource.write_raw", + mock.MagicMock(side_effect=visa.VisaIOError(123)), + ), + pytest.raises(visa.Error), + ): scope.write_raw(b"INVALID") with pytest.raises(visa.Error): scope.query_binary("INVALID?") @@ -60,20 +63,26 @@ def test_pi_control( # noqa: PLR0915 scope.query_raw_binary("INVALID?") with pytest.raises(SystemError): scope.query_raw_binary("EMPTY?") - with mock.patch( - "pyvisa.resources.messagebased.MessageBasedResource.write", - mock.MagicMock(side_effect=visa.VisaIOError(123)), - ), pytest.raises(visa.Error): + with ( + mock.patch( + "pyvisa.resources.messagebased.MessageBasedResource.write", + mock.MagicMock(side_effect=visa.VisaIOError(123)), + ), + pytest.raises(visa.Error), + ): scope.write("INVALID") with mock.patch( "pyvisa.resources.resource.Resource.wait_on_event", mock.MagicMock(return_value=object()), ): assert scope.wait_for_srq_event(1) - with mock.patch( - "pyvisa.resources.messagebased.MessageBasedResource.read", - mock.MagicMock(return_value="2"), - ), pytest.raises(SystemError): + with ( + mock.patch( + "pyvisa.resources.messagebased.MessageBasedResource.read", + mock.MagicMock(return_value="2"), + ), + pytest.raises(SystemError), + ): scope.write("FACTORY", opc=True) # from OPC return which was previously mocked and unable to be read assert scope.read() == "1" @@ -112,10 +121,13 @@ def test_pi_control( # noqa: PLR0915 assert scope.visa_timeout == old_timeout # Test closing a device that is powered off - with mock.patch( - "pyvisa.resources.resource.Resource.close", - mock.MagicMock(side_effect=visa.VisaIOError(123)), - ), pytest.warns(Warning): + with ( + mock.patch( + "pyvisa.resources.resource.Resource.close", + mock.MagicMock(side_effect=visa.VisaIOError(123)), + ), + pytest.warns(Warning), + ): scope._close() # noqa: SLF001 assert scope._visa_resource is None # noqa: SLF001 assert not scope._is_open # noqa: SLF001 diff --git a/tests/test_scopes.py b/tests/test_scopes.py index 9464a92c5..d33d09203 100644 --- a/tests/test_scopes.py +++ b/tests/test_scopes.py @@ -519,15 +519,19 @@ def test_tekscopepc( ): scope.save_screenshot("temp.png", device_folder="filename.txt") - with mock.patch( - "pyvisa.resources.messagebased.MessageBasedResource.read_raw", - mock.MagicMock(return_value=b"1234"), - ), mock.patch( - "pyvisa.resources.messagebased.MessageBasedResource.write", - mock.MagicMock(return_value=None), - ), mock.patch( - "pyvisa.resources.messagebased.MessageBasedResource.read", - mock.MagicMock(return_value="1"), # this mocks the *OPC? query return value + with ( + mock.patch( + "pyvisa.resources.messagebased.MessageBasedResource.read_raw", + mock.MagicMock(return_value=b"1234"), + ), + mock.patch( + "pyvisa.resources.messagebased.MessageBasedResource.write", + mock.MagicMock(return_value=None), + ), + mock.patch( + "pyvisa.resources.messagebased.MessageBasedResource.read", + mock.MagicMock(return_value="1"), # this mocks the *OPC? query return value + ), ): scope.enable_verification = False filename = pathlib.Path( @@ -542,10 +546,13 @@ def test_tekscopepc( assert f'FILESYSTEM:READFILE "./{filename.as_posix()}"' in stdout assert f'FILESYSTEM:DELETE "./{filename.as_posix()}"' in stdout - with mock.patch( - "pyvisa.resources.messagebased.MessageBasedResource.read_raw", - mock.MagicMock(return_value=b"5678"), - ), scope.temporary_enable_verification(True): + with ( + mock.patch( + "pyvisa.resources.messagebased.MessageBasedResource.read_raw", + mock.MagicMock(return_value=b"5678"), + ), + scope.temporary_enable_verification(True), + ): filename = pathlib.Path("temp.png") local_file = tmp_path / "folder" / filename scope.save_screenshot( diff --git a/tests/test_smu.py b/tests/test_smu.py index 37d229f6c..fbfe6b5fe 100644 --- a/tests/test_smu.py +++ b/tests/test_smu.py @@ -96,10 +96,13 @@ def test_smu( # noqa: PLR0915 with mock.patch("pyvisa.highlevel.VisaLibraryBase.clear", mock.MagicMock(return_value=None)): assert smu.query_expect_timeout("INVALID?", timeout_ms=1) == "" - with mock.patch( - "pyvisa.resources.messagebased.MessageBasedResource.query", - mock.MagicMock(side_effect=visa.errors.Error("custom error")), - ), pytest.raises(visa.errors.Error): + with ( + mock.patch( + "pyvisa.resources.messagebased.MessageBasedResource.query", + mock.MagicMock(side_effect=visa.errors.Error("custom error")), + ), + pytest.raises(visa.errors.Error), + ): smu.query_expect_timeout("INVALID?", timeout_ms=1) assert smu.expect_esr(32, ("Command error", "No Error")) @@ -185,8 +188,9 @@ def test_smu( # noqa: PLR0915 assert smu.address == "SMU2601B-HOSTNAME" assert smu.alias == "SMU-DEVICE" assert smu.connection_type == "TCPIP" - with mock.patch("socket.socket.connect", mock.MagicMock(return_value=None)), mock.patch( - "socket.socket.shutdown", mock.MagicMock(return_value=None) + with ( + mock.patch("socket.socket.connect", mock.MagicMock(return_value=None)), + mock.patch("socket.socket.shutdown", mock.MagicMock(return_value=None)), ): assert smu.wait_for_port_connection( 4000, wait_time=0.05, sleep_seconds=0, accept_immediate_connection=True @@ -286,10 +290,13 @@ def test_smu2450(device_manager: DeviceManager, capsys: pytest.CaptureFixture[st with mock.patch("pyvisa.highlevel.VisaLibraryBase.clear", mock.MagicMock(return_value=None)): assert smu.query_expect_timeout("INVALID?", timeout_ms=1) == "" - with mock.patch( - "pyvisa.resources.messagebased.MessageBasedResource.query", - mock.MagicMock(side_effect=visa.errors.Error("custom error")), - ), pytest.raises(visa.errors.Error): + with ( + mock.patch( + "pyvisa.resources.messagebased.MessageBasedResource.query", + mock.MagicMock(side_effect=visa.errors.Error("custom error")), + ), + pytest.raises(visa.errors.Error), + ): smu.query_expect_timeout("INVALID?", timeout_ms=1) assert smu.expect_esr(32, ("Command error", "No Error")) assert smu.all_channel_names_list == ("OUTPUT1",) diff --git a/tests/test_ss.py b/tests/test_ss.py index f06af0468..861d81efe 100644 --- a/tests/test_ss.py +++ b/tests/test_ss.py @@ -27,10 +27,13 @@ def test_ss(device_manager: DeviceManager) -> None: with mock.patch("pyvisa.highlevel.VisaLibraryBase.clear", mock.MagicMock(return_value=None)): assert switch.query_expect_timeout("INVALID?", timeout_ms=1) == "" - with mock.patch( - "pyvisa.resources.messagebased.MessageBasedResource.query", - mock.MagicMock(side_effect=visa.errors.Error("custom error")), - ), pytest.raises(visa.errors.Error): + with ( + mock.patch( + "pyvisa.resources.messagebased.MessageBasedResource.query", + mock.MagicMock(side_effect=visa.errors.Error("custom error")), + ), + pytest.raises(visa.errors.Error), + ): switch.query_expect_timeout("INVALID?", timeout_ms=1) assert switch.expect_esr(32, ("Command error", "No Error")) assert switch.total_channels == 576 diff --git a/tests/test_tm_devices.py b/tests/test_tm_devices.py index f449ea540..147a00d9d 100644 --- a/tests/test_tm_devices.py +++ b/tests/test_tm_devices.py @@ -3,11 +3,12 @@ import inspect from abc import ABC +from collections.abc import Generator # noinspection PyUnresolvedReferences from functools import _lru_cache_wrapper, cached_property # pyright: ignore [reportPrivateUsage] from types import FunctionType -from typing import Any, Generator, List, Set, Type +from typing import Any, List, Set, Type import pytest diff --git a/tests/test_unsupported_device_type.py b/tests/test_unsupported_device_type.py index 7c70a7b7e..b48f30e49 100644 --- a/tests/test_unsupported_device_type.py +++ b/tests/test_unsupported_device_type.py @@ -37,14 +37,17 @@ def test_unsupported_device_type_class(device_manager: DeviceManager) -> None: "UNSUPPORTED": CustomUnsupportedDeviceUnitTestOnly } - with pytest.warns( - UserWarning, - match="An unsupported device type is being added to the DeviceManager. " - "Not all functionality will be available in the device driver. " - "Please consider contributing to tm_devices to implement official support for " - "this device type.", - ), pytest.warns( - UserWarning, match='The "UNSUPPORTED" model is not supported by tm_devices' + with ( + pytest.warns( + UserWarning, + match="An unsupported device type is being added to the DeviceManager. " + "Not all functionality will be available in the device driver. " + "Please consider contributing to tm_devices to implement official support for " + "this device type.", + ), + pytest.warns( + UserWarning, match='The "UNSUPPORTED" model is not supported by tm_devices' + ), ): unsupported_device: CustomUnsupportedDeviceUnitTestOnly = ( device_manager.add_unsupported_device( @@ -62,14 +65,17 @@ def test_unsupported_device_type_class(device_manager: DeviceManager) -> None: assert device_manager.devices == {} # Load in the device from a config file - with pytest.warns( - UserWarning, - match="An unsupported device type is being added to the DeviceManager. " - "Not all functionality will be available in the device driver. " - "Please consider contributing to tm_devices to implement official support for " - "this device type.", - ), pytest.warns( - UserWarning, match='The "UNSUPPORTED" model is not supported by tm_devices' + with ( + pytest.warns( + UserWarning, + match="An unsupported device type is being added to the DeviceManager. " + "Not all functionality will be available in the device driver. " + "Please consider contributing to tm_devices to implement official support for " + "this device type.", + ), + pytest.warns( + UserWarning, match='The "UNSUPPORTED" model is not supported by tm_devices' + ), ): device_manager.load_config_file( Path(__file__).parent / "samples/unsupported_device_type_config.yaml"