From 841c15e56b68540c0e3c6ba589d75de69ebeaa3b Mon Sep 17 00:00:00 2001 From: "Felt, Nicholas" Date: Tue, 10 Sep 2024 12:37:30 -0700 Subject: [PATCH 1/7] style: Update code to pass ruff linting --- docs/macros.py | 3 +- examples/create_sine_waveform.py | 11 +-- examples/custom_filter.py | 8 +- examples/plot_example.py | 6 +- examples/plot_iq.py | 6 +- examples/simple_plot.py | 6 +- examples/simple_read.py | 3 +- pyproject.toml | 113 ++++++++++++++-------- scripts/contributor_setup.py | 6 +- src/tekhsi/__init__.py | 5 +- src/tekhsi/tek_hsi_connect.py | 35 ++++--- tests/conftest.py | 26 ++--- tests/server/tekhsi_test_server.py | 118 ++++++++++------------- tests/test_client.py | 150 +++++++++++++++++++---------- 14 files changed, 294 insertions(+), 202 deletions(-) diff --git a/docs/macros.py b/docs/macros.py index e3ec347..c90877d 100644 --- a/docs/macros.py +++ b/docs/macros.py @@ -162,7 +162,8 @@ def define_env(env: MacrosPlugin) -> None: """ # Read in the current package version number to use in templates and files with open( - pathlib.Path(f"{pathlib.Path(__file__).parents[1]}") / "pyproject.toml", "rb" + pathlib.Path(f"{pathlib.Path(__file__).parents[1]}") / "pyproject.toml", + "rb", ) as file_handle: pyproject_data = tomli.load(file_handle) package_version = "v" + pyproject_data["tool"]["poetry"]["version"] diff --git a/examples/create_sine_waveform.py b/examples/create_sine_waveform.py index 864a236..8a27d37 100644 --- a/examples/create_sine_waveform.py +++ b/examples/create_sine_waveform.py @@ -1,9 +1,11 @@ """An example script for creating a sine waveform and saving it to a file.""" -from tm_data_types import write_file, AnalogWaveform -import numpy as np import math +import numpy as np + +from tm_data_types import AnalogWaveform, write_file + length = 1000 # Record length of the waveform frequency = 40.0e-10 # Frequency of the sinewave cycles = 10 # Number of cycles of the sinewave in the waveform @@ -30,7 +32,4 @@ # waveform.y_axis_values = np.sin(2 * np.pi # * x_points) * amplitude / 2.0 -try: - write_file("sample_waveforms/test_sine.wfm", waveform) -except Exception as e: - print(e) +write_file("sample_waveforms/test_sine.wfm", waveform) diff --git a/examples/custom_filter.py b/examples/custom_filter.py index c59ad75..8cb0167 100644 --- a/examples/custom_filter.py +++ b/examples/custom_filter.py @@ -1,9 +1,11 @@ -"""An example script to connect to a scope, apply a custom filter to waveform data, and save to files.""" +"""A script to connect to a scope, apply a custom filter to waveform data, and save to files.""" -from tekhsi import TekHSIConnect, WaveformHeader -from tm_data_types import AnalogWaveform, write_file from typing import Dict, List +from tm_data_types import AnalogWaveform, write_file + +from tekhsi import TekHSIConnect, WaveformHeader + addr = "192.168.0.1" # Replace with the IP address of your instrument diff --git a/examples/plot_example.py b/examples/plot_example.py index 0d4f8b0..ff465fd 100644 --- a/examples/plot_example.py +++ b/examples/plot_example.py @@ -1,10 +1,12 @@ """An example script for connecting to a scope, retrieving waveform data, and plotting it.""" -from tekhsi import TekHSIConnect -from tm_data_types import AnalogWaveform import matplotlib.pyplot as plt import numpy as np +from tm_data_types import AnalogWaveform + +from tekhsi import TekHSIConnect + source = "ch1" address = "192.168.0.1" # Replace with the IP address of your instrument diff --git a/examples/plot_iq.py b/examples/plot_iq.py index 5c1b028..7005f85 100644 --- a/examples/plot_iq.py +++ b/examples/plot_iq.py @@ -1,10 +1,12 @@ """An example script for connecting to a Tek instrument, retrieving IQ waveform data, and plotting it.""" -from tekhsi import TekHSIConnect, AcqWaitOn -from tm_data_types import IQWaveform import matplotlib.pyplot as plt import numpy as np +from tm_data_types import IQWaveform + +from tekhsi import AcqWaitOn, TekHSIConnect + source = "ch1_iq" address = "192.168.0.1" # Replace with the IP address of your instrument decimate_count = 150 diff --git a/examples/simple_plot.py b/examples/simple_plot.py index 6e45dab..2cb54ea 100644 --- a/examples/simple_plot.py +++ b/examples/simple_plot.py @@ -1,9 +1,11 @@ """An example script for connecting to a scope, retrieving waveform data from multiple channels, and plotting it.""" -from tekhsi import TekHSIConnect -from tm_data_types import AnalogWaveform import matplotlib.pyplot as plt +from tm_data_types import AnalogWaveform + +from tekhsi import TekHSIConnect + address = "192.168.0.1" # Replace with the IP address of your instrument # Open connection to the instrument diff --git a/examples/simple_read.py b/examples/simple_read.py index ea5ee6c..5db66ee 100644 --- a/examples/simple_read.py +++ b/examples/simple_read.py @@ -1,9 +1,10 @@ """An example script for demonstrating reading waveform files and plotting the data.""" -from tm_data_types import read_file, AnalogWaveform, IQWaveform, DigitalWaveform import matplotlib.pyplot as plt import numpy as np +from tm_data_types import AnalogWaveform, DigitalWaveform, IQWaveform, read_file + # Read the waveform file file = read_file("sample_waveforms/test_sine.wfm") diff --git a/pyproject.toml b/pyproject.toml index bc155ec..d1c2e35 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,7 +16,7 @@ skip_empty = true branch = true cover_pylib = false omit = [ - ".tox/**/_*.py" + "_*.py" ] source = ["tekhsi"] @@ -27,10 +27,10 @@ recursive = true wrap-descriptions = 100 wrap-summaries = 0 -[tool.poetry] # TODO: fill in this section with more information +[tool.poetry] authors = ["Tektronix "] classifiers = [ - "Development Status :: 3 - Alpha", + "Development Status :: 4 - Beta", "Intended Audience :: Developers", "Intended Audience :: Science/Research", "Operating System :: OS Independent", @@ -46,6 +46,7 @@ keywords = [ "Test & Measurement" ] license = "Apache-2.0" +maintainers = ["Tektronix "] name = "TekHSI" readme = "README.md" repository = "https://github.com/tektronix/TekHSI" @@ -112,6 +113,7 @@ wheel = "^0.44" [tool.poetry.group.docs.dependencies] black = "^24.4.2" codespell = "^2.2.6" +griffe = "^1.1.0" mkdocs = "^1.6.0" mkdocs-ezglossary-plugin = "^1.6.10" mkdocs-gen-files = "^0.5.0" @@ -217,6 +219,16 @@ disable = [ "wrong-import-order" # caught by ruff ] +[tool.pylint.reports] +# Python expression which should return a score less than or equal to 10. You +# have access to the variables 'fatal', 'error', 'warning', 'refactor', +# 'convention', and 'info' which contain the number of messages in each category, +# as well as 'statement' which is the total number of statements analyzed. This +# score is used by the global evaluation report (RP0004). +evaluation = "max(0, 0 if fatal else 10.0 - ((float(5 * error + warning + refactor + convention + info) / statement) * 10))" +score = true +output-format = "text" # colorized could be another option + [tool.pyright] ignore = [ "**/output_*/**", @@ -226,12 +238,11 @@ ignore = [ pythonPlatform = "All" pythonVersion = "3.8" reportCallInDefaultInitializer = "error" -reportImplicitOverride = "none" # this check is not needed -# TODO: turn on the check for implicit string concatenation -reportImplicitStringConcatenation = "none" # this is allowed by this project's formatting standard +reportImplicitOverride = "error" +reportImplicitStringConcatenation = "error" reportImportCycles = "none" # other analysis tools catch these more effectively reportMissingSuperCall = "none" # this can be ignored since this would break unit tests if handled incorrectly -reportPropertyTypeMismatch = "none" # the auto-generated properties can have mismatches +reportPropertyTypeMismatch = "error" reportShadowedImports = "error" reportUninitializedInstanceVariable = "error" reportUnnecessaryTypeIgnoreComment = "error" @@ -241,16 +252,10 @@ stubPath = "src" typeCheckingMode = "strict" useLibraryCodeForTypes = true -# Pytest configuration [tool.pytest.ini_options] addopts = "--order-scope=module --cov-config=pyproject.toml" doctest_optionflags = "ELLIPSIS NORMALIZE_WHITESPACE IGNORE_EXCEPTION_DETAIL" -filterwarnings = [ - "ignore:'xdrlib' is deprecated:DeprecationWarning", - "ignore::DeprecationWarning:pkg_resources", - "ignore:GPIB library not found:UserWarning", - "ignore:pkg_resources is deprecated:DeprecationWarning" -] +filterwarnings = [] junit_family = "xunit2" junit_logging = "all" markers = [ @@ -265,38 +270,69 @@ pytest_report_title = {skip_if_set = true, value = "Test Results"} [tool.ruff] line-length = 100 -namespace-packages = ["examples/**", "scripts/**", "tests/**"] +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 -[tool.ruff.lint] # TODO: Update this section +[tool.ruff.lint] allowed-confusables = ["¸", "×"] fixable = ["ALL"] flake8-pytest-style = {mark-parentheses = false} flake8-quotes = {docstring-quotes = "double"} ignore = [ - "ANN101", # Missing type annotation for self in method - "ANN102", # Missing type annotation for cls in method + "ANN", # flake8-annotations # TODO: enable this "ANN401", # Dynamically typed expressions (typing.Any) are disallowed in *args and **kwargs - "COM812", # Trailing comma missing - "EM102", # Exception must not use an f-string literal, assign to variable first - "FA100", # Missing `from __future__ import annotations`, but uses ... - "FBT", # flake8-boolean-trap + "COM812", # Trailing comma missing (handled by formatter) + "D", # pydocstyle # TODO: enable this + "EM101", # Exception must not use a string literal, assign to variable first # TODO: enable this + "EM102", # Exception must not use an f-string literal, assign to variable first (covered by TRY003) + "FA100", # Missing `from __future__ import annotations`, but uses ... # TODO: enable this + "FBT", # flake8-boolean-trap # TODO: enable this "FIX002", # Line contains TO DO "ISC001", # single-line-implicit-string-concatenation (handled by formatter) - "PTH109", # `os.getcwd()` should be replaced by `Path.cwd()` - "PTH123", # `open()` should be replaced by `Path.open()` - "PTH207", # Replace `iglob` with `Path.glob` or `Path.rglob` - "PYI021", # Docstrings should not be included in stubs + "PTH", # flake8-use-pathlib # TODO: enable this + "RET504", # Unnecessary assignment to ... before return statement # TODO: enable this + "SLF001", # Private member accessed # TODO: enable this "T20", # flake8-print "TD002", # Missing author in TO DO "TD003", # Missing issue link on the line following this TO DO - "TRY301", # Abstract raise to an inner function - "UP006", # Use {to} instead of {from} for type annotation - "UP007", # Use `X | Y` for type annotations + "TRY003", # Avoid specifying long messages outside the exception class # TODO: enable this "UP024", # Replace aliased errors with `OSError` "UP037" # Remove quotes from type annotation ] +pydocstyle = {convention = "google"} +pylint = {max-args = 7} +# https://beta.ruff.rs/docs/rules/ +select = [ + "ALL" +] +task-tags = ["FIXME", "FUTURE", "RELIC", "TODO"] + +[tool.ruff.lint.isort] +force-sort-within-sections = false +known-first-party = [ + "conftest", + "tekhsi" +] +lines-between-types = 1 +order-by-type = false + +[tool.ruff.lint.per-file-ignores] +"examples/**" = [ + "E501", # Line too long + "ERA001", # Found commented-out code + "S101", # Use of assert detected + "SIM117" # Use a single `with` statement with multiple contexts instead of nested `with` statements +] +"src/tekhsi/_tek_highspeed_server_pb2*.py*" = [ + "ALL" +] +"tests/**" = [ + "PLC1901", # compare-to-empty-string + "PLR2004", # Magic value used in comparison + "S101" # Use of assert detected +] [tool.semantic_release] version_toml = [ @@ -349,6 +385,7 @@ install_command = python -I -m pip install --upgrade --upgrade-strategy=eager {o deps = poetry 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 @@ -358,7 +395,7 @@ commands = !tests: poetry build --output=dist_{envname} !tests: twine check --strict dist_{envname}/* !tests: pre-commit run --all-files -# !tests: pytest -vv --doctest-modules --doctest-report=ndiff --showlocals --junitxml={tox_root}/.results_{envname}/results_doctests.xml --self-contained-html --html={tox_root}/.results_{envname}/results_doctests.html src + # !tests: pytest -vv --doctest-modules --doctest-report=ndiff --showlocals --junitxml={tox_root}/.results_{envname}/results_doctests.xml --self-contained-html --html={tox_root}/.results_{envname}/results_doctests.html src pytest -vv -k "not test_docs" --showlocals --cov --junitxml={tox_root}/.results_{envname}/results.xml --cov-report=term --cov-report=xml:{tox_root}/.coverage_{envname}.xml --cov-report=html:{tox_root}/.results_{envname}/html --self-contained-html --html={tox_root}/.results_{envname}/results.html [testenv:tests] @@ -376,20 +413,20 @@ commands_pre = [testenv:docs] basepython = {env:DOC_PYTHON_VERSION} deps = - -rdocs/requirements.txt + -rdocs/requirements.txt commands_pre = - nodeenv --python-virtualenv --clean-src + nodeenv --python-virtualenv --clean-src commands = - python -c "import shutil; shutil.rmtree('.results_{envname}', ignore_errors=True)" - mkdocs --verbose build --site-dir .results_{envname} + python -c "import shutil; shutil.rmtree('.results_{envname}', ignore_errors=True)" + mkdocs --verbose build --site-dir .results_{envname} [testenv:doctests] basepython = {env:DOC_PYTHON_VERSION} deps = - -rdocs/requirements.txt - -rtests/requirements.txt + -rdocs/requirements.txt + -rtests/requirements.txt commands_pre = - nodeenv --python-virtualenv --clean-src + nodeenv --python-virtualenv --clean-src commands = - pytest -v -k "test_docs" --showlocals --junitxml={tox_root}/.results_{envname}/results.xml --self-contained-html --html={tox_root}/.results_{envname}/results.html + pytest -v -k "test_docs" --showlocals --junitxml={tox_root}/.results_{envname}/results.xml --self-contained-html --html={tox_root}/.results_{envname}/results.html """ diff --git a/scripts/contributor_setup.py b/scripts/contributor_setup.py index bceda25..45f566d 100644 --- a/scripts/contributor_setup.py +++ b/scripts/contributor_setup.py @@ -48,7 +48,7 @@ def main() -> None: starting_dir = Path.cwd() try: if RUNNING_IN_VIRTUALENV: - raise IndexError + 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): @@ -80,7 +80,7 @@ def main() -> None: f"{virtual_env_dir}/{'bin' if RUNNING_ON_LINUX else 'Scripts'}/**/python*", recursive=True, ), - ) + ), ) python_executable = files[0] commands_to_send = ( @@ -88,7 +88,7 @@ def main() -> None: f"{python_executable} -m poetry install", f"{python_executable} -m nodeenv --python-virtualenv --clean-src", f"{python_executable} -m pre_commit install --install-hooks", - # f"{python_executable} -m tox -e tests", + # f"{python_executable} -m tox -e tests", # noqa: ERA001 ) for command in commands_to_send: _run_cmd_in_subprocess(command) diff --git a/src/tekhsi/__init__.py b/src/tekhsi/__init__.py index e9f7fd7..0797df2 100644 --- a/src/tekhsi/__init__.py +++ b/src/tekhsi/__init__.py @@ -5,10 +5,9 @@ from importlib.metadata import version -from tekhsi.helpers import PACKAGE_NAME -from tekhsi.tek_hsi_connect import TekHSIConnect -from tekhsi.tek_hsi_connect import AcqWaitOn from tekhsi._tek_highspeed_server_pb2 import WaveformHeader # pylint: disable= no-name-in-module +from tekhsi.helpers import PACKAGE_NAME +from tekhsi.tek_hsi_connect import AcqWaitOn, TekHSIConnect # Read version from installed package. __version__ = version(PACKAGE_NAME) diff --git a/src/tekhsi/tek_hsi_connect.py b/src/tekhsi/tek_hsi_connect.py index 9849826..b5fe98c 100644 --- a/src/tekhsi/tek_hsi_connect.py +++ b/src/tekhsi/tek_hsi_connect.py @@ -6,24 +6,28 @@ import uuid from atexit import register +from contextlib import contextmanager from enum import Enum - -from typing import List, Optional, Dict +from typing import Dict, List, Optional import grpc import numpy as np from tm_data_types import ( AnalogWaveform, + DigitalWaveform, IQWaveform, IQWaveformMetaInfo, Waveform, - DigitalWaveform, ) -from tekhsi.helpers.functions import print_with_timestamp -from tekhsi._tek_highspeed_server_pb2 import ConnectRequest, WaveformHeader, WaveformRequest # pylint: disable=no-name-in-module +from tekhsi._tek_highspeed_server_pb2 import ( # pylint: disable=no-name-in-module + ConnectRequest, + WaveformHeader, + WaveformRequest, +) from tekhsi._tek_highspeed_server_pb2_grpc import ConnectStub, NativeDataStub +from tekhsi.helpers.functions import print_with_timestamp class AcqWaitOn(Enum): @@ -128,7 +132,6 @@ def __exit__(self, exc_type, exc_val, exc_tb): exc_val: The exception value. exc_tb: The traceback object. """ - # Required for "with" command to work with this class self._is_exiting = True @@ -141,7 +144,7 @@ def __exit__(self, exc_type, exc_val, exc_tb): if self._instrument and self._sum_count > 0: print_with_timestamp( f"Average Update Rate:{(1 / (self._sum_acq_time / self._sum_count)):.2f}, " - f"Data Rate:{(self._sum_data_rate / self._sum_count):.2f}Mbs" + f"Data Rate:{(self._sum_data_rate / self._sum_count):.2f}Mbs", ) ################################################################################################ @@ -247,7 +250,8 @@ def any_acq(prevheader, currentheader) -> bool: @staticmethod def any_horizontal_change( - prevheader: Dict[str, WaveformHeader], currentheader: Dict[str, WaveformHeader] + prevheader: Dict[str, WaveformHeader], + currentheader: Dict[str, WaveformHeader], ) -> bool: """Prebuilt acq acceptance filter that accepts only acqs with changes to horizontal settings. @@ -274,7 +278,8 @@ def any_horizontal_change( @staticmethod def any_vertical_change( - prevheader: Dict[str, WaveformHeader], currentheader: Dict[str, WaveformHeader] + prevheader: Dict[str, WaveformHeader], + currentheader: Dict[str, WaveformHeader], ) -> bool: """Prebuilt acq acceptance filter that accepts only acqs with changes to vertical settings. @@ -308,7 +313,6 @@ def active_symbols(self, symbols: List[str]) -> None: def close(self): """Close and clean up gRPC connection.""" - if not self._connected: return @@ -400,7 +404,8 @@ def get_data(self, name: str) -> Optional[Waveform]: self._lock_getdata.acquire() try: retval = self._datacache.get( - name.lower(), None + name.lower(), + None, ) # Return None if cached data is not found finally: self._lock_getdata.release() @@ -509,7 +514,7 @@ def _instrumentation(self, acqtime, transfertime, datasize, datawidth) -> None: self._sum_count += 1 print( f"UpdateRate:{(1 / acqtime):.2f}," - f"Data Rate:{((datasize * 8 / 1e6) / transfertime):.2f}Mbs,Data Width:{datawidth}" + f"Data Rate:{((datasize * 8 / 1e6) / transfertime):.2f}Mbs,Data Width:{datawidth}", ) @staticmethod @@ -531,7 +536,8 @@ def _is_header_value(header: WaveformHeader) -> bool: def _finished_with_data_access(self) -> None: """Releases access to instrument data - this is required - to allow the instrument to continue acquiring""" + to allow the instrument to continue acquiring + """ if not self._in_wait_for_data: return @@ -890,7 +896,8 @@ def _wait_next_acq(self): @register def _terminate(): """Cleans up mess on termination if possible - this is required - to keep the scope from hanging""" + to keep the scope from hanging + """ for key in TekHSIConnect._connections: # pylint:disable=consider-using-dict-items with contextlib.suppress(Exception): if TekHSIConnect._connections[key]._holding_scope_open: diff --git a/tests/conftest.py b/tests/conftest.py index 6d446c0..4abb89f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,18 +1,20 @@ -from abc import ABC - -import grpc import os -import psutil -import pytest import subprocess import sys import time + +from abc import ABC from io import StringIO from typing import List +import grpc +import psutil +import pytest + +from tm_data_types import Waveform + from tekhsi._tek_highspeed_server_pb2_grpc import ConnectStub from tekhsi.tek_hsi_connect import TekHSIConnect -from tm_data_types import Waveform class DerivedWaveform(Waveform, ABC): @@ -75,7 +77,7 @@ def __exit__(self, exc_type, exc_value, traceback): self.server_process.wait() -@pytest.fixture +@pytest.fixture() def capture_stdout(): """Fixture to capture the standard output.""" old_stdout = sys.stdout @@ -84,7 +86,7 @@ def capture_stdout(): sys.stdout = old_stdout -@pytest.fixture +@pytest.fixture() def derived_waveform_handler(): """Fixture to create an instance of DerivedWaveformHandler. @@ -94,7 +96,7 @@ def derived_waveform_handler(): return DerivedWaveformHandler() -@pytest.fixture +@pytest.fixture() def expected_header(): """Fixture to provide a sample waveform header. @@ -118,7 +120,7 @@ def expected_header(): } -@pytest.fixture +@pytest.fixture() def grpc_channel(): # pylint: disable=useless-suppression """Create a gRPC channel to the test server.""" channel = grpc.insecure_channel("localhost:5000") @@ -126,7 +128,7 @@ def grpc_channel(): # pylint: disable=useless-suppression channel.close() -@pytest.fixture +@pytest.fixture() def grpc_stub(grpc_channel): # pylint: disable=redefined-outer-name """Create a gRPC stub for the Connect service.""" return ConnectStub(grpc_channel) @@ -139,7 +141,7 @@ def start_test_server(): yield -@pytest.fixture +@pytest.fixture() def tekhsi_client(): """Create a TekHSIConnect client.""" return TekHSIConnect("localhost:5000") diff --git a/tests/server/tekhsi_test_server.py b/tests/server/tekhsi_test_server.py index 6e68919..062687b 100644 --- a/tests/server/tekhsi_test_server.py +++ b/tests/server/tekhsi_test_server.py @@ -8,16 +8,18 @@ forward while remaining backwards compatible. """ +import math import sys -import grpc import time -import math + +from concurrent import futures +from enum import Enum from threading import Lock, Thread + +import grpc import numpy as np -from enum import Enum -from concurrent import futures -from tm_data_types import AnalogWaveform, IQWaveform, DigitalWaveform +from tm_data_types import AnalogWaveform, DigitalWaveform, IQWaveform import tekhsi._tek_highspeed_server_pb2 as tekhsi_pb2 import tekhsi._tek_highspeed_server_pb2_grpc as tekhsi_pb2_grpc @@ -140,7 +142,7 @@ def __init__( [ (math.cos(increment * index) / 2.0) * amplitude - offset for index in range(length) - ] + ], ) elif encoding == WfmEncoding.Square: ampl2 = amplitude / 2 @@ -148,7 +150,7 @@ def __init__( [ ampl2 if (math.cos(increment * index) / 2.0) >= 0 else -ampl2 for index in range(length) - ] + ], ) elif encoding == WfmEncoding.PRBS7: pass @@ -242,8 +244,6 @@ def GetWaveform(self, request, context): context : Any This contains information relevant to the current gRPC call. """ - global connect_server - global verbose if verbose: print(f"TekHSI_NormalizedDataServer.GetWaveform({request.sourcename})") try: @@ -255,9 +255,9 @@ def GetWaveform(self, request, context): reply = tekhsi_pb2.NormalizedReply( headerordata=tekhsi_pb2.NormalizedReply.DataOrHeaderAccess( chunk=tekhsi_pb2.NormalizedReply.WaveformSampleChunk( - data=wfm._vertical_data[cur : cur + chunksize] - ) - ) + data=wfm._vertical_data[cur : cur + chunksize], + ), + ), ) yield reply reply = tekhsi_pb2.NormalizedReply() @@ -279,14 +279,11 @@ def GetHeader(self, request, context): context : Any This contains information relevant to the current gRPC call. - Returns + Returns: ------- NormalizedReply The return reply contains the status + the requested header information. """ - global connect_server - global verbose - global acq_id if verbose: print(f"TekHSI_NormalizedDataServer.GetHeader({request.sourcename})") try: @@ -319,7 +316,7 @@ def GetHeader(self, request, context): except Exception as e: print(e) return tekhsi_pb2.NormalizedReply( - status=tekhsi_pb2.WfmReplyStatus.Value("WFMREPLYSTATUS_FAILURE") + status=tekhsi_pb2.WfmReplyStatus.Value("WFMREPLYSTATUS_FAILURE"), ) @@ -343,14 +340,12 @@ def GetWaveform(self, request, context): context : Any This contains information relevant to the current gRPC call. - Returns + Returns: ------- NativeReply The return reply contains the status. The yield reply, returns each data chunk. """ - global connect_server - global verbose if verbose: print(f"TekHSI_NativeDataServer.GetWaveform({request.sourcename})") try: @@ -363,9 +358,9 @@ def GetWaveform(self, request, context): reply = tekhsi_pb2.RawReply( headerordata=tekhsi_pb2.RawReply.DataOrHeaderAccess( chunk=tekhsi_pb2.RawReply.WaveformSampleByteChunk( - data=raw_bytes[cur : cur + chunksize] - ) - ) + data=raw_bytes[cur : cur + chunksize], + ), + ), ) yield reply reply = tekhsi_pb2.RawReply() @@ -386,14 +381,11 @@ def GetHeader(self, request, context): context : Any This contains information relevant to the current gRPC call. - Returns + Returns: ------- NativeReply The return reply contains the status + the requested header information. """ - global connect_server - global verbose - global acq_id if verbose: print(f"TekHSI_NativeDataServer.GetHeader({request.sourcename})") try: @@ -496,7 +488,7 @@ def Connect(self, request, context): if self._connections.get(request.name): context.set_code(grpc.StatusCode.ALREADY_EXISTS) return tekhsi_pb2.ConnectReply( - status=tekhsi_pb2.ConnectStatus.Value("CONNECTSTATUS_INUSE_FAILURE") + status=tekhsi_pb2.ConnectStatus.Value("CONNECTSTATUS_INUSE_FAILURE"), ) self._connections[request.name] = True context.set_code(grpc.StatusCode.OK) @@ -506,13 +498,13 @@ def Connect(self, request, context): if verbose: print(f'connection successful - "{request.name}"') return tekhsi_pb2.ConnectReply( - status=tekhsi_pb2.ConnectStatus.Value("CONNECTSTATUS_SUCCESS") + status=tekhsi_pb2.ConnectStatus.Value("CONNECTSTATUS_SUCCESS"), ) except Exception as e: if verbose: print(f'** Exception:{e}: Connect - "{request.name}"') return tekhsi_pb2.ConnectReply( - status=tekhsi_pb2.ConnectStatus.Value("CONNECTSTATUS_UNSPECIFIED") + status=tekhsi_pb2.ConnectStatus.Value("CONNECTSTATUS_UNSPECIFIED"), ) def Disconnect(self, request, context): @@ -533,23 +525,22 @@ def Disconnect(self, request, context): print(f'Disconnect Success "{request.name}"') context.set_code(grpc.StatusCode.OK) return tekhsi_pb2.ConnectReply( - status=tekhsi_pb2.ConnectStatus.Value("CONNECTSTATUS_SUCCESS") - ) - else: - self._connections.clear() - if self._new_data: - self.FinishedWithDataAccess(request, context) - # force a cleanup - self._new_data = False - self._dataaccess_allowed = False - if mutex.locked(): - mutex.release() - context.set_code(grpc.StatusCode.OK) - if verbose: - print(f'Disconnect Success - but used a bad name {request.name}"') - return tekhsi_pb2.ConnectReply( - status=tekhsi_pb2.ConnectStatus.Value("CONNECTSTATUS_UNSPECIFIED") + status=tekhsi_pb2.ConnectStatus.Value("CONNECTSTATUS_SUCCESS"), ) + self._connections.clear() + if self._new_data: + self.FinishedWithDataAccess(request, context) + # force a cleanup + self._new_data = False + self._dataaccess_allowed = False + if mutex.locked(): + mutex.release() + context.set_code(grpc.StatusCode.OK) + if verbose: + print(f'Disconnect Success - but used a bad name {request.name}"') + return tekhsi_pb2.ConnectReply( + status=tekhsi_pb2.ConnectStatus.Value("CONNECTSTATUS_UNSPECIFIED"), + ) except Exception as e: if self._new_data: self.FinishedWithDataAccess(request, context) @@ -562,7 +553,7 @@ def Disconnect(self, request, context): if verbose: print(f'** Exception: Disconnect - "{request.name} - {e}"') return tekhsi_pb2.ConnectReply( - status=tekhsi_pb2.ConnectStatus.Value("CONNECTSTATUS_UNSPECIFIED") + status=tekhsi_pb2.ConnectStatus.Value("CONNECTSTATUS_UNSPECIFIED"), ) def RequestNewSequence(self, request, context): @@ -573,7 +564,6 @@ def RequestNewSequence(self, request, context): else: print(f'RequestNewSequence Failed - No Connection "{request.name}"') - global connect_server mutex.acquire() if verbose: print("mutex-acquired: RequestNewSequence") @@ -584,7 +574,7 @@ def RequestNewSequence(self, request, context): print("mutex-released: RequestNewSequence") context.set_code(grpc.StatusCode.OK) return tekhsi_pb2.ConnectReply( - status=tekhsi_pb2.ConnectStatus.Value("CONNECTSTATUS_SUCCESS") + status=tekhsi_pb2.ConnectStatus.Value("CONNECTSTATUS_SUCCESS"), ) except Exception as e: context.set_code(grpc.StatusCode.FAILED_PRECONDITION) @@ -592,7 +582,7 @@ def RequestNewSequence(self, request, context): print(e) print(f'** Exception as: RequestNewSequence - "{request.name}"') return tekhsi_pb2.ConnectReply( - status=tekhsi_pb2.ConnectStatus.Value("CONNECTSTATUS_UNSPECIFIED") + status=tekhsi_pb2.ConnectStatus.Value("CONNECTSTATUS_UNSPECIFIED"), ) def RequestAvailableNames(self, request, context): @@ -613,7 +603,7 @@ def RequestAvailableNames(self, request, context): print(f'** Exception: RequestNewSequence - "{request.name}"') context.set_code(grpc.StatusCode.FAILED_PRECONDITION) return tekhsi_pb2.AvailableNamesReply( - status=tekhsi_pb2.ConnectStatus.Value("CONNECTSTATUS_UNSPECIFIED") + status=tekhsi_pb2.ConnectStatus.Value("CONNECTSTATUS_UNSPECIFIED"), ) def WaitForDataAccess(self, request, context): @@ -621,10 +611,10 @@ def WaitForDataAccess(self, request, context): if not self._connections: if verbose: print("WaitForDataAccess Success - requested with no connections active") - return + return None if not self._connections.get(request.name) and len(request.name) > 0: - return + return None while not self._new_data: time.sleep(0.001) @@ -635,14 +625,14 @@ def WaitForDataAccess(self, request, context): self._dataaccess_allowed = True context.set_code(grpc.StatusCode.OK) return tekhsi_pb2.ConnectReply( - status=tekhsi_pb2.ConnectStatus.Value("CONNECTSTATUS_SUCCESS") + status=tekhsi_pb2.ConnectStatus.Value("CONNECTSTATUS_SUCCESS"), ) except Exception as e: if verbose: print(e) print(f'** Exception: WaitForDataAccess - "{request.name}"') return tekhsi_pb2.ConnectReply( - status=tekhsi_pb2.ConnectStatus.Value("CONNECTSTATUS_UNSPECIFIED") + status=tekhsi_pb2.ConnectStatus.Value("CONNECTSTATUS_UNSPECIFIED"), ) def FinishedWithDataAccess(self, request, context): @@ -660,21 +650,20 @@ def FinishedWithDataAccess(self, request, context): print(f'FinishedWithDataAccess Bad Connection Name "{request.name}"') context.set_code(grpc.StatusCode.OK) return tekhsi_pb2.ConnectReply( - status=tekhsi_pb2.ConnectStatus.Value("CONNECTSTATUS_SUCCESS") - ) - else: - if verbose: - print(f'FinishedWithDataAccess Failed "{request.name} - No WaitForDataPending"') - return tekhsi_pb2.ConnectReply( - status=tekhsi_pb2.ConnectStatus.Value("CONNECTSTATUS_UNSPECIFIED") + status=tekhsi_pb2.ConnectStatus.Value("CONNECTSTATUS_SUCCESS"), ) + if verbose: + print(f'FinishedWithDataAccess Failed "{request.name} - No WaitForDataPending"') + return tekhsi_pb2.ConnectReply( + status=tekhsi_pb2.ConnectStatus.Value("CONNECTSTATUS_UNSPECIFIED"), + ) except Exception as e: if verbose: print(e) print(f'** Exception: FinishedWithDataAccess - "{request.name}"') return tekhsi_pb2.ConnectReply( - status=tekhsi_pb2.ConnectStatus.Value("CONNECTSTATUS_UNSPECIFIED") + status=tekhsi_pb2.ConnectStatus.Value("CONNECTSTATUS_UNSPECIFIED"), ) @@ -686,7 +675,6 @@ def periodic_data_creation(): If you want to change the named sets of data returned you should modify 'make_new_data()' """ while True: - global connect_server global acq_id try: mutex.acquire() @@ -704,7 +692,7 @@ def make_new_data(): symbol names and associated data. This is called on initialization of the TekHSI_Connect service and periodically to create new data. The symbols defined here well be seen by the client. - Returns + Returns: ------- dict Returns a name/value dictionary defining a set of symbols and their associated waveforms. diff --git a/tests/test_client.py b/tests/test_client.py index 834504f..666e3b6 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -1,6 +1,7 @@ """Unit tests for the TekHSI client functionality.""" import sys + from io import StringIO from unittest.mock import patch @@ -8,20 +9,32 @@ import pytest from tm_data_types import AnalogWaveform, IQWaveform, DigitalWaveform -from tekhsi.helpers import print_with_timestamp -from tekhsi.tek_hsi_connect import TekHSIConnect, AcqWaitOn -from tekhsi._tek_highspeed_server_pb2 import ConnectRequest, ConnectStatus, WaveformHeader # pylint: disable=no-name-in-module +from tm_data_types import AnalogWaveform, DigitalWaveform, IQWaveform + from conftest import DerivedWaveform, DerivedWaveformHandler +from tekhsi._tek_highspeed_server_pb2 import ( # pylint: disable=no-name-in-module + ConnectRequest, + ConnectStatus, + WaveformHeader, +) +from tekhsi.helpers import print_with_timestamp +from tekhsi.tek_hsi_connect import AcqWaitOn, TekHSIConnect @pytest.mark.parametrize( - "instrument, sum_count, sum_acq_time, sum_data_rate, expected_output", + ("instrument", "sum_count", "sum_acq_time", "sum_data_rate", "expected_output"), [ (True, 5, 10.0, 50.0, "Average Update Rate:0.50, Data Rate:10.00Mbs"), ], ) def test_server_connection( - tekhsi_client, capsys, instrument, sum_count, sum_acq_time, sum_data_rate, expected_output + tekhsi_client, + capsys, + instrument, + sum_count, + sum_acq_time, + sum_data_rate, + expected_output, ): """Test the server connection using the TekHSI client. @@ -54,7 +67,7 @@ def test_server_connection( @pytest.mark.parametrize( - "prevheader, currentheader, expected", + ("prevheader", "currentheader", "expected"), [ ({}, {}, True), # Always returns True ], @@ -72,7 +85,7 @@ def test_any_acq(prevheader, currentheader, expected): @pytest.mark.parametrize( - "prevheader, currentheader, expected", + ("prevheader", "currentheader", "expected"), [ # No changes ( @@ -162,7 +175,7 @@ def test_any_horizontal_change(prevheader, currentheader, expected): @pytest.mark.parametrize( - "prevheader, currentheader, expected", + ("prevheader", "currentheader", "expected"), [ # No changes ( @@ -230,7 +243,7 @@ def test_any_vertical_change(prevheader, currentheader, expected): @pytest.mark.parametrize( - "acq_filter, expected_exception, expected_message", + ("acq_filter", "expected_exception", "expected_message"), [ ({"name": "TestFilter"}, None, None), (None, ValueError, "Filter cannot be None"), @@ -258,7 +271,7 @@ def test_set_acq_filter(tekhsi_client, acq_filter, expected_exception, expected_ @pytest.mark.parametrize( - "cache_enabled, data_cache, name, expected_result", + ("cache_enabled", "data_cache", "name", "expected_result"), [ (True, {"test_data": "waveform_data"}, "test_data", "waveform_data"), # Valid case (True, {"test_data": "waveform_data"}, "nonexistent_data", None), # Data not found @@ -288,8 +301,15 @@ def test_get_data(tekhsi_client, cache_enabled, data_cache, name, expected_resul @pytest.mark.parametrize( - "cache_enabled, wait_for_data_count, acqcount, expected_wait_for_data_count, expected_lastacqseen, " - "expected_output, verbose", + ( + "cache_enabled", + "wait_for_data_count", + "acqcount", + "expected_wait_for_data_count", + "expected_lastacqseen", + "expected_output", + "verbose", + ), [ (True, 1, 5, 0, 5, None, True), # Valid case: Cache enabled and data count decrement (True, 0, 5, 0, 0, "** done_with_data called when no wait_for_data pending", True), @@ -379,8 +399,18 @@ def mock_method(): @pytest.mark.parametrize( - "cache_enabled, wait_on, after, datacache, acqcount, acqtime, lastacqseen, expected_wait_for_data_count, " - "expected_lastacqseen, expected_output", + ( + "cache_enabled", + "wait_on", + "after", + "datacache", + "acqcount", + "acqtime", + "lastacqseen", + "expected_wait_for_data_count", + "expected_lastacqseen", + "expected_output", + ), [ ( True, @@ -450,7 +480,8 @@ def test_wait_for_data( # Mocking print_with_timestamp if expected_output is provided if expected_output: with patch( - "tekhsi.tekhsi_client.print_with_timestamp", side_effect=lambda x: x + "tekhsi.tekhsi_client.print_with_timestamp", + side_effect=lambda x: x, ) as mock_print: connection.wait_for_data(wait_on, after) mock_print.assert_called_once_with(expected_output) @@ -485,12 +516,12 @@ def test_available_symbols(tekhsi_client, expected_symbols): # Check that the symbols match the expected values from the test server assert sorted(symbols) == sorted( - expected_symbols + expected_symbols, ), f"Expected symbols {expected_symbols}, got {symbols}" @pytest.mark.parametrize( - "initial_value, new_value, expected_value", + ("initial_value", "new_value", "expected_value"), [ (True, False, False), # Change from True to False (False, True, True), # Change from False to True @@ -526,7 +557,7 @@ def test_instrumentation_enabled(tekhsi_client, initial_value, new_value, expect @pytest.mark.parametrize( - "initial_symbols, expected_symbols", + ("initial_symbols", "expected_symbols"), [ (["source1", "source2"], ["source1", "source2"]), # Valid case ([], []), # No sources @@ -556,7 +587,7 @@ def test_source_names(tekhsi_client, initial_symbols, expected_symbols): @pytest.mark.parametrize( - "header, expected", + ("header", "expected"), [ (WaveformHeader(noofsamples=100, sourcewidth=1, hasdata=True), True), (WaveformHeader(noofsamples=0, sourcewidth=1, hasdata=True), False), @@ -576,7 +607,17 @@ def test_is_header_value(header, expected): @pytest.mark.parametrize( - "cache_enabled, wait_on, after, datacache, acqcount, acqtime, lastacqseen, expected_wait_for_data_count, expected_lastacqseen", + ( + "cache_enabled", + "wait_on", + "after", + "datacache", + "acqcount", + "acqtime", + "lastacqseen", + "expected_wait_for_data_count", + "expected_lastacqseen", + ), [ (True, AcqWaitOn.Time, 1, {"data": "value"}, 0, 0, 0, 1, 0), (True, AcqWaitOn.Time, 0, {"data": "value"}, 0, 1, 0, 1, 0), @@ -629,7 +670,14 @@ def test_wait_for_data_acq_time( @pytest.mark.parametrize( - "cache_enabled, wait_on, datacache, acqcount, expected_wait_for_data_count, expected_lastacqseen", + ( + "cache_enabled", + "wait_on", + "datacache", + "acqcount", + "expected_wait_for_data_count", + "expected_lastacqseen", + ), [ (True, AcqWaitOn.AnyAcq, {"data": "value"}, 1, 1, 0), # acqcount > 0 ], @@ -674,7 +722,15 @@ def test_wait_for_data_any_acq( @pytest.mark.parametrize( - "cache_enabled, wait_on, datacache, acqcount, lastacqseen, expected_wait_for_data_count, expected_lastacqseen", + ( + "cache_enabled", + "wait_on", + "datacache", + "acqcount", + "lastacqseen", + "expected_wait_for_data_count", + "expected_lastacqseen", + ), [ (True, AcqWaitOn.NewData, {"data": "value"}, 5, 0, 1, 0), ], @@ -723,7 +779,7 @@ def test_wait_for_data_new_and_next_acq( @pytest.mark.parametrize( - "headers, expected_datasize", + ("headers", "expected_datasize"), [ ( [ @@ -738,7 +794,7 @@ def test_wait_for_data_new_and_next_acq( horizontalzeroindex=0, sourcewidth=1, noofsamples=4, - ) + ), ], 4, ), @@ -780,26 +836,7 @@ def test_data_arrival(derived_waveform_handler: DerivedWaveformHandler): @pytest.mark.parametrize( - "headers, expected", - [ - ([WaveformHeader(dataid=1), WaveformHeader(dataid=2)], 1), # Multiple headers - ([], None), # Empty list - ([WaveformHeader(dataid=3)], 3), # Single header - ], -) -def test_acq_id(headers, expected): - """Test the _acq_id method of TekHSIConnect. - - Args: - headers (list): A list of WaveformHeader objects. - expected (int): The expected acquisition ID. - """ - result = TekHSIConnect._acq_id(headers) - assert result == expected, f"Expected {expected}, got {result}" - - -@pytest.mark.parametrize( - "headers, expected", + ("headers", "expected"), [ ([WaveformHeader(dataid=1), WaveformHeader(dataid=2)], 1), # Multiple headers ([], None), # Empty list @@ -818,7 +855,7 @@ def test_acq_id(headers, expected): @pytest.mark.parametrize( - "header, response_data, expected_waveform_type, expected_length", + ("header", "response_data", "expected_waveform_type", "expected_length"), [ ( WaveformHeader( @@ -840,7 +877,11 @@ def test_acq_id(headers, expected): ], ) def test_read_waveform_analog( - tekhsi_client, header, response_data, expected_waveform_type, expected_length + tekhsi_client, + header, + response_data, + expected_waveform_type, + expected_length, ): """Test reading an analog or IQ waveform. @@ -864,7 +905,16 @@ def test_read_waveform_analog( @pytest.mark.parametrize( - "instrument, connected, is_exiting, acqtime, transfertime, datasize, datawidth, expected_output", + ( + "instrument", + "connected", + "is_exiting", + "acqtime", + "transfertime", + "datasize", + "datawidth", + "expected_output", + ), [ (True, True, False, 0.5, 0.2, 1000, 16, "UpdateRate:2.00,Data Rate:0.04Mbs,Data Width:16"), (False, True, False, 0.5, 0.2, 1000, 16, None), @@ -916,7 +966,7 @@ def test_instrumentation( @pytest.mark.parametrize( - "header, response_data, expected_length", + ("header", "response_data", "expected_length"), [ ( WaveformHeader( @@ -933,7 +983,7 @@ def test_instrumentation( ), np.array([1, 2, 3, 4], dtype=np.uint8).tobytes(), 4, - ) + ), ], ) def test_read_waveform_digital(tekhsi_client, header, response_data, expected_length): @@ -980,7 +1030,7 @@ def close(self): self.close_called = True -@pytest.fixture +@pytest.fixture() def setup_tekhsi_connections(): """Fixture to set up dummy connections for TekHSIConnect.""" TekHSIConnect._connections = { From 72795d8a4181af501bf8e7fe9650859858096582 Mon Sep 17 00:00:00 2001 From: "Felt, Nicholas" Date: Tue, 10 Sep 2024 13:54:35 -0700 Subject: [PATCH 2/7] style: Update code to pass ruff linting --- .github/workflows/check-api-for-breaking-changes.yml | 2 +- .github/workflows/codeql-analysis.yml | 2 +- .github/workflows/enforce-community-standards.yml | 2 +- .github/workflows/package-build.yml | 2 +- .github/workflows/package-release.yml | 4 +++- .github/workflows/package-testpypi.yml | 4 +++- .github/workflows/publish-api-comparison.yml | 2 +- .github/workflows/publish-test-results.yml | 2 +- .github/workflows/sbom-scan.yml | 2 +- .github/workflows/test-code.yml | 2 +- .github/workflows/test-docs.yml | 2 +- .../workflows/update-python-and-pre-commit-dependencies.yml | 3 ++- .pre-commit-config.yaml | 5 ++--- CHANGELOG.md | 6 ++---- pyproject.toml | 2 +- 15 files changed, 22 insertions(+), 20 deletions(-) diff --git a/.github/workflows/check-api-for-breaking-changes.yml b/.github/workflows/check-api-for-breaking-changes.yml index 5ab252f..804c03a 100644 --- a/.github/workflows/check-api-for-breaking-changes.yml +++ b/.github/workflows/check-api-for-breaking-changes.yml @@ -5,6 +5,6 @@ on: branches: [main] jobs: check-api-for-breaking-changes: - uses: tektronix/python-package-ci-cd/.github/workflows/_reusable-check-api-for-breaking-changes.yml@v1.2.0 + uses: tektronix/python-package-ci-cd/.github/workflows/_reusable-check-api-for-breaking-changes.yml@v1.3.0 with: package-name: tekhsi # griffe requires the package name in lowercase diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index d14228f..f097c36 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -9,7 +9,7 @@ on: - cron: 17 16 * * 4 jobs: analyze: - uses: tektronix/python-package-ci-cd/.github/workflows/_reusable-codeql-analysis.yml@v1.2.0 + uses: tektronix/python-package-ci-cd/.github/workflows/_reusable-codeql-analysis.yml@v1.3.0 with: languages-array: '["python", "javascript"]' codeql-queries: security-extended,security-and-quality diff --git a/.github/workflows/enforce-community-standards.yml b/.github/workflows/enforce-community-standards.yml index 987694a..9d4b122 100644 --- a/.github/workflows/enforce-community-standards.yml +++ b/.github/workflows/enforce-community-standards.yml @@ -7,4 +7,4 @@ on: branches: [main] jobs: enforce-community-standards: - uses: tektronix/python-package-ci-cd/.github/workflows/_reusable-enforce-community-standards.yml@v1.2.0 + uses: tektronix/python-package-ci-cd/.github/workflows/_reusable-enforce-community-standards.yml@v1.3.0 diff --git a/.github/workflows/package-build.yml b/.github/workflows/package-build.yml index fc6f54e..5c5a428 100644 --- a/.github/workflows/package-build.yml +++ b/.github/workflows/package-build.yml @@ -12,7 +12,7 @@ concurrency: cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} jobs: package-build: - uses: tektronix/python-package-ci-cd/.github/workflows/_reusable-package-build.yml@v1.2.0 + uses: tektronix/python-package-ci-cd/.github/workflows/_reusable-package-build.yml@v1.3.0 with: package-name: TekHSI python-versions-array: '["3.8", "3.9", "3.10", "3.11", "3.12"]' # when updating this, make sure to update all workflows that use this strategy diff --git a/.github/workflows/package-release.yml b/.github/workflows/package-release.yml index 24c1755..534583e 100644 --- a/.github/workflows/package-release.yml +++ b/.github/workflows/package-release.yml @@ -16,7 +16,7 @@ concurrency: group: pypi jobs: package-release: - uses: tektronix/python-package-ci-cd/.github/workflows/_reusable-package-release.yml@v1.2.0 + uses: tektronix/python-package-ci-cd/.github/workflows/_reusable-package-release.yml@v1.3.0 with: package-name: TekHSI repo-name: tektronix/TekHSI @@ -36,3 +36,5 @@ jobs: checkout-token: ${{ secrets.TEK_OPENSOURCE_TOKEN }} ssh-signing-key-private: ${{ secrets.TEK_OPENSOURCE_SSH_SIGNING_KEY_PRIVATE }} ssh-signing-key-public: ${{ secrets.TEK_OPENSOURCE_SSH_SIGNING_KEY_PUBLIC }} + pypi-api-token: ${{ secrets.PYPI_API_TOKEN }} + test-pypi-api-token: ${{ secrets.TEST_PYPI_API_TOKEN }} diff --git a/.github/workflows/package-testpypi.yml b/.github/workflows/package-testpypi.yml index 33b99e8..aaf7fbf 100644 --- a/.github/workflows/package-testpypi.yml +++ b/.github/workflows/package-testpypi.yml @@ -7,7 +7,7 @@ concurrency: group: pypi jobs: package-testpypi: - uses: tektronix/python-package-ci-cd/.github/workflows/_reusable-package-testpypi.yml@v1.2.0 + uses: tektronix/python-package-ci-cd/.github/workflows/_reusable-package-testpypi.yml@v1.3.0 with: package-name: TekHSI repo-name: tektronix/TekHSI @@ -15,3 +15,5 @@ jobs: contents: read id-token: write attestations: write + secrets: + test-pypi-api-token: ${{ secrets.TEST_PYPI_API_TOKEN }} diff --git a/.github/workflows/publish-api-comparison.yml b/.github/workflows/publish-api-comparison.yml index 9271f5a..dc45138 100644 --- a/.github/workflows/publish-api-comparison.yml +++ b/.github/workflows/publish-api-comparison.yml @@ -6,7 +6,7 @@ on: types: [completed] jobs: publish-api-comparison: - uses: tektronix/python-package-ci-cd/.github/workflows/_reusable-publish-api-comparison.yml@v1.2.0 + uses: tektronix/python-package-ci-cd/.github/workflows/_reusable-publish-api-comparison.yml@v1.3.0 permissions: checks: write pull-requests: write diff --git a/.github/workflows/publish-test-results.yml b/.github/workflows/publish-test-results.yml index 24bed44..fdffc6f 100644 --- a/.github/workflows/publish-test-results.yml +++ b/.github/workflows/publish-test-results.yml @@ -6,7 +6,7 @@ on: types: [completed] jobs: publish-test-results: - uses: tektronix/python-package-ci-cd/.github/workflows/_reusable-publish-test-results.yml@v1.2.0 + uses: tektronix/python-package-ci-cd/.github/workflows/_reusable-publish-test-results.yml@v1.3.0 with: operating-systems-array: '["ubuntu", "windows", "macos"]' permissions: diff --git a/.github/workflows/sbom-scan.yml b/.github/workflows/sbom-scan.yml index 9e3116f..169183b 100644 --- a/.github/workflows/sbom-scan.yml +++ b/.github/workflows/sbom-scan.yml @@ -9,7 +9,7 @@ on: types: [published] jobs: sbom-scan: - uses: tektronix/python-package-ci-cd/.github/workflows/_reusable-sbom-scan.yml@v1.2.0 + uses: tektronix/python-package-ci-cd/.github/workflows/_reusable-sbom-scan.yml@v1.3.0 permissions: security-events: write contents: write diff --git a/.github/workflows/test-code.yml b/.github/workflows/test-code.yml index 513e6a1..43208cc 100644 --- a/.github/workflows/test-code.yml +++ b/.github/workflows/test-code.yml @@ -10,7 +10,7 @@ concurrency: cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} jobs: test-code: - uses: tektronix/python-package-ci-cd/.github/workflows/_reusable-test-code.yml@v1.2.0 + uses: tektronix/python-package-ci-cd/.github/workflows/_reusable-test-code.yml@v1.3.0 with: repo-name: tektronix/TekHSI operating-systems-array: '["ubuntu", "windows", "macos"]' diff --git a/.github/workflows/test-docs.yml b/.github/workflows/test-docs.yml index 08bd00b..f906703 100644 --- a/.github/workflows/test-docs.yml +++ b/.github/workflows/test-docs.yml @@ -10,7 +10,7 @@ concurrency: cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} jobs: test-docs: - uses: tektronix/python-package-ci-cd/.github/workflows/_reusable-test-docs.yml@v1.2.0 + uses: tektronix/python-package-ci-cd/.github/workflows/_reusable-test-docs.yml@v1.3.0 with: node-version: 20 # The node version needs to stay in sync with .readthedocs.yml python-version: '3.11' # This needs to stay in sync with .readthedocs.yml and the tox config in pyproject.toml diff --git a/.github/workflows/update-python-and-pre-commit-dependencies.yml b/.github/workflows/update-python-and-pre-commit-dependencies.yml index b53cbe8..40c2936 100644 --- a/.github/workflows/update-python-and-pre-commit-dependencies.yml +++ b/.github/workflows/update-python-and-pre-commit-dependencies.yml @@ -5,7 +5,8 @@ on: branches: [main] jobs: update-python-and-pre-commit-dependencies: - uses: tektronix/python-package-ci-cd/.github/workflows/_reusable-update-python-and-pre-commit-dependencies.yml@v1.2.0 + if: ${{ github.actor == 'dependabot[bot]' && contains(github.head_ref, '/pip/') }} + uses: tektronix/python-package-ci-cd/.github/workflows/_reusable-update-python-and-pre-commit-dependencies.yml@v1.3.0 with: commit-user-name: ${{ vars.TEK_OPENSOURCE_NAME }} commit-user-email: ${{ vars.TEK_OPENSOURCE_EMAIL }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ee8966e..b6b0f91 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -114,7 +114,6 @@ repos: types: [python] pass_filenames: true args: [-sn] - exclude: ^tests/.* # TODO: include # - id: pyright # TODO: enable # name: pyright # entry: pyright @@ -147,8 +146,8 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit rev: 24d039e647a08707e6cb31e75e01844eeff925e7 # frozen: v0.6.2 hooks: -# - id: ruff # TODO: enable -# args: [--fix, --exit-non-zero-on-fix] + - id: ruff + args: [--fix, --exit-non-zero-on-fix] - id: ruff-format - repo: https://github.com/PyCQA/docformatter rev: dfefe062799848234b4cd60b04aa633c0608025e # frozen: v1.7.5 diff --git a/CHANGELOG.md b/CHANGELOG.md index 4970dfb..76b875d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,10 +18,8 @@ Valid subsections within a version are: Things to be included in the next release go here. ---- - -## v0.1.0 - ### Added - First release of `TekHSI`! + +--- diff --git a/pyproject.toml b/pyproject.toml index d1c2e35..cb6d38f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -276,7 +276,7 @@ src = ["docs", "examples", "scripts", "src", "tests"] target-version = "py38" # always generate Python 3.8 compatible code [tool.ruff.lint] -allowed-confusables = ["¸", "×"] +allowed-confusables = [] fixable = ["ALL"] flake8-pytest-style = {mark-parentheses = false} flake8-quotes = {docstring-quotes = "double"} From 7f93da43590478537d4efdba7b54171915003d5e Mon Sep 17 00:00:00 2001 From: "Felt, Nicholas" Date: Tue, 10 Sep 2024 16:09:37 -0700 Subject: [PATCH 3/7] style: Update code to pass ruff linting --- docs/basic_usage.md | 8 +- examples/custom_filter.py | 8 +- pyproject.toml | 7 + src/tekhsi/tek_hsi_connect.py | 59 +++---- tests/conftest.py | 4 +- tests/server/tekhsi_test_server.py | 267 ++++++++++++++--------------- tests/test_client.py | 152 ++++++++-------- 7 files changed, 249 insertions(+), 256 deletions(-) diff --git a/docs/basic_usage.md b/docs/basic_usage.md index f67e0b7..eab1e60 100644 --- a/docs/basic_usage.md +++ b/docs/basic_usage.md @@ -287,14 +287,14 @@ running and then only consider the change when it arrives. It reduces the need f start and '\*OPC?'. ```python -def any_horizontal_change(prevheader, currentheader): +def any_horizontal_change(previous_header, current_header): """Prebuilt acq acceptance filter that accepts only acqs with changes to horizontal settings. """ - for key, cur in currentheader.items(): - if key not in prevheader: + for key, cur in current_header.items(): + if key not in previous_header: return True - prev = prevheader[key] + prev = previous_header[key] if prev is None and cur != None: return True if prev is not None and ( diff --git a/examples/custom_filter.py b/examples/custom_filter.py index 8cb0167..8915929 100644 --- a/examples/custom_filter.py +++ b/examples/custom_filter.py @@ -9,12 +9,12 @@ addr = "192.168.0.1" # Replace with the IP address of your instrument -def custom_filter(prevheader: Dict[WaveformHeader], currentheader: List[WaveformHeader]): +def custom_filter(previous_header: Dict[WaveformHeader], current_header: List[WaveformHeader]): """A custom criterion for deciding when to consider an acquisition for acceptance.""" - for key, cur in currentheader.items(): - if key not in prevheader: + for key, cur in current_header.items(): + if key not in previous_header: return True - prev = prevheader[key] + prev = previous_header[key] if prev is not None and ( prev.verticalspacing != cur.verticalspacing or prev.horizontalspacing != cur.horizontalspacing diff --git a/pyproject.toml b/pyproject.toml index cb6d38f..2053921 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -212,6 +212,7 @@ disable = [ "too-many-lines", # not necessary to check for "too-many-statements", # caught by ruff "too-many-statements", # caught by ruff + "undefined-variable", # caught by ruff "unused-argument", # caught by ruff "unused-import", # caught by ruff "use-implicit-booleaness-not-comparison-to-string", # caught by ruff @@ -283,6 +284,7 @@ flake8-quotes = {docstring-quotes = "double"} ignore = [ "ANN", # flake8-annotations # TODO: enable this "ANN401", # Dynamically typed expressions (typing.Any) are disallowed in *args and **kwargs + "BLE001", # Do not catch blind exception: `Exception` # TODO: enable this "COM812", # Trailing comma missing (handled by formatter) "D", # pydocstyle # TODO: enable this "EM101", # Exception must not use a string literal, assign to variable first # TODO: enable this @@ -291,6 +293,7 @@ ignore = [ "FBT", # flake8-boolean-trap # TODO: enable this "FIX002", # Line contains TO DO "ISC001", # single-line-implicit-string-concatenation (handled by formatter) + "PLW0603", # Using the global statement to update ... is discouraged # TODO: enable this "PTH", # flake8-use-pathlib # TODO: enable this "RET504", # Unnecessary assignment to ... before return statement # TODO: enable this "SLF001", # Private member accessed # TODO: enable this @@ -333,6 +336,10 @@ order-by-type = false "PLR2004", # Magic value used in comparison "S101" # Use of assert detected ] +"tests/server/tekhsi_test_server.py" = [ + # TODO: remove this entire ignore section + "N80" +] [tool.semantic_release] version_toml = [ diff --git a/src/tekhsi/tek_hsi_connect.py b/src/tekhsi/tek_hsi_connect.py index b5fe98c..2aa757a 100644 --- a/src/tekhsi/tek_hsi_connect.py +++ b/src/tekhsi/tek_hsi_connect.py @@ -6,9 +6,8 @@ import uuid from atexit import register -from contextlib import contextmanager from enum import Enum -from typing import Dict, List, Optional +from typing import ClassVar, Dict, List, Optional import grpc import numpy as np @@ -49,7 +48,7 @@ class TekHSIConnect: # pylint:disable=too-many-instance-attributes - this API is intended to aid in retrieving data from instruments as fast as possible. """ - _connections = {} + _connections: ClassVar[Dict[str, "TekHSIConnect"]] = {} ################################################################################################ # Magic Methods @@ -63,7 +62,7 @@ def __init__(self, url: str, activesymbols=None, callback=None, data_filter=None callback (function, optional): Callback function to handle incoming data. data_filter (function, optional): Filter function to apply to incoming data. """ - self.prevheaders = [] + self.previous_headers = [] self.chunksize = 80000 self.url = url self.v_datatypes = {1: np.int8, 2: np.int16, 4: np.float32, 8: np.double} @@ -236,12 +235,15 @@ def access_data(self, on: AcqWaitOn = AcqWaitOn.NewData, after: float = -1): # Public Methods ################################################################################################ @staticmethod - def any_acq(prevheader, currentheader) -> bool: + def any_acq( + previous_header: Dict[str, WaveformHeader], # noqa: ARG004 + current_header: Dict[str, WaveformHeader], # noqa: ARG004 + ) -> bool: """Prebuilt acq acceptance filter that accepts all new acqs. Args: - prevheader (List[WaveformHeader]): Previous header list. - currentheader (List[WaveformHeader]): Current header list. + previous_header: Previous header. + current_header: Current header. Returns: True if the acquisition is accepted, False otherwise. @@ -250,23 +252,23 @@ def any_acq(prevheader, currentheader) -> bool: @staticmethod def any_horizontal_change( - prevheader: Dict[str, WaveformHeader], - currentheader: Dict[str, WaveformHeader], + previous_header: Dict[str, WaveformHeader], + current_header: Dict[str, WaveformHeader], ) -> bool: - """Prebuilt acq acceptance filter that accepts only acqs with changes to horizontal settings. + """Acq acceptance filter that accepts only acqs with changes to horizontal settings. Args: - prevheader (dict[str, WaveformHeader]): Previous header dictionary. - currentheader (dict[str, WaveformHeader]): Current header dictionary. + previous_header (dict[str, WaveformHeader]): Previous header dictionary. + current_header (dict[str, WaveformHeader]): Current header dictionary. Returns: True if the acquisition is accepted, False otherwise. """ - for key, cur in currentheader.items(): - if key not in prevheader: + for key, cur in current_header.items(): + if key not in previous_header: return True - prev = prevheader[key] - if prev is None and cur != None: # pylint: disable=singleton-comparison + prev = previous_header[key] + if prev is None and cur is not None: return True if prev is not None and ( prev.noofsamples != cur.noofsamples @@ -278,22 +280,22 @@ def any_horizontal_change( @staticmethod def any_vertical_change( - prevheader: Dict[str, WaveformHeader], - currentheader: Dict[str, WaveformHeader], + previous_header: Dict[str, WaveformHeader], + current_header: Dict[str, WaveformHeader], ) -> bool: """Prebuilt acq acceptance filter that accepts only acqs with changes to vertical settings. Args: - prevheader (dict[str, WaveformHeader]): Previous header dictionary. - currentheader (dict[str, WaveformHeader]): Current header dictionary. + previous_header (dict[str, WaveformHeader]): Previous header dictionary. + current_header (dict[str, WaveformHeader]): Current header dictionary. Returns: True if the acquisition is accepted, False otherwise. """ - for key, cur in currentheader.items(): - if key not in prevheader: + for key, cur in current_header.items(): + if key not in previous_header: return True - prev = prevheader[key] + prev = previous_header[key] if prev is not None and ( prev.verticalspacing != cur.verticalspacing or prev.verticaloffset != cur.verticaloffset @@ -349,9 +351,8 @@ def close(self): self._disconnect() @staticmethod - def data_arrival(waveforms: List[Waveform]) -> None: - """Available to be overridden if user wants to create a - derived class. + def data_arrival(waveforms: List[Waveform]) -> None: # noqa: ARG004 + """Available to be overridden if user wants to create a derived class. This method will be called on every accepted acq. @@ -579,7 +580,7 @@ def _read_headers(self, headers, header_dict: dict): header_dict[header.sourcename] = header # pylint: disable= too-many-locals - def _read_waveform(self, header: WaveformHeader): + def _read_waveform(self, header: WaveformHeader): # noqa: C901,PLR0912,PLR0915 """Reads the analog waveform associated with the passed header. Args: @@ -589,7 +590,7 @@ def _read_waveform(self, header: WaveformHeader): Waveform: contains definition and data for the specified source """ try: - if 0 < header.wfmtype <= 3: # Vector + if 0 < header.wfmtype <= 3: # Vector # noqa: PLR2004 waveform = AnalogWaveform() waveform.source_name = header.sourcename waveform.y_axis_spacing = header.verticalspacing @@ -758,7 +759,7 @@ def _run(self): self._lock_filter.release() self._holding_scope_open = False - def _run_inner(self, headers, waveforms, startwait): + def _run_inner(self, headers, waveforms, startwait): # noqa: C901,PLR0912 """Background thread for participating in the instruments sequence. Args: diff --git a/tests/conftest.py b/tests/conftest.py index 4abb89f..1a8f360 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -45,7 +45,7 @@ def is_port_in_use(self): for conn in proc.connections(kind="inet"): if conn.laddr.port == self.port: return proc.pid - except (psutil.AccessDenied, psutil.NoSuchProcess): + except (psutil.AccessDenied, psutil.NoSuchProcess): # noqa: PERF203 continue return None @@ -65,7 +65,7 @@ def __enter__(self): raise RuntimeError("Server script not found.") # Start the server - self.server_process = subprocess.Popen([sys.executable, server_script, "--verbose"]) + self.server_process = subprocess.Popen([sys.executable, server_script, "--verbose"]) # noqa: S603 # Wait a few seconds for the server to start time.sleep(5) return self diff --git a/tests/server/tekhsi_test_server.py b/tests/server/tekhsi_test_server.py index 062687b..6cab35c 100644 --- a/tests/server/tekhsi_test_server.py +++ b/tests/server/tekhsi_test_server.py @@ -47,8 +47,9 @@ class WfmEncoding(Enum): class ServerWaveform: # pylint: disable=too-many-instance-attributes - """This class simplifies the process of creating new data for the server. It is divided into to sets of inputs: - Signal Definition, and Signal Representation Properties. + """This class simplifies the process of creating new data for the server. + + It is divided into to sets of inputs: Signal Definition, and Signal Representation Properties. Signal Definition: frequency : float @@ -64,16 +65,17 @@ class ServerWaveform: # pylint: disable=too-many-instance-attributes Signal Representation: type : WfmDataType - Defines the underlying waveform representation. Options are: WfmDataType.Int8, WfmDatType.Int16, - and WfmDataType.Float. + Defines the underlying waveform representation. Options are: + WfmDataType.Int8, WfmDatType.Int16, and WfmDataType.Float. - The resulting class instance will contain both a waveform in the native format, and an equivalent float array. + The resulting class instance will contain both a waveform in the native format, and an + equivalent float array. These are intended to target either NativeData or NormalizedData services. This simplifies the process of feeding the data to the streaming service. """ - def __init__( + def __init__( # noqa: C901,PLR0912,PLR0915 self, frequency: float = 1000.0, wfm_data_type: WfmDataType = WfmDataType.Int8, @@ -113,14 +115,11 @@ def __init__( resolution = None offset = 0 noise = 0.01 - if self._type is WfmDataType.Int8: - yincr = self.amplitude / 230 - if self._type is WfmDataType.Int16: - yincr = self.amplitude / 58000 - if self._type is WfmDataType.Float: - yincr = 1 - else: - yincr = 1 + # FUTURE # if self._type is WfmDataType.Int8: + # FUTURE # yincr = self.amplitude / 230 + # FUTURE # if self._type is WfmDataType.Int16: + # FUTURE # yincr = self.amplitude / 58000 + yincr = 1 if self._type is WfmDataType.Float else 1 self._yincr = yincr @@ -219,7 +218,9 @@ def _add_noise(array, noise_range: float): This is to make it visually clear that each waveform is unique. """ - return np.array(array) + np.random.normal(loc=0.0, scale=noise_range / 4, size=len(array)) + return np.array(array) + np.random.Generator.normal( + loc=0.0, scale=noise_range / 4, size=len(array) + ) class TekHSI_NormalizedDataServer(tekhsi_pb2_grpc.NormalizedDataServicer): @@ -230,11 +231,12 @@ class TekHSI_NormalizedDataServer(tekhsi_pb2_grpc.NormalizedDataServicer): slower than the native server. """ - def GetWaveform(self, request, context): + def GetWaveform(self, request, context): # noqa: ARG002 """This message returns the stream of the data representing the requested channel/math. + The data is returned as normalized data. This usually slower than using the raw service - because this moves significantly more data because floats are 4 bytes while raw data is normally - either 1 or 2 bytes. + because this moves significantly more data because floats are 4 bytes while raw data is + normally either 1 or 2 bytes. Parameters ---------- @@ -247,28 +249,26 @@ def GetWaveform(self, request, context): if verbose: print(f"TekHSI_NormalizedDataServer.GetWaveform({request.sourcename})") try: - if connect_server.dataaccess_allowed: - if request.sourcename in connect_server.data: - wfm = connect_server.data[request.sourcename] - chunksize = request.chunksize - for cur in range(0, len(wfm._vertical_data), chunksize): - reply = tekhsi_pb2.NormalizedReply( - headerordata=tekhsi_pb2.NormalizedReply.DataOrHeaderAccess( - chunk=tekhsi_pb2.NormalizedReply.WaveformSampleChunk( - data=wfm._vertical_data[cur : cur + chunksize], - ), + if connect_server.dataaccess_allowed and request.sourcename in connect_server.data: + wfm = connect_server.data[request.sourcename] + chunksize = request.chunksize + for cur in range(0, len(wfm._vertical_data), chunksize): + reply = tekhsi_pb2.NormalizedReply( + headerordata=tekhsi_pb2.NormalizedReply.DataOrHeaderAccess( + chunk=tekhsi_pb2.NormalizedReply.WaveformSampleChunk( + data=wfm._vertical_data[cur : cur + chunksize], ), - ) - yield reply - reply = tekhsi_pb2.NormalizedReply() - reply.status = tekhsi_pb2.WfmReplyStatus.Value("WFMREPLYSTATUS_SUCCESS") + ), + ) yield reply - return + reply = tekhsi_pb2.NormalizedReply() + reply.status = tekhsi_pb2.WfmReplyStatus.Value("WFMREPLYSTATUS_SUCCESS") + yield reply + return except Exception as e: print(e) - return - def GetHeader(self, request, context): + def GetHeader(self, request, context): # noqa: ARG002 """The message returns the header (equivalent to preamble when using SCPI commands). Parameters @@ -287,32 +287,31 @@ def GetHeader(self, request, context): if verbose: print(f"TekHSI_NormalizedDataServer.GetHeader({request.sourcename})") try: - if connect_server.dataaccess_allowed: - if request.sourcename in connect_server.data: - wfm = connect_server.data[request.sourcename] - reply = tekhsi_pb2.NormalizedReply() - reply.headerordata.header.dataid = acq_id - reply.headerordata.header.hasdata = True - reply.headerordata.header.horizontalspacing = wfm.xincr - reply.headerordata.header.horizontalUnits = "S" - reply.headerordata.header.horizontalzeroindex = wfm.trigger_index - reply.headerordata.header.noofsamples = wfm.length - reply.headerordata.header.sourcename = request.sourcename - reply.headerordata.header.sourcewidth = 4 - - if isinstance(data, AnalogWaveform): - reply.headerordata.header.wfmtype = 3 - elif isinstance(data, IQWaveform): - reply.headerordata.header.wfmtype = 6 - elif isinstance(data, DigitalWaveform): - reply.headerordata.header.wfmtype = 4 - - reply.headerordata.header.pairtype = 1 - reply.headerordata.header.verticaloffset = 0 - reply.headerordata.header.verticalspacing = 1.0 - reply.headerordata.header.verticalunits = "V" - reply.status = tekhsi_pb2.WfmReplyStatus.Value("WFMREPLYSTATUS_SUCCESS") - return reply + if connect_server.dataaccess_allowed and request.sourcename in connect_server.data: + wfm = connect_server.data[request.sourcename] + reply = tekhsi_pb2.NormalizedReply() + reply.headerordata.header.dataid = acq_id + reply.headerordata.header.hasdata = True + reply.headerordata.header.horizontalspacing = wfm.xincr + reply.headerordata.header.horizontalUnits = "S" + reply.headerordata.header.horizontalzeroindex = wfm.trigger_index + reply.headerordata.header.noofsamples = wfm.length + reply.headerordata.header.sourcename = request.sourcename + reply.headerordata.header.sourcewidth = 4 + + if isinstance(data, AnalogWaveform): # pylint: disable=undefined-variable # noqa: F821 + reply.headerordata.header.wfmtype = 3 + elif isinstance(data, IQWaveform): # pylint: disable=undefined-variable # noqa: F821 + reply.headerordata.header.wfmtype = 6 + elif isinstance(data, DigitalWaveform): # pylint: disable=undefined-variable # noqa: F821 + reply.headerordata.header.wfmtype = 4 + + reply.headerordata.header.pairtype = 1 + reply.headerordata.header.verticaloffset = 0 + reply.headerordata.header.verticalspacing = 1.0 + reply.headerordata.header.verticalunits = "V" + reply.status = tekhsi_pb2.WfmReplyStatus.Value("WFMREPLYSTATUS_SUCCESS") + return reply except Exception as e: print(e) return tekhsi_pb2.NormalizedReply( @@ -327,7 +326,7 @@ class TekHSI_NativeDataServer(tekhsi_pb2_grpc.NativeDataServicer): normalized version. """ - def GetWaveform(self, request, context): + def GetWaveform(self, request, context): # noqa: ARG002 """This message returns the stream of the data representing the requested channel/math. The data is returned as native data. How the data is represented is defined in the header. @@ -349,28 +348,27 @@ def GetWaveform(self, request, context): if verbose: print(f"TekHSI_NativeDataServer.GetWaveform({request.sourcename})") try: - if connect_server.dataaccess_allowed: - if request.sourcename in connect_server.data: - wfm = connect_server.data[request.sourcename] - chunksize = request.chunksize - raw_bytes = wfm._raw_data.tobytes() - for cur in range(0, len(raw_bytes), chunksize): - reply = tekhsi_pb2.RawReply( - headerordata=tekhsi_pb2.RawReply.DataOrHeaderAccess( - chunk=tekhsi_pb2.RawReply.WaveformSampleByteChunk( - data=raw_bytes[cur : cur + chunksize], - ), + if connect_server.dataaccess_allowed and request.sourcename in connect_server.data: + wfm = connect_server.data[request.sourcename] + chunksize = request.chunksize + raw_bytes = wfm._raw_data.tobytes() + for cur in range(0, len(raw_bytes), chunksize): + reply = tekhsi_pb2.RawReply( + headerordata=tekhsi_pb2.RawReply.DataOrHeaderAccess( + chunk=tekhsi_pb2.RawReply.WaveformSampleByteChunk( + data=raw_bytes[cur : cur + chunksize], ), - ) - yield reply - reply = tekhsi_pb2.RawReply() - reply.status = tekhsi_pb2.WfmReplyStatus.Value("WFMREPLYSTATUS_SUCCESS") - return reply + ), + ) + yield reply + reply = tekhsi_pb2.RawReply() + reply.status = tekhsi_pb2.WfmReplyStatus.Value("WFMREPLYSTATUS_SUCCESS") + return reply except Exception as e: print(e) return tekhsi_pb2.RawReply(status=tekhsi_pb2.WfmReplyStatus.Value("WFMREPLYSTATUS_FAILURE")) - def GetHeader(self, request, context): + def GetHeader(self, request, context): # noqa: ARG002,PLR0912,PLR0915,C901 """The message returns the header (equivalent to preamble when using SCPI commands). Parameters @@ -389,58 +387,57 @@ def GetHeader(self, request, context): if verbose: print(f"TekHSI_NativeDataServer.GetHeader({request.sourcename})") try: - if connect_server.dataaccess_allowed: - if request.sourcename in connect_server.data: - wfm = connect_server.data[request.sourcename] - reply = tekhsi_pb2.RawReply() - reply.headerordata.header.dataid = acq_id - reply.headerordata.header.hasdata = True - reply.headerordata.header.horizontalspacing = wfm.xincr - reply.headerordata.header.horizontalUnits = "S" - reply.headerordata.header.horizontalzeroindex = wfm.trigger_index - reply.headerordata.header.noofsamples = wfm.length - reply.headerordata.header.sourcename = request.sourcename - - if isinstance(data, AnalogWaveform): - if wfm.type == WfmDataType.Int8: - reply.headerordata.header.sourcewidth = 1 - reply.headerordata.header.wfmtype = 1 - elif wfm.type == WfmDataType.Int16: - reply.headerordata.header.sourcewidth = 2 - reply.headerordata.header.wfmtype = 2 - elif wfm.type == WfmDataType.Float: - reply.headerordata.header.sourcewidth = 4 - reply.headerordata.header.wfmtype = 3 - else: - reply.headerordata.header.sourcewidth = 1 - reply.headerordata.header.wfmtype = 1 - elif isinstance(data, IQWaveform): - if wfm.type == WfmDataType.Int8: - reply.headerordata.header.sourcewidth = 1 - reply.headerordata.header.wfmtype = 6 - elif wfm.type == WfmDataType.Int16: - reply.headerordata.header.sourcewidth = 2 - reply.headerordata.header.wfmtype = 7 - else: - reply.headerordata.header.sourcewidth = 1 - reply.headerordata.header.wfmtype = 6 - elif isinstance(data, DigitalWaveform): - if wfm.type == WfmDataType.Int8: - reply.headerordata.header.sourcewidth = 1 - reply.headerordata.header.wfmtype = 4 - elif wfm.type == WfmDataType.Int16: - reply.headerordata.header.sourcewidth = 2 - reply.headerordata.header.wfmtype = 5 - else: - reply.headerordata.header.sourcewidth = 1 - reply.headerordata.header.wfmtype = 4 - - reply.headerordata.header.pairtype = 1 - reply.headerordata.header.verticaloffset = 0 - reply.headerordata.header.verticalspacing = wfm.yincr - reply.headerordata.header.verticalunits = "V" - reply.status = tekhsi_pb2.WfmReplyStatus.Value("WFMREPLYSTATUS_SUCCESS") - return reply + if connect_server.dataaccess_allowed and request.sourcename in connect_server.data: + wfm = connect_server.data[request.sourcename] + reply = tekhsi_pb2.RawReply() + reply.headerordata.header.dataid = acq_id + reply.headerordata.header.hasdata = True + reply.headerordata.header.horizontalspacing = wfm.xincr + reply.headerordata.header.horizontalUnits = "S" + reply.headerordata.header.horizontalzeroindex = wfm.trigger_index + reply.headerordata.header.noofsamples = wfm.length + reply.headerordata.header.sourcename = request.sourcename + + if isinstance(data, AnalogWaveform): # pylint: disable=undefined-variable # noqa: F821 + if wfm.type == WfmDataType.Int8: + reply.headerordata.header.sourcewidth = 1 + reply.headerordata.header.wfmtype = 1 + elif wfm.type == WfmDataType.Int16: + reply.headerordata.header.sourcewidth = 2 + reply.headerordata.header.wfmtype = 2 + elif wfm.type == WfmDataType.Float: + reply.headerordata.header.sourcewidth = 4 + reply.headerordata.header.wfmtype = 3 + else: + reply.headerordata.header.sourcewidth = 1 + reply.headerordata.header.wfmtype = 1 + elif isinstance(data, IQWaveform): # pylint: disable=undefined-variable # noqa: F821 + if wfm.type == WfmDataType.Int8: + reply.headerordata.header.sourcewidth = 1 + reply.headerordata.header.wfmtype = 6 + elif wfm.type == WfmDataType.Int16: + reply.headerordata.header.sourcewidth = 2 + reply.headerordata.header.wfmtype = 7 + else: + reply.headerordata.header.sourcewidth = 1 + reply.headerordata.header.wfmtype = 6 + elif isinstance(data, DigitalWaveform): # pylint: disable=undefined-variable # noqa: F821 + if wfm.type == WfmDataType.Int8: + reply.headerordata.header.sourcewidth = 1 + reply.headerordata.header.wfmtype = 4 + elif wfm.type == WfmDataType.Int16: + reply.headerordata.header.sourcewidth = 2 + reply.headerordata.header.wfmtype = 5 + else: + reply.headerordata.header.sourcewidth = 1 + reply.headerordata.header.wfmtype = 4 + + reply.headerordata.header.pairtype = 1 + reply.headerordata.header.verticaloffset = 0 + reply.headerordata.header.verticalspacing = wfm.yincr + reply.headerordata.header.verticalunits = "V" + reply.status = tekhsi_pb2.WfmReplyStatus.Value("WFMREPLYSTATUS_SUCCESS") + return reply except Exception as e: print(e) return tekhsi_pb2.RawReply(status=tekhsi_pb2.WfmReplyStatus.Value("WFMREPLYSTATUS_FAILURE")) @@ -467,7 +464,7 @@ def __init__(self): @property def dataaccess_allowed(self) -> bool: - """Returns True if the Connect state is between WaitForDataAccess, and FinishedWithDataAccess.""" + """Return if the Connect state is between WaitForDataAccess and FinishedWithDataAccess.""" return self._dataaccess_allowed def dataconnection_name(self, name) -> bool: @@ -507,7 +504,7 @@ def Connect(self, request, context): status=tekhsi_pb2.ConnectStatus.Value("CONNECTSTATUS_UNSPECIFIED"), ) - def Disconnect(self, request, context): + def Disconnect(self, request, context): # noqa: C901 if verbose: print(f'Disconnect Request "{request.name}"') try: @@ -702,7 +699,7 @@ def make_new_data(): "ch1_iq": ServerWaveform(encoding=WfmEncoding.IQ, wfm_data_type=WfmDataType.Int16), "ch2": ServerWaveform(wfm_data_type=WfmDataType.Int16), "ch3": ServerWaveform(wfm_data_type=WfmDataType.Int16), - # "ch4_DAll": ServerWaveform(encoding=WfmEncoding.Digital, wfm_data_type=WfmDataType.Int8), + # FUTURE # "ch4_DAll": ServerWaveform(encoding=WfmEncoding.Digital, wfm_data_type=WfmDataType.Int8), # noqa: E501 "math1": ServerWaveform(wfm_data_type=WfmDataType.Float), "math2": ServerWaveform(wfm_data_type=WfmDataType.Float), } @@ -748,7 +745,7 @@ def kill_server(): if __name__ == "__main__": - for i, arg in enumerate(sys.argv[1:]): + for _, arg in enumerate(sys.argv[1:]): if arg.lower() == "--verbose": verbose = True verbose = True diff --git a/tests/test_client.py b/tests/test_client.py index 666e3b6..6b8d19e 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -7,7 +7,6 @@ import numpy as np import pytest -from tm_data_types import AnalogWaveform, IQWaveform, DigitalWaveform from tm_data_types import AnalogWaveform, DigitalWaveform, IQWaveform @@ -40,6 +39,12 @@ def test_server_connection( Args: tekhsi_client (TekHSIConnect): An instance of the TekHSI client to be tested. + capsys (CaptureFixture): Pytest fixture to capture system output. + instrument (bool): Whether the instrument is connected. + sum_count (int): The sum count. + sum_acq_time (float): The sum acquisition time. + sum_data_rate (float): The sum data rate. + expected_output (str): The expected output message. """ # Set the required attributes tekhsi_client._instrument = instrument @@ -67,25 +72,25 @@ def test_server_connection( @pytest.mark.parametrize( - ("prevheader", "currentheader", "expected"), + ("previous_header", "current_header", "expected"), [ ({}, {}, True), # Always returns True ], ) -def test_any_acq(prevheader, currentheader, expected): +def test_any_acq(previous_header, current_header, expected): """Test the any_acq method of TekHSIConnect. Args: - prevheader (dict): The previous header data. - currentheader (dict): The current header data. + previous_header (dict): The previous header data. + current_header (dict): The current header data. expected (bool): The expected result of the any_acq method. """ - result = TekHSIConnect.any_acq(prevheader, currentheader) - assert result == expected, f"Expected any_acq to return {expected}" + result = TekHSIConnect.any_acq(previous_header, current_header) + assert result == expected @pytest.mark.parametrize( - ("prevheader", "currentheader", "expected"), + ("previous_header", "current_header", "expected"), [ # No changes ( @@ -162,20 +167,20 @@ def test_any_acq(prevheader, currentheader, expected): ), ], ) -def test_any_horizontal_change(prevheader, currentheader, expected): +def test_any_horizontal_change(previous_header, current_header, expected): """Test the any_horizontal_change method of TekHSIConnect. Args: - prevheader (dict): The previous header data. - currentheader (dict): The current header data. + previous_header (dict): The previous header data. + current_header (dict): The current header data. expected (bool): The expected result of the any_horizontal_change method. """ - result = TekHSIConnect.any_horizontal_change(prevheader, currentheader) - assert result == expected, f"Expected any_horizontal_change to return {expected}" + result = TekHSIConnect.any_horizontal_change(previous_header, current_header) + assert result == expected @pytest.mark.parametrize( - ("prevheader", "currentheader", "expected"), + ("previous_header", "current_header", "expected"), [ # No changes ( @@ -230,16 +235,16 @@ def test_any_horizontal_change(prevheader, currentheader, expected): ), ], ) -def test_any_vertical_change(prevheader, currentheader, expected): +def test_any_vertical_change(previous_header, current_header, expected): """Test the any_vertical_change method of TekHSIConnect. Args: - prevheader (dict): The previous header data. - currentheader (dict): The current header data. + previous_header (dict): The previous header data. + current_header (dict): The current header data. expected (bool): The expected result of the any_vertical_change method. """ - result = TekHSIConnect.any_vertical_change(prevheader, currentheader) - assert result == expected, f"Expected any_vertical_change to return {expected}" + result = TekHSIConnect.any_vertical_change(previous_header, current_header) + assert result == expected @pytest.mark.parametrize( @@ -297,7 +302,7 @@ def test_get_data(tekhsi_client, cache_enabled, data_cache, name, expected_resul result = connection.get_data(name) # Verify the result - assert result == expected_result, f"Expected {expected_result}, got {result}" + assert result == expected_result @pytest.mark.parametrize( @@ -320,10 +325,9 @@ def test_get_data(tekhsi_client, cache_enabled, data_cache, name, expected_resul ], ) @patch("tekhsi.tek_hsi_connect.print_with_timestamp") -def test_done_with_data( +def test_done_with_data( # noqa: PLR0913 mock_print_with_timestamp, tekhsi_client, - capsys, cache_enabled, wait_for_data_count, acqcount, @@ -337,7 +341,6 @@ def test_done_with_data( Args: mock_print_with_timestamp (MagicMock): Mocked print_with_timestamp function. tekhsi_client (TekHSIConnect): An instance of the TekHSI client to be tested. - capsys (CaptureFixture): Pytest fixture to capture system output. cache_enabled (bool): Whether the data cache is enabled. wait_for_data_count (int): The initial wait_for_data_count value. acqcount (int): The initial acquisition count. @@ -367,9 +370,7 @@ def test_done_with_data( if expected_output: mock_print_with_timestamp.assert_called_once_with(expected_output) captured = mock_print_with_timestamp.return_value - assert ( - expected_output in captured - ), f"Expected output '{expected_output}', got '{captured}'" + assert expected_output in captured elif verbose: mock_print_with_timestamp.assert_not_called() @@ -394,8 +395,8 @@ def mock_method(): with pytest.raises(RuntimeError, match="Lock not acquired"): connection.done_with_data() - # Restore the original method - connection.done_with_data() + # Restore the original method + connection.done_with_data() @pytest.mark.parametrize( @@ -441,7 +442,7 @@ def mock_method(): (False, AcqWaitOn.NewData, -1, {}, 0, 0, 0, 0, 0, None), # Caching disabled ], ) -def test_wait_for_data( +def test_wait_for_data( # noqa: PLR0913 tekhsi_client, cache_enabled, wait_on, @@ -489,12 +490,8 @@ def test_wait_for_data( connection.wait_for_data(wait_on, after) # Verify the internal state - assert ( - connection._wait_for_data_count == expected_wait_for_data_count - ), f"Expected wait_for_data_count {expected_wait_for_data_count}, got {connection._wait_for_data_count}" - assert ( - connection._lastacqseen == expected_lastacqseen - ), f"Expected lastacqseen {expected_lastacqseen}, got {connection._lastacqseen}" + assert connection._wait_for_data_count == expected_wait_for_data_count + assert connection._lastacqseen == expected_lastacqseen @pytest.mark.parametrize( @@ -515,9 +512,7 @@ def test_available_symbols(tekhsi_client, expected_symbols): symbols = connection.available_symbols # Check that the symbols match the expected values from the test server - assert sorted(symbols) == sorted( - expected_symbols, - ), f"Expected symbols {expected_symbols}, got {symbols}" + assert sorted(symbols) == sorted(expected_symbols) @pytest.mark.parametrize( @@ -536,24 +531,21 @@ def test_instrumentation_enabled(tekhsi_client, initial_value, new_value, expect tekhsi_client (TekHSIConnect): An instance of the TekHSI client to be tested. initial_value (bool): The initial value of the instrumentation_enabled property. new_value (bool): The new value to set for the instrumentation_enabled property. - expected_value (bool): The expected value of the instrumentation_enabled property after setting the new value. + expected_value (bool): The expected value of the instrumentation_enabled property after + setting the new value. """ with tekhsi_client as connection: # Set the initial value connection.instrumentation_enabled = initial_value # Verify the initial value - assert ( - connection.instrumentation_enabled == initial_value - ), f"Expected initial value {initial_value}, got {connection.instrumentation_enabled}" + assert connection.instrumentation_enabled == initial_value # Change the value connection.instrumentation_enabled = new_value # Verify the new value - assert ( - connection.instrumentation_enabled == expected_value - ), f"Expected new value {expected_value}, got {connection.instrumentation_enabled}" + assert connection.instrumentation_enabled == expected_value @pytest.mark.parametrize( @@ -581,9 +573,7 @@ def test_source_names(tekhsi_client, initial_symbols, expected_symbols): source_names = connection.source_names # Verify the source names match the expected values - assert ( - source_names == expected_symbols - ), f"Expected source names {expected_symbols}, got {source_names}" + assert source_names == expected_symbols @pytest.mark.parametrize( @@ -623,7 +613,7 @@ def test_is_header_value(header, expected): (True, AcqWaitOn.Time, 0, {"data": "value"}, 0, 1, 0, 1, 0), ], ) -def test_wait_for_data_acq_time( +def test_wait_for_data_acq_time( # noqa: PLR0913 tekhsi_client, cache_enabled, wait_on, @@ -661,12 +651,8 @@ def test_wait_for_data_acq_time( connection.wait_for_data(wait_on, after) # Verify the internal state - assert ( - connection._wait_for_data_count == expected_wait_for_data_count - ), f"Expected wait_for_data_count {expected_wait_for_data_count}, got {connection._wait_for_data_count}" - assert ( - connection._lastacqseen == expected_lastacqseen - ), f"Expected lastacqseen {expected_lastacqseen}, got {connection._lastacqseen}" + assert connection._wait_for_data_count == expected_wait_for_data_count + assert connection._lastacqseen == expected_lastacqseen @pytest.mark.parametrize( @@ -713,12 +699,8 @@ def test_wait_for_data_any_acq( connection.wait_for_data(wait_on) # Verify the internal state - assert ( - connection._wait_for_data_count == expected_wait_for_data_count - ), f"Expected wait_for_data_count {expected_wait_for_data_count}, got {connection._wait_for_data_count}" - assert ( - connection._lastacqseen == expected_lastacqseen - ), f"Expected lastacqseen {expected_lastacqseen}, got {connection._lastacqseen}" + assert connection._wait_for_data_count == expected_wait_for_data_count + assert connection._lastacqseen == expected_lastacqseen @pytest.mark.parametrize( @@ -735,7 +717,7 @@ def test_wait_for_data_any_acq( (True, AcqWaitOn.NewData, {"data": "value"}, 5, 0, 1, 0), ], ) -def test_wait_for_data_new_and_next_acq( +def test_wait_for_data_new_and_next_acq( # noqa: PLR0913 tekhsi_client, cache_enabled, wait_on, @@ -768,13 +750,8 @@ def test_wait_for_data_new_and_next_acq( connection.wait_for_data(wait_on) # Verify the internal state - assert ( - connection._wait_for_data_count == expected_wait_for_data_count - ), f"Expected wait_for_data_count {expected_wait_for_data_count}, got {connection._wait_for_data_count}" - assert ( - connection._lastacqseen == expected_lastacqseen - ), f"Expected lastacqseen {expected_lastacqseen}, got {connection._lastacqseen}" - + assert connection._wait_for_data_count == expected_wait_for_data_count + assert connection._lastacqseen == expected_lastacqseen assert connection._wait_for_data_holds_lock, "_wait_next_acq was not called" @@ -818,9 +795,14 @@ def test_data_arrival(derived_waveform_handler: DerivedWaveformHandler): """Test the data_arrival method of DerivedWaveformHandler. Args: - derived_waveform_handler (DerivedWaveformHandler): An instance of the DerivedWaveformHandler to be tested. + derived_waveform_handler (DerivedWaveformHandler): An instance of the DerivedWaveformHandler + to be tested. """ - waveforms = [DerivedWaveform(), DerivedWaveform()] # pylint: disable=abstract-class-instantiated + # pylint: disable=abstract-class-instantiated + waveforms = [ + DerivedWaveform(), + DerivedWaveform(), + ] # Capture the output captured_output = StringIO() @@ -851,7 +833,7 @@ def test_acq_id(headers, expected): expected (int): The expected acquisition ID. """ result = TekHSIConnect._acq_id(headers) - assert result == expected, f"Expected {expected}, got {result}" + assert result == expected @pytest.mark.parametrize( @@ -889,7 +871,7 @@ def test_read_waveform_analog( tekhsi_client (TekHSIConnect): An instance of the TekHSI client to be tested. header (WaveformHeader): The header information for the waveform. response_data (bytes): The response data for the waveform. - expected_waveform_type (type): The expected type of the waveform (e.g., AnalogWaveform, IQWaveform). + expected_waveform_type (type): The expected type of the waveform. expected_length (int): The expected length of the waveform data. """ client = tekhsi_client @@ -923,7 +905,7 @@ def test_read_waveform_analog( ], ) @patch("builtins.print") -def test_instrumentation( +def test_instrumentation( # noqa: PLR0913 mock_print, tekhsi_client, instrument, @@ -939,6 +921,7 @@ def test_instrumentation( Args: mock_print (MagicMock): Mocked print function. + tekhsi_client (TekHSIConnect): An instance of the TekHSI client to be tested. instrument (bool): Whether the instrument is enabled. connected (bool): Whether the client is connected. is_exiting (bool): Whether the client is in the process of exiting. @@ -1010,7 +993,7 @@ def test_read_waveform_digital(tekhsi_client, header, response_data, expected_le assert len(waveform.y_axis_byte_values) == expected_length -class DummyConnection: +class DummyConnection: # pylint: disable=too-few-public-methods def __init__(self, holding_scope_open): """Initialize the DummyConnection. @@ -1030,8 +1013,8 @@ def close(self): self.close_called = True -@pytest.fixture() -def setup_tekhsi_connections(): +@pytest.fixture(name="setup_tekhsi_connections") +def fixture_setup_tekhsi_connections(): """Fixture to set up dummy connections for TekHSIConnect.""" TekHSIConnect._connections = { "conn1": DummyConnection(holding_scope_open=True), @@ -1039,7 +1022,7 @@ def setup_tekhsi_connections(): } -def test_terminate(setup_tekhsi_connections): +def test_terminate(setup_tekhsi_connections): # noqa: ARG001 """Test the _terminate method of TekHSIConnect. Args: @@ -1067,9 +1050,7 @@ def test_active_symbols(tekhsi_client): tekhsi_client.active_symbols(symbols) # Verify that the activesymbols attribute is updated correctly - assert ( - tekhsi_client.activesymbols == symbols - ), f"Expected {symbols}, got {tekhsi_client.activesymbols}" + assert tekhsi_client.activesymbols == symbols def test_callback_invocation(tekhsi_client): @@ -1089,7 +1070,7 @@ def real_callback(waveforms): @pytest.mark.parametrize( - "header, expected_sample_rate", + ("header", "expected_sample_rate"), [ ( WaveformHeader(wfmtype=6, iq_windowType="Blackharris", iq_fftLength=1024, iq_rbw=1e6), @@ -1102,6 +1083,13 @@ def real_callback(waveforms): ], ) def test_read_waveform_iq(tekhsi_client, header, expected_sample_rate): + """Test reading an IQ waveform. + + Args: + tekhsi_client (TekHSIConnect): An instance of the TekHSI client to be tested. + header (WaveformHeader): The header information for the waveform. + expected_sample_rate (float): The expected IQ sample rate. + """ waveform = tekhsi_client._read_waveform(header) assert isinstance(waveform, IQWaveform) assert waveform.meta_info.iq_sample_rate == pytest.approx(expected_sample_rate) From 4c05536a0a846b7cdf09346d08219b3aed49af03 Mon Sep 17 00:00:00 2001 From: "Felt, Nicholas" Date: Tue, 10 Sep 2024 16:10:25 -0700 Subject: [PATCH 4/7] style: Update code to pass ruff linting --- tests/server/tekhsi_test_server.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/server/tekhsi_test_server.py b/tests/server/tekhsi_test_server.py index 6cab35c..182351b 100644 --- a/tests/server/tekhsi_test_server.py +++ b/tests/server/tekhsi_test_server.py @@ -299,11 +299,11 @@ def GetHeader(self, request, context): # noqa: ARG002 reply.headerordata.header.sourcename = request.sourcename reply.headerordata.header.sourcewidth = 4 - if isinstance(data, AnalogWaveform): # pylint: disable=undefined-variable # noqa: F821 + if isinstance(data, AnalogWaveform): # noqa: F821 reply.headerordata.header.wfmtype = 3 - elif isinstance(data, IQWaveform): # pylint: disable=undefined-variable # noqa: F821 + elif isinstance(data, IQWaveform): # noqa: F821 reply.headerordata.header.wfmtype = 6 - elif isinstance(data, DigitalWaveform): # pylint: disable=undefined-variable # noqa: F821 + elif isinstance(data, DigitalWaveform): # noqa: F821 reply.headerordata.header.wfmtype = 4 reply.headerordata.header.pairtype = 1 @@ -398,7 +398,7 @@ def GetHeader(self, request, context): # noqa: ARG002,PLR0912,PLR0915,C901 reply.headerordata.header.noofsamples = wfm.length reply.headerordata.header.sourcename = request.sourcename - if isinstance(data, AnalogWaveform): # pylint: disable=undefined-variable # noqa: F821 + if isinstance(data, AnalogWaveform): # noqa: F821 if wfm.type == WfmDataType.Int8: reply.headerordata.header.sourcewidth = 1 reply.headerordata.header.wfmtype = 1 @@ -411,7 +411,7 @@ def GetHeader(self, request, context): # noqa: ARG002,PLR0912,PLR0915,C901 else: reply.headerordata.header.sourcewidth = 1 reply.headerordata.header.wfmtype = 1 - elif isinstance(data, IQWaveform): # pylint: disable=undefined-variable # noqa: F821 + elif isinstance(data, IQWaveform): # noqa: F821 if wfm.type == WfmDataType.Int8: reply.headerordata.header.sourcewidth = 1 reply.headerordata.header.wfmtype = 6 @@ -421,7 +421,7 @@ def GetHeader(self, request, context): # noqa: ARG002,PLR0912,PLR0915,C901 else: reply.headerordata.header.sourcewidth = 1 reply.headerordata.header.wfmtype = 6 - elif isinstance(data, DigitalWaveform): # pylint: disable=undefined-variable # noqa: F821 + elif isinstance(data, DigitalWaveform): # noqa: F821 if wfm.type == WfmDataType.Int8: reply.headerordata.header.sourcewidth = 1 reply.headerordata.header.wfmtype = 4 From 1deebec8c961d625807d570c8b483ec54758494d Mon Sep 17 00:00:00 2001 From: "Felt, Nicholas" Date: Tue, 10 Sep 2024 16:51:39 -0700 Subject: [PATCH 5/7] test: Update test server to ignore linting failures for now --- pyproject.toml | 8 +- tests/server/tekhsi_test_server.py | 279 ++++++++++++++++------------- 2 files changed, 156 insertions(+), 131 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 2053921..31020fd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -256,7 +256,9 @@ useLibraryCodeForTypes = true [tool.pytest.ini_options] addopts = "--order-scope=module --cov-config=pyproject.toml" doctest_optionflags = "ELLIPSIS NORMALIZE_WHITESPACE IGNORE_EXCEPTION_DETAIL" -filterwarnings = [] +filterwarnings = [ + "ignore:Type google._upb._message.:DeprecationWarning" +] junit_family = "xunit2" junit_logging = "all" markers = [ @@ -338,7 +340,9 @@ order-by-type = false ] "tests/server/tekhsi_test_server.py" = [ # TODO: remove this entire ignore section - "N80" + "N80", + "PLW0602", + "SIM" ] [tool.semantic_release] diff --git a/tests/server/tekhsi_test_server.py b/tests/server/tekhsi_test_server.py index 182351b..ed2140b 100644 --- a/tests/server/tekhsi_test_server.py +++ b/tests/server/tekhsi_test_server.py @@ -1,3 +1,4 @@ +# pylint: disable=global-variable-not-assigned """This file provides a simple TekHSI streaming server implementation for testing. The primary usage for this is to allow unit testing to occur on GitHub. An alternative usage is as @@ -115,11 +116,14 @@ def __init__( # noqa: C901,PLR0912,PLR0915 resolution = None offset = 0 noise = 0.01 - # FUTURE # if self._type is WfmDataType.Int8: - # FUTURE # yincr = self.amplitude / 230 - # FUTURE # if self._type is WfmDataType.Int16: - # FUTURE # yincr = self.amplitude / 58000 - yincr = 1 if self._type is WfmDataType.Float else 1 + if self._type is WfmDataType.Int8: + yincr = self.amplitude / 230 + if self._type is WfmDataType.Int16: + yincr = self.amplitude / 58000 + if self._type is WfmDataType.Float: + yincr = 1 + else: + yincr = 1 self._yincr = yincr @@ -246,27 +250,31 @@ def GetWaveform(self, request, context): # noqa: ARG002 context : Any This contains information relevant to the current gRPC call. """ + global connect_server + global verbose if verbose: print(f"TekHSI_NormalizedDataServer.GetWaveform({request.sourcename})") try: - if connect_server.dataaccess_allowed and request.sourcename in connect_server.data: - wfm = connect_server.data[request.sourcename] - chunksize = request.chunksize - for cur in range(0, len(wfm._vertical_data), chunksize): - reply = tekhsi_pb2.NormalizedReply( - headerordata=tekhsi_pb2.NormalizedReply.DataOrHeaderAccess( - chunk=tekhsi_pb2.NormalizedReply.WaveformSampleChunk( - data=wfm._vertical_data[cur : cur + chunksize], - ), - ), - ) + if connect_server.dataaccess_allowed: + if request.sourcename in connect_server.data: + wfm = connect_server.data[request.sourcename] + chunksize = request.chunksize + for cur in range(0, len(wfm._vertical_data), chunksize): + reply = tekhsi_pb2.NormalizedReply( + headerordata=tekhsi_pb2.NormalizedReply.DataOrHeaderAccess( + chunk=tekhsi_pb2.NormalizedReply.WaveformSampleChunk( + data=wfm._vertical_data[cur : cur + chunksize] + ) + ) + ) + yield reply + reply = tekhsi_pb2.NormalizedReply() + reply.status = tekhsi_pb2.WfmReplyStatus.Value("WFMREPLYSTATUS_SUCCESS") yield reply - reply = tekhsi_pb2.NormalizedReply() - reply.status = tekhsi_pb2.WfmReplyStatus.Value("WFMREPLYSTATUS_SUCCESS") - yield reply - return + return except Exception as e: print(e) + return def GetHeader(self, request, context): # noqa: ARG002 """The message returns the header (equivalent to preamble when using SCPI commands). @@ -279,43 +287,47 @@ def GetHeader(self, request, context): # noqa: ARG002 context : Any This contains information relevant to the current gRPC call. - Returns: + Returns ------- NormalizedReply The return reply contains the status + the requested header information. """ + global connect_server + global verbose + global acq_id if verbose: print(f"TekHSI_NormalizedDataServer.GetHeader({request.sourcename})") try: - if connect_server.dataaccess_allowed and request.sourcename in connect_server.data: - wfm = connect_server.data[request.sourcename] - reply = tekhsi_pb2.NormalizedReply() - reply.headerordata.header.dataid = acq_id - reply.headerordata.header.hasdata = True - reply.headerordata.header.horizontalspacing = wfm.xincr - reply.headerordata.header.horizontalUnits = "S" - reply.headerordata.header.horizontalzeroindex = wfm.trigger_index - reply.headerordata.header.noofsamples = wfm.length - reply.headerordata.header.sourcename = request.sourcename - reply.headerordata.header.sourcewidth = 4 - - if isinstance(data, AnalogWaveform): # noqa: F821 - reply.headerordata.header.wfmtype = 3 - elif isinstance(data, IQWaveform): # noqa: F821 - reply.headerordata.header.wfmtype = 6 - elif isinstance(data, DigitalWaveform): # noqa: F821 - reply.headerordata.header.wfmtype = 4 - - reply.headerordata.header.pairtype = 1 - reply.headerordata.header.verticaloffset = 0 - reply.headerordata.header.verticalspacing = 1.0 - reply.headerordata.header.verticalunits = "V" - reply.status = tekhsi_pb2.WfmReplyStatus.Value("WFMREPLYSTATUS_SUCCESS") - return reply + if connect_server.dataaccess_allowed: + if request.sourcename in connect_server.data: + wfm = connect_server.data[request.sourcename] + reply = tekhsi_pb2.NormalizedReply() + reply.headerordata.header.dataid = acq_id + reply.headerordata.header.hasdata = True + reply.headerordata.header.horizontalspacing = wfm.xincr + reply.headerordata.header.horizontalUnits = "S" + reply.headerordata.header.horizontalzeroindex = wfm.trigger_index + reply.headerordata.header.noofsamples = wfm.length + reply.headerordata.header.sourcename = request.sourcename + reply.headerordata.header.sourcewidth = 4 + + if isinstance(data, AnalogWaveform): # noqa: F821 + reply.headerordata.header.wfmtype = 3 + elif isinstance(data, IQWaveform): # noqa: F821 + reply.headerordata.header.wfmtype = 6 + elif isinstance(data, DigitalWaveform): # noqa: F821 + reply.headerordata.header.wfmtype = 4 + + reply.headerordata.header.pairtype = 1 + reply.headerordata.header.verticaloffset = 0 + reply.headerordata.header.verticalspacing = 1.0 + reply.headerordata.header.verticalunits = "V" + reply.status = tekhsi_pb2.WfmReplyStatus.Value("WFMREPLYSTATUS_SUCCESS") + return reply except Exception as e: print(e) return tekhsi_pb2.NormalizedReply( - status=tekhsi_pb2.WfmReplyStatus.Value("WFMREPLYSTATUS_FAILURE"), + status=tekhsi_pb2.WfmReplyStatus.Value("WFMREPLYSTATUS_FAILURE") ) @@ -339,31 +351,34 @@ def GetWaveform(self, request, context): # noqa: ARG002 context : Any This contains information relevant to the current gRPC call. - Returns: + Returns ------- NativeReply The return reply contains the status. The yield reply, returns each data chunk. """ + global connect_server + global verbose if verbose: print(f"TekHSI_NativeDataServer.GetWaveform({request.sourcename})") try: - if connect_server.dataaccess_allowed and request.sourcename in connect_server.data: - wfm = connect_server.data[request.sourcename] - chunksize = request.chunksize - raw_bytes = wfm._raw_data.tobytes() - for cur in range(0, len(raw_bytes), chunksize): - reply = tekhsi_pb2.RawReply( - headerordata=tekhsi_pb2.RawReply.DataOrHeaderAccess( - chunk=tekhsi_pb2.RawReply.WaveformSampleByteChunk( - data=raw_bytes[cur : cur + chunksize], - ), - ), - ) - yield reply - reply = tekhsi_pb2.RawReply() - reply.status = tekhsi_pb2.WfmReplyStatus.Value("WFMREPLYSTATUS_SUCCESS") - return reply + if connect_server.dataaccess_allowed: + if request.sourcename in connect_server.data: + wfm = connect_server.data[request.sourcename] + chunksize = request.chunksize + raw_bytes = wfm._raw_data.tobytes() + for cur in range(0, len(raw_bytes), chunksize): + reply = tekhsi_pb2.RawReply( + headerordata=tekhsi_pb2.RawReply.DataOrHeaderAccess( + chunk=tekhsi_pb2.RawReply.WaveformSampleByteChunk( + data=raw_bytes[cur : cur + chunksize] + ) + ) + ) + yield reply + reply = tekhsi_pb2.RawReply() + reply.status = tekhsi_pb2.WfmReplyStatus.Value("WFMREPLYSTATUS_SUCCESS") + return reply except Exception as e: print(e) return tekhsi_pb2.RawReply(status=tekhsi_pb2.WfmReplyStatus.Value("WFMREPLYSTATUS_FAILURE")) @@ -384,60 +399,64 @@ def GetHeader(self, request, context): # noqa: ARG002,PLR0912,PLR0915,C901 NativeReply The return reply contains the status + the requested header information. """ + global connect_server + global verbose + global acq_id if verbose: print(f"TekHSI_NativeDataServer.GetHeader({request.sourcename})") try: - if connect_server.dataaccess_allowed and request.sourcename in connect_server.data: - wfm = connect_server.data[request.sourcename] - reply = tekhsi_pb2.RawReply() - reply.headerordata.header.dataid = acq_id - reply.headerordata.header.hasdata = True - reply.headerordata.header.horizontalspacing = wfm.xincr - reply.headerordata.header.horizontalUnits = "S" - reply.headerordata.header.horizontalzeroindex = wfm.trigger_index - reply.headerordata.header.noofsamples = wfm.length - reply.headerordata.header.sourcename = request.sourcename - - if isinstance(data, AnalogWaveform): # noqa: F821 - if wfm.type == WfmDataType.Int8: - reply.headerordata.header.sourcewidth = 1 - reply.headerordata.header.wfmtype = 1 - elif wfm.type == WfmDataType.Int16: - reply.headerordata.header.sourcewidth = 2 - reply.headerordata.header.wfmtype = 2 - elif wfm.type == WfmDataType.Float: - reply.headerordata.header.sourcewidth = 4 - reply.headerordata.header.wfmtype = 3 - else: - reply.headerordata.header.sourcewidth = 1 - reply.headerordata.header.wfmtype = 1 - elif isinstance(data, IQWaveform): # noqa: F821 - if wfm.type == WfmDataType.Int8: - reply.headerordata.header.sourcewidth = 1 - reply.headerordata.header.wfmtype = 6 - elif wfm.type == WfmDataType.Int16: - reply.headerordata.header.sourcewidth = 2 - reply.headerordata.header.wfmtype = 7 - else: - reply.headerordata.header.sourcewidth = 1 - reply.headerordata.header.wfmtype = 6 - elif isinstance(data, DigitalWaveform): # noqa: F821 - if wfm.type == WfmDataType.Int8: - reply.headerordata.header.sourcewidth = 1 - reply.headerordata.header.wfmtype = 4 - elif wfm.type == WfmDataType.Int16: - reply.headerordata.header.sourcewidth = 2 - reply.headerordata.header.wfmtype = 5 - else: - reply.headerordata.header.sourcewidth = 1 - reply.headerordata.header.wfmtype = 4 - - reply.headerordata.header.pairtype = 1 - reply.headerordata.header.verticaloffset = 0 - reply.headerordata.header.verticalspacing = wfm.yincr - reply.headerordata.header.verticalunits = "V" - reply.status = tekhsi_pb2.WfmReplyStatus.Value("WFMREPLYSTATUS_SUCCESS") - return reply + if connect_server.dataaccess_allowed: + if request.sourcename in connect_server.data: + wfm = connect_server.data[request.sourcename] + reply = tekhsi_pb2.RawReply() + reply.headerordata.header.dataid = acq_id + reply.headerordata.header.hasdata = True + reply.headerordata.header.horizontalspacing = wfm.xincr + reply.headerordata.header.horizontalUnits = "S" + reply.headerordata.header.horizontalzeroindex = wfm.trigger_index + reply.headerordata.header.noofsamples = wfm.length + reply.headerordata.header.sourcename = request.sourcename + + if isinstance(data, AnalogWaveform): # noqa: F821 + if wfm.type == WfmDataType.Int8: + reply.headerordata.header.sourcewidth = 1 + reply.headerordata.header.wfmtype = 1 + elif wfm.type == WfmDataType.Int16: + reply.headerordata.header.sourcewidth = 2 + reply.headerordata.header.wfmtype = 2 + elif wfm.type == WfmDataType.Float: + reply.headerordata.header.sourcewidth = 4 + reply.headerordata.header.wfmtype = 3 + else: + reply.headerordata.header.sourcewidth = 1 + reply.headerordata.header.wfmtype = 1 + elif isinstance(data, IQWaveform): # noqa: F821 + if wfm.type == WfmDataType.Int8: + reply.headerordata.header.sourcewidth = 1 + reply.headerordata.header.wfmtype = 6 + elif wfm.type == WfmDataType.Int16: + reply.headerordata.header.sourcewidth = 2 + reply.headerordata.header.wfmtype = 7 + else: + reply.headerordata.header.sourcewidth = 1 + reply.headerordata.header.wfmtype = 6 + elif isinstance(data, DigitalWaveform): # noqa: F821 + if wfm.type == WfmDataType.Int8: + reply.headerordata.header.sourcewidth = 1 + reply.headerordata.header.wfmtype = 4 + elif wfm.type == WfmDataType.Int16: + reply.headerordata.header.sourcewidth = 2 + reply.headerordata.header.wfmtype = 5 + else: + reply.headerordata.header.sourcewidth = 1 + reply.headerordata.header.wfmtype = 4 + + reply.headerordata.header.pairtype = 1 + reply.headerordata.header.verticaloffset = 0 + reply.headerordata.header.verticalspacing = wfm.yincr + reply.headerordata.header.verticalunits = "V" + reply.status = tekhsi_pb2.WfmReplyStatus.Value("WFMREPLYSTATUS_SUCCESS") + return reply except Exception as e: print(e) return tekhsi_pb2.RawReply(status=tekhsi_pb2.WfmReplyStatus.Value("WFMREPLYSTATUS_FAILURE")) @@ -485,7 +504,7 @@ def Connect(self, request, context): if self._connections.get(request.name): context.set_code(grpc.StatusCode.ALREADY_EXISTS) return tekhsi_pb2.ConnectReply( - status=tekhsi_pb2.ConnectStatus.Value("CONNECTSTATUS_INUSE_FAILURE"), + status=tekhsi_pb2.ConnectStatus.Value("CONNECTSTATUS_INUSE_FAILURE") ) self._connections[request.name] = True context.set_code(grpc.StatusCode.OK) @@ -495,13 +514,13 @@ def Connect(self, request, context): if verbose: print(f'connection successful - "{request.name}"') return tekhsi_pb2.ConnectReply( - status=tekhsi_pb2.ConnectStatus.Value("CONNECTSTATUS_SUCCESS"), + status=tekhsi_pb2.ConnectStatus.Value("CONNECTSTATUS_SUCCESS") ) except Exception as e: if verbose: print(f'** Exception:{e}: Connect - "{request.name}"') return tekhsi_pb2.ConnectReply( - status=tekhsi_pb2.ConnectStatus.Value("CONNECTSTATUS_UNSPECIFIED"), + status=tekhsi_pb2.ConnectStatus.Value("CONNECTSTATUS_UNSPECIFIED") ) def Disconnect(self, request, context): # noqa: C901 @@ -536,7 +555,7 @@ def Disconnect(self, request, context): # noqa: C901 if verbose: print(f'Disconnect Success - but used a bad name {request.name}"') return tekhsi_pb2.ConnectReply( - status=tekhsi_pb2.ConnectStatus.Value("CONNECTSTATUS_UNSPECIFIED"), + status=tekhsi_pb2.ConnectStatus.Value("CONNECTSTATUS_UNSPECIFIED") ) except Exception as e: if self._new_data: @@ -550,7 +569,7 @@ def Disconnect(self, request, context): # noqa: C901 if verbose: print(f'** Exception: Disconnect - "{request.name} - {e}"') return tekhsi_pb2.ConnectReply( - status=tekhsi_pb2.ConnectStatus.Value("CONNECTSTATUS_UNSPECIFIED"), + status=tekhsi_pb2.ConnectStatus.Value("CONNECTSTATUS_UNSPECIFIED") ) def RequestNewSequence(self, request, context): @@ -561,6 +580,7 @@ def RequestNewSequence(self, request, context): else: print(f'RequestNewSequence Failed - No Connection "{request.name}"') + global connect_server mutex.acquire() if verbose: print("mutex-acquired: RequestNewSequence") @@ -571,7 +591,7 @@ def RequestNewSequence(self, request, context): print("mutex-released: RequestNewSequence") context.set_code(grpc.StatusCode.OK) return tekhsi_pb2.ConnectReply( - status=tekhsi_pb2.ConnectStatus.Value("CONNECTSTATUS_SUCCESS"), + status=tekhsi_pb2.ConnectStatus.Value("CONNECTSTATUS_SUCCESS") ) except Exception as e: context.set_code(grpc.StatusCode.FAILED_PRECONDITION) @@ -579,7 +599,7 @@ def RequestNewSequence(self, request, context): print(e) print(f'** Exception as: RequestNewSequence - "{request.name}"') return tekhsi_pb2.ConnectReply( - status=tekhsi_pb2.ConnectStatus.Value("CONNECTSTATUS_UNSPECIFIED"), + status=tekhsi_pb2.ConnectStatus.Value("CONNECTSTATUS_UNSPECIFIED") ) def RequestAvailableNames(self, request, context): @@ -600,7 +620,7 @@ def RequestAvailableNames(self, request, context): print(f'** Exception: RequestNewSequence - "{request.name}"') context.set_code(grpc.StatusCode.FAILED_PRECONDITION) return tekhsi_pb2.AvailableNamesReply( - status=tekhsi_pb2.ConnectStatus.Value("CONNECTSTATUS_UNSPECIFIED"), + status=tekhsi_pb2.ConnectStatus.Value("CONNECTSTATUS_UNSPECIFIED") ) def WaitForDataAccess(self, request, context): @@ -622,14 +642,14 @@ def WaitForDataAccess(self, request, context): self._dataaccess_allowed = True context.set_code(grpc.StatusCode.OK) return tekhsi_pb2.ConnectReply( - status=tekhsi_pb2.ConnectStatus.Value("CONNECTSTATUS_SUCCESS"), + status=tekhsi_pb2.ConnectStatus.Value("CONNECTSTATUS_SUCCESS") ) except Exception as e: if verbose: print(e) print(f'** Exception: WaitForDataAccess - "{request.name}"') return tekhsi_pb2.ConnectReply( - status=tekhsi_pb2.ConnectStatus.Value("CONNECTSTATUS_UNSPECIFIED"), + status=tekhsi_pb2.ConnectStatus.Value("CONNECTSTATUS_UNSPECIFIED") ) def FinishedWithDataAccess(self, request, context): @@ -647,12 +667,12 @@ def FinishedWithDataAccess(self, request, context): print(f'FinishedWithDataAccess Bad Connection Name "{request.name}"') context.set_code(grpc.StatusCode.OK) return tekhsi_pb2.ConnectReply( - status=tekhsi_pb2.ConnectStatus.Value("CONNECTSTATUS_SUCCESS"), + status=tekhsi_pb2.ConnectStatus.Value("CONNECTSTATUS_SUCCESS") ) if verbose: print(f'FinishedWithDataAccess Failed "{request.name} - No WaitForDataPending"') return tekhsi_pb2.ConnectReply( - status=tekhsi_pb2.ConnectStatus.Value("CONNECTSTATUS_UNSPECIFIED"), + status=tekhsi_pb2.ConnectStatus.Value("CONNECTSTATUS_UNSPECIFIED") ) except Exception as e: @@ -660,7 +680,7 @@ def FinishedWithDataAccess(self, request, context): print(e) print(f'** Exception: FinishedWithDataAccess - "{request.name}"') return tekhsi_pb2.ConnectReply( - status=tekhsi_pb2.ConnectStatus.Value("CONNECTSTATUS_UNSPECIFIED"), + status=tekhsi_pb2.ConnectStatus.Value("CONNECTSTATUS_UNSPECIFIED") ) @@ -672,6 +692,7 @@ def periodic_data_creation(): If you want to change the named sets of data returned you should modify 'make_new_data()' """ while True: + global connect_server global acq_id try: mutex.acquire() @@ -689,7 +710,7 @@ def make_new_data(): symbol names and associated data. This is called on initialization of the TekHSI_Connect service and periodically to create new data. The symbols defined here well be seen by the client. - Returns: + Returns ------- dict Returns a name/value dictionary defining a set of symbols and their associated waveforms. From 30a276bde7850c476bd58d34a998004ec24faf04 Mon Sep 17 00:00:00 2001 From: "Felt, Nicholas" Date: Tue, 10 Sep 2024 16:58:45 -0700 Subject: [PATCH 6/7] fix: linting fixes for test server --- tests/server/tekhsi_test_server.py | 58 +++++++++++++++--------------- 1 file changed, 28 insertions(+), 30 deletions(-) diff --git a/tests/server/tekhsi_test_server.py b/tests/server/tekhsi_test_server.py index ed2140b..9e6a0ab 100644 --- a/tests/server/tekhsi_test_server.py +++ b/tests/server/tekhsi_test_server.py @@ -9,18 +9,16 @@ forward while remaining backwards compatible. """ -import math import sys +import grpc import time - -from concurrent import futures -from enum import Enum +import math from threading import Lock, Thread - -import grpc import numpy as np +from enum import Enum +from concurrent import futures -from tm_data_types import AnalogWaveform, DigitalWaveform, IQWaveform +from tm_data_types import AnalogWaveform, IQWaveform, DigitalWaveform import tekhsi._tek_highspeed_server_pb2 as tekhsi_pb2 import tekhsi._tek_highspeed_server_pb2_grpc as tekhsi_pb2_grpc @@ -222,9 +220,7 @@ def _add_noise(array, noise_range: float): This is to make it visually clear that each waveform is unique. """ - return np.array(array) + np.random.Generator.normal( - loc=0.0, scale=noise_range / 4, size=len(array) - ) + return np.array(array) + np.random.normal(loc=0.0, scale=noise_range / 4, size=len(array)) class TekHSI_NormalizedDataServer(tekhsi_pb2_grpc.NormalizedDataServicer): @@ -394,7 +390,7 @@ def GetHeader(self, request, context): # noqa: ARG002,PLR0912,PLR0915,C901 context : Any This contains information relevant to the current gRPC call. - Returns: + Returns ------- NativeReply The return reply contains the status + the requested header information. @@ -543,20 +539,21 @@ def Disconnect(self, request, context): # noqa: C901 return tekhsi_pb2.ConnectReply( status=tekhsi_pb2.ConnectStatus.Value("CONNECTSTATUS_SUCCESS"), ) - self._connections.clear() - if self._new_data: - self.FinishedWithDataAccess(request, context) - # force a cleanup - self._new_data = False - self._dataaccess_allowed = False - if mutex.locked(): - mutex.release() - context.set_code(grpc.StatusCode.OK) - if verbose: - print(f'Disconnect Success - but used a bad name {request.name}"') - return tekhsi_pb2.ConnectReply( - status=tekhsi_pb2.ConnectStatus.Value("CONNECTSTATUS_UNSPECIFIED") - ) + else: + self._connections.clear() + if self._new_data: + self.FinishedWithDataAccess(request, context) + # force a cleanup + self._new_data = False + self._dataaccess_allowed = False + if mutex.locked(): + mutex.release() + context.set_code(grpc.StatusCode.OK) + if verbose: + print(f'Disconnect Success - but used a bad name {request.name}"') + return tekhsi_pb2.ConnectReply( + status=tekhsi_pb2.ConnectStatus.Value("CONNECTSTATUS_UNSPECIFIED") + ) except Exception as e: if self._new_data: self.FinishedWithDataAccess(request, context) @@ -669,11 +666,12 @@ def FinishedWithDataAccess(self, request, context): return tekhsi_pb2.ConnectReply( status=tekhsi_pb2.ConnectStatus.Value("CONNECTSTATUS_SUCCESS") ) - if verbose: - print(f'FinishedWithDataAccess Failed "{request.name} - No WaitForDataPending"') - return tekhsi_pb2.ConnectReply( - status=tekhsi_pb2.ConnectStatus.Value("CONNECTSTATUS_UNSPECIFIED") - ) + else: + if verbose: + print(f'FinishedWithDataAccess Failed "{request.name} - No WaitForDataPending"') + return tekhsi_pb2.ConnectReply( + status=tekhsi_pb2.ConnectStatus.Value("CONNECTSTATUS_UNSPECIFIED") + ) except Exception as e: if verbose: From 79df11d06950497d2a2dcc7ed9d02dafdba2bf36 Mon Sep 17 00:00:00 2001 From: "Felt, Nicholas" Date: Tue, 10 Sep 2024 17:09:40 -0700 Subject: [PATCH 7/7] fix: linting fixes for test server --- tests/server/tekhsi_test_server.py | 54 +++++++++++++++--------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/tests/server/tekhsi_test_server.py b/tests/server/tekhsi_test_server.py index 9e6a0ab..70f9984 100644 --- a/tests/server/tekhsi_test_server.py +++ b/tests/server/tekhsi_test_server.py @@ -9,16 +9,18 @@ forward while remaining backwards compatible. """ +import math import sys -import grpc import time -import math + +from concurrent import futures +from enum import Enum from threading import Lock, Thread + +import grpc import numpy as np -from enum import Enum -from concurrent import futures -from tm_data_types import AnalogWaveform, IQWaveform, DigitalWaveform +from tm_data_types import AnalogWaveform, DigitalWaveform, IQWaveform import tekhsi._tek_highspeed_server_pb2 as tekhsi_pb2 import tekhsi._tek_highspeed_server_pb2_grpc as tekhsi_pb2_grpc @@ -220,7 +222,7 @@ def _add_noise(array, noise_range: float): This is to make it visually clear that each waveform is unique. """ - return np.array(array) + np.random.normal(loc=0.0, scale=noise_range / 4, size=len(array)) + return np.array(array) + np.random.normal(loc=0.0, scale=noise_range / 4, size=len(array)) # noqa: NPY002 class TekHSI_NormalizedDataServer(tekhsi_pb2_grpc.NormalizedDataServicer): @@ -539,21 +541,20 @@ def Disconnect(self, request, context): # noqa: C901 return tekhsi_pb2.ConnectReply( status=tekhsi_pb2.ConnectStatus.Value("CONNECTSTATUS_SUCCESS"), ) - else: - self._connections.clear() - if self._new_data: - self.FinishedWithDataAccess(request, context) - # force a cleanup - self._new_data = False - self._dataaccess_allowed = False - if mutex.locked(): - mutex.release() - context.set_code(grpc.StatusCode.OK) - if verbose: - print(f'Disconnect Success - but used a bad name {request.name}"') - return tekhsi_pb2.ConnectReply( - status=tekhsi_pb2.ConnectStatus.Value("CONNECTSTATUS_UNSPECIFIED") - ) + self._connections.clear() + if self._new_data: + self.FinishedWithDataAccess(request, context) + # force a cleanup + self._new_data = False + self._dataaccess_allowed = False + if mutex.locked(): + mutex.release() + context.set_code(grpc.StatusCode.OK) + if verbose: + print(f'Disconnect Success - but used a bad name {request.name}"') + return tekhsi_pb2.ConnectReply( + status=tekhsi_pb2.ConnectStatus.Value("CONNECTSTATUS_UNSPECIFIED") + ) except Exception as e: if self._new_data: self.FinishedWithDataAccess(request, context) @@ -666,12 +667,11 @@ def FinishedWithDataAccess(self, request, context): return tekhsi_pb2.ConnectReply( status=tekhsi_pb2.ConnectStatus.Value("CONNECTSTATUS_SUCCESS") ) - else: - if verbose: - print(f'FinishedWithDataAccess Failed "{request.name} - No WaitForDataPending"') - return tekhsi_pb2.ConnectReply( - status=tekhsi_pb2.ConnectStatus.Value("CONNECTSTATUS_UNSPECIFIED") - ) + if verbose: + print(f'FinishedWithDataAccess Failed "{request.name} - No WaitForDataPending"') + return tekhsi_pb2.ConnectReply( + status=tekhsi_pb2.ConnectStatus.Value("CONNECTSTATUS_UNSPECIFIED") + ) except Exception as e: if verbose: