From 85a013a1a9cdc5be5a23a19b207feb77b6362304 Mon Sep 17 00:00:00 2001 From: MatthieuDartiailh Date: Mon, 27 May 2024 17:45:27 +0200 Subject: [PATCH 01/17] tcpip: implement open timeout for hislip --- pyvisa_py/protocols/hislip.py | 2 ++ pyvisa_py/tcpip.py | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/pyvisa_py/protocols/hislip.py b/pyvisa_py/protocols/hislip.py index 8df77234..db412a2f 100644 --- a/pyvisa_py/protocols/hislip.py +++ b/pyvisa_py/protocols/hislip.py @@ -367,6 +367,7 @@ class Instrument: def __init__( self, ip_addr: str, + open_timeout: float = 0.0, timeout: Optional[float] = None, port: int = PORT, sub_address: str = "hislip0", @@ -381,6 +382,7 @@ def __init__( # open the synchronous socket and send an initialize packet self._sync = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self._sync.settimeout(open_timeout) self._sync.connect((ip_addr, port)) self._sync.settimeout(timeout) self._sync.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) diff --git a/pyvisa_py/tcpip.py b/pyvisa_py/tcpip.py index 3c6f4a88..ef7371c5 100644 --- a/pyvisa_py/tcpip.py +++ b/pyvisa_py/tcpip.py @@ -137,6 +137,11 @@ def after_parsing(self) -> None: port = 4880 self.interface = hislip.Instrument( self.parsed.host_address, + open_timeout=( + self.open_timeout * 1000 + if self.open_timeout is not None + else self.open_timeout + ), timeout=self.timeout, port=port, sub_address=sub_address, From 718cd7520e55f131c436efb4008b91cdd33b9807 Mon Sep 17 00:00:00 2001 From: MatthieuDartiailh Date: Mon, 27 May 2024 17:48:58 +0200 Subject: [PATCH 02/17] tcpip: fix typing --- pyvisa_py/protocols/hislip.py | 2 +- pyvisa_py/tcpip.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyvisa_py/protocols/hislip.py b/pyvisa_py/protocols/hislip.py index db412a2f..c4808b1f 100644 --- a/pyvisa_py/protocols/hislip.py +++ b/pyvisa_py/protocols/hislip.py @@ -367,7 +367,7 @@ class Instrument: def __init__( self, ip_addr: str, - open_timeout: float = 0.0, + open_timeout: float | None = 0.0, timeout: Optional[float] = None, port: int = PORT, sub_address: str = "hislip0", diff --git a/pyvisa_py/tcpip.py b/pyvisa_py/tcpip.py index ef7371c5..f426095f 100644 --- a/pyvisa_py/tcpip.py +++ b/pyvisa_py/tcpip.py @@ -138,7 +138,7 @@ def after_parsing(self) -> None: self.interface = hislip.Instrument( self.parsed.host_address, open_timeout=( - self.open_timeout * 1000 + self.open_timeout * 1000.0 if self.open_timeout is not None else self.open_timeout ), From 2c5829ba477eac8d39dff1797c933ef02e772b8b Mon Sep 17 00:00:00 2001 From: MatthieuDartiailh Date: Mon, 27 May 2024 17:59:54 +0200 Subject: [PATCH 03/17] testsuite: add debug prints --- pyvisa_py/testsuite/test_sessions.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyvisa_py/testsuite/test_sessions.py b/pyvisa_py/testsuite/test_sessions.py index 91c90514..7618d7de 100644 --- a/pyvisa_py/testsuite/test_sessions.py +++ b/pyvisa_py/testsuite/test_sessions.py @@ -73,5 +73,6 @@ def test_sessions(self): except Exception: exp_missing.append(vicp) + print(available, missing) assert sorted(available) == sorted(expected) assert sorted(missing) == sorted(exp_missing) From 4ac2711c5629f762c39d4bad45d725b8ba1404ae Mon Sep 17 00:00:00 2001 From: MatthieuDartiailh Date: Mon, 27 May 2024 18:16:24 +0200 Subject: [PATCH 04/17] =?UTF-8?q?=EF=BB=BFfix=20issue=20on=203.9=20and=20a?= =?UTF-8?q?dd=20a=20changelog=20entry?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGES | 1 + dev-requirements.txt | 1 + pyvisa_py/protocols/hislip.py | 2 +- pyvisa_py/testsuite/test_sessions.py | 1 + 4 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 91133c39..cacddd7a 100644 --- a/CHANGES +++ b/CHANGES @@ -13,6 +13,7 @@ PyVISA-py Changelog - Implemented partial USBTMC message functionality that allows reading the amount of bytes specified by host PR #470 - add support for VI_ATTR_SUPPRESS_END_EN for USB resources PR #449 +- support open_timeout for TCPIP hislip resources PR #430 0.7.2 (07/03/2024) ------------------ diff --git a/dev-requirements.txt b/dev-requirements.txt index dc669f42..97988f64 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -1,3 +1,4 @@ ruff +pytest sphinx sphinx-rtd-theme \ No newline at end of file diff --git a/pyvisa_py/protocols/hislip.py b/pyvisa_py/protocols/hislip.py index c4808b1f..107eeae1 100644 --- a/pyvisa_py/protocols/hislip.py +++ b/pyvisa_py/protocols/hislip.py @@ -367,7 +367,7 @@ class Instrument: def __init__( self, ip_addr: str, - open_timeout: float | None = 0.0, + open_timeout: Optional[float] = 0.0, timeout: Optional[float] = None, port: int = PORT, sub_address: str = "hislip0", diff --git a/pyvisa_py/testsuite/test_sessions.py b/pyvisa_py/testsuite/test_sessions.py index 7618d7de..9124a191 100644 --- a/pyvisa_py/testsuite/test_sessions.py +++ b/pyvisa_py/testsuite/test_sessions.py @@ -10,6 +10,7 @@ from pyvisa.constants import InterfaceType from pyvisa.testsuite import BaseTestCase +import pyvisa_py.highlevel from pyvisa_py.sessions import Session From c56962cb80d2c04cd789d6bc6ceee8b9fdcfd6e8 Mon Sep 17 00:00:00 2001 From: MatthieuDartiailh Date: Mon, 27 May 2024 18:55:27 +0200 Subject: [PATCH 05/17] =?UTF-8?q?=EF=BB=BFfix=20linting=20issues?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyvisa_py/__init__.py | 1 + pyvisa_py/testsuite/test_sessions.py | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/pyvisa_py/__init__.py b/pyvisa_py/__init__.py index 13964fcc..0e6ffc0d 100644 --- a/pyvisa_py/__init__.py +++ b/pyvisa_py/__init__.py @@ -17,6 +17,7 @@ pass # We need to import all attributes so that __init_subclass__() is executed once +# (hence the noqa) from . import attributes # noqa: F401 from .highlevel import PyVisaLibrary diff --git a/pyvisa_py/testsuite/test_sessions.py b/pyvisa_py/testsuite/test_sessions.py index 9124a191..7618d7de 100644 --- a/pyvisa_py/testsuite/test_sessions.py +++ b/pyvisa_py/testsuite/test_sessions.py @@ -10,7 +10,6 @@ from pyvisa.constants import InterfaceType from pyvisa.testsuite import BaseTestCase -import pyvisa_py.highlevel from pyvisa_py.sessions import Session From 5f10f095ddfb92ab5b2da19f91689944d6424291 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 8 Jul 2024 19:55:39 +0200 Subject: [PATCH 06/17] =?UTF-8?q?=EF=BB=BF[pre-commit.ci]=20pre-commit=20a?= =?UTF-8?q?utoupdate=20(#440)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.5.0 → v0.5.1](https://github.com/astral-sh/ruff-pre-commit/compare/v0.5.0...v0.5.1) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a2f2e993..9a2f5317 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,7 +37,7 @@ jobs: - name: Linting if: always() run: | - ruff check pyvisa_py; + ruff pyvisa_py; - name: Mypy if: always() run: | From 6042c085946bcbf081ba6bdbcec8ecf339a1360a Mon Sep 17 00:00:00 2001 From: MatthieuDartiailh Date: Mon, 27 May 2024 18:16:24 +0200 Subject: [PATCH 07/17] =?UTF-8?q?=EF=BB=BFfix=20issue=20on=203.9=20and=20a?= =?UTF-8?q?dd=20a=20changelog=20entry?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyvisa_py/testsuite/test_sessions.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyvisa_py/testsuite/test_sessions.py b/pyvisa_py/testsuite/test_sessions.py index 7618d7de..9124a191 100644 --- a/pyvisa_py/testsuite/test_sessions.py +++ b/pyvisa_py/testsuite/test_sessions.py @@ -10,6 +10,7 @@ from pyvisa.constants import InterfaceType from pyvisa.testsuite import BaseTestCase +import pyvisa_py.highlevel from pyvisa_py.sessions import Session From e3d05f6d88f8f6f7ee2c6abada656bd3ace46c39 Mon Sep 17 00:00:00 2001 From: MatthieuDartiailh Date: Tue, 17 Sep 2024 19:13:24 +0200 Subject: [PATCH 08/17] =?UTF-8?q?=EF=BB=BFsessions:=20improve=20handling?= =?UTF-8?q?=20of=20open=20connection=20error=20for=20TCPIP=20resources?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyvisa_py/__init__.py | 3 +++ pyvisa_py/highlevel.py | 5 +++- pyvisa_py/sessions.py | 7 ++++++ pyvisa_py/tcpip.py | 55 ++++++++++++++++++++++++++++-------------- 4 files changed, 51 insertions(+), 19 deletions(-) diff --git a/pyvisa_py/__init__.py b/pyvisa_py/__init__.py index 0e6ffc0d..eb61dd7b 100644 --- a/pyvisa_py/__init__.py +++ b/pyvisa_py/__init__.py @@ -7,8 +7,11 @@ """ +import logging from importlib.metadata import PackageNotFoundError, version +LOGGER = logging.getLogger("pyvisa.pyvisa-py") + __version__ = "unknown" try: __version__ = version(__name__) diff --git a/pyvisa_py/highlevel.py b/pyvisa_py/highlevel.py index 26a78ca0..d9eba98c 100644 --- a/pyvisa_py/highlevel.py +++ b/pyvisa_py/highlevel.py @@ -165,7 +165,10 @@ def open( parsed.interface_type_const, parsed.resource_class ) - sess = cls(session, resource_name, parsed, open_timeout) + try: + sess = cls(session, resource_name, parsed, open_timeout) + except sessions.OpenError as e: + return 0, e.error_code return self._register(sess), StatusCode.success diff --git a/pyvisa_py/sessions.py b/pyvisa_py/sessions.py index b2a09eb5..09bbd341 100644 --- a/pyvisa_py/sessions.py +++ b/pyvisa_py/sessions.py @@ -32,6 +32,13 @@ T = TypeVar("T", bound=Type["Session"]) +class OpenError(Exception): + """Custom exception signaling we failed to open a resource.""" + + def __init__(self, error_code: int = StatusCode.error_resource_not_found): + self.error_code = error_code + + class UnknownAttribute(Exception): """Custom exception signaling a VISA attribute is not supported.""" diff --git a/pyvisa_py/tcpip.py b/pyvisa_py/tcpip.py index f426095f..d7ce29b7 100644 --- a/pyvisa_py/tcpip.py +++ b/pyvisa_py/tcpip.py @@ -18,9 +18,10 @@ from pyvisa import attributes, constants, errors, rname from pyvisa.constants import BufferOperation, ResourceAttribute, StatusCode -from . import common +from . import common, LOGGER from .protocols import hislip, rpc, vxi11 -from .sessions import Session, UnknownAttribute, VISARMSession +from .sessions import OpenError, Session, UnknownAttribute, VISARMSession + # Let psutil be optional dependency try: @@ -135,17 +136,25 @@ def after_parsing(self) -> None: else: sub_address = self.parsed.lan_device_name port = 4880 - self.interface = hislip.Instrument( - self.parsed.host_address, - open_timeout=( - self.open_timeout * 1000.0 - if self.open_timeout is not None - else self.open_timeout - ), - timeout=self.timeout, - port=port, - sub_address=sub_address, - ) + + try: + self.interface = hislip.Instrument( + self.parsed.host_address, + open_timeout=( + self.open_timeout * 1000.0 + if self.open_timeout is not None + else self.open_timeout + ), + timeout=self.timeout, + port=port, + sub_address=sub_address, + ) + except OSError as e: + LOGGER.exception( + f"Failed to open HiSLIP connection to {self.parsed.host_address} " + f"on port {port} with lan device name {sub_address}" + ) + raise OpenError() from e # initialize the constant attributes self.attrs[ResourceAttribute.dma_allow_enabled] = constants.VI_FALSE @@ -482,7 +491,10 @@ def after_parsing(self) -> None: try: self.interface = Vxi11CoreClient(host_address, port, self.open_timeout) except rpc.RPCError: - raise errors.VisaIOError(constants.VI_ERROR_RSRC_NFOUND) + LOGGER.exception( + f"Failed to open VX11 connection to {host_address} on port {port}" + ) + raise OpenError() # vxi11 expect all timeouts to be expressed in ms and should be integers self.lock_timeout = 10000 @@ -511,7 +523,7 @@ def close(self) -> StatusCode: try: self.interface.destroy_link(self.link) except (errors.VisaIOError, socket.error, rpc.RPCError) as e: - print("Error closing VISA link: {}".format(e)) + LOGGER.error("Error closing VISA link: {}".format(e)) self.interface.close() self.link = 0 @@ -870,9 +882,16 @@ def after_parsing(self) -> None: else: port = 1861 - self.interface = pyvicp.Client( - self.parsed.host_address, port, timeout=self.timeout - ) + try: + self.interface = pyvicp.Client( + self.parsed.host_address, port, timeout=self.timeout + ) + except OSError as e: + LOGGER.exception( + f"Failed to open VICP connection to {self.parsed.host_address} " + f"on port {port}" + ) + raise OpenError() from e # initialize the constant attributes for name in ("SEND_END_EN", "TERMCHAR", "TERMCHAR_EN"): From 3011546c94652c282447cddd92010861a87a3ad1 Mon Sep 17 00:00:00 2001 From: MatthieuDartiailh Date: Tue, 17 Sep 2024 19:28:04 +0200 Subject: [PATCH 09/17] ci and linter fixes --- .github/workflows/ci.yml | 2 +- pyvisa_py/sessions.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9a2f5317..a2f2e993 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,7 +37,7 @@ jobs: - name: Linting if: always() run: | - ruff pyvisa_py; + ruff check pyvisa_py; - name: Mypy if: always() run: | diff --git a/pyvisa_py/sessions.py b/pyvisa_py/sessions.py index 09bbd341..edfa792c 100644 --- a/pyvisa_py/sessions.py +++ b/pyvisa_py/sessions.py @@ -35,7 +35,7 @@ class OpenError(Exception): """Custom exception signaling we failed to open a resource.""" - def __init__(self, error_code: int = StatusCode.error_resource_not_found): + def __init__(self, error_code: StatusCode = StatusCode.error_resource_not_found): self.error_code = error_code From 77f1c3287d27e4b5927a096287edb7a367a06852 Mon Sep 17 00:00:00 2001 From: MatthieuDartiailh Date: Tue, 17 Sep 2024 19:32:01 +0200 Subject: [PATCH 10/17] =?UTF-8?q?=EF=BB=BFlinter=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyvisa_py/__init__.py | 12 +++++++----- pyvisa_py/highlevel.py | 2 +- pyvisa_py/tcpip.py | 3 +-- pyvisa_py/testsuite/test_sessions.py | 1 - 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pyvisa_py/__init__.py b/pyvisa_py/__init__.py index eb61dd7b..678561de 100644 --- a/pyvisa_py/__init__.py +++ b/pyvisa_py/__init__.py @@ -10,6 +10,13 @@ import logging from importlib.metadata import PackageNotFoundError, version +# We need to import all attributes so that __init_subclass__() is executed once +# (hence the noqa) +from . import attributes # noqa: F401 +from .highlevel import PyVisaLibrary + +# Global pyvisa-py logger used to provide more details on errors (VISA error code +# can make some report rather terse). LOGGER = logging.getLogger("pyvisa.pyvisa-py") __version__ = "unknown" @@ -19,9 +26,4 @@ # package is not installed pass -# We need to import all attributes so that __init_subclass__() is executed once -# (hence the noqa) -from . import attributes # noqa: F401 -from .highlevel import PyVisaLibrary - WRAPPER_CLASS = PyVisaLibrary diff --git a/pyvisa_py/highlevel.py b/pyvisa_py/highlevel.py index d9eba98c..baf29ed9 100644 --- a/pyvisa_py/highlevel.py +++ b/pyvisa_py/highlevel.py @@ -168,7 +168,7 @@ def open( try: sess = cls(session, resource_name, parsed, open_timeout) except sessions.OpenError as e: - return 0, e.error_code + return VISASession(0), e.error_code return self._register(sess), StatusCode.success diff --git a/pyvisa_py/tcpip.py b/pyvisa_py/tcpip.py index d7ce29b7..be4c1446 100644 --- a/pyvisa_py/tcpip.py +++ b/pyvisa_py/tcpip.py @@ -18,11 +18,10 @@ from pyvisa import attributes, constants, errors, rname from pyvisa.constants import BufferOperation, ResourceAttribute, StatusCode -from . import common, LOGGER +from . import LOGGER, common from .protocols import hislip, rpc, vxi11 from .sessions import OpenError, Session, UnknownAttribute, VISARMSession - # Let psutil be optional dependency try: import psutil # type: ignore diff --git a/pyvisa_py/testsuite/test_sessions.py b/pyvisa_py/testsuite/test_sessions.py index 9124a191..7618d7de 100644 --- a/pyvisa_py/testsuite/test_sessions.py +++ b/pyvisa_py/testsuite/test_sessions.py @@ -10,7 +10,6 @@ from pyvisa.constants import InterfaceType from pyvisa.testsuite import BaseTestCase -import pyvisa_py.highlevel from pyvisa_py.sessions import Session From a4a10de877a10f8eefe931acb7595a307b8f4025 Mon Sep 17 00:00:00 2001 From: MatthieuDartiailh Date: Wed, 18 Sep 2024 09:03:26 +0200 Subject: [PATCH 11/17] use the right logger in tcpip sessions --- pyvisa_py/__init__.py | 5 ----- pyvisa_py/tcpip.py | 10 +++++----- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/pyvisa_py/__init__.py b/pyvisa_py/__init__.py index 678561de..50d23168 100644 --- a/pyvisa_py/__init__.py +++ b/pyvisa_py/__init__.py @@ -7,7 +7,6 @@ """ -import logging from importlib.metadata import PackageNotFoundError, version # We need to import all attributes so that __init_subclass__() is executed once @@ -15,10 +14,6 @@ from . import attributes # noqa: F401 from .highlevel import PyVisaLibrary -# Global pyvisa-py logger used to provide more details on errors (VISA error code -# can make some report rather terse). -LOGGER = logging.getLogger("pyvisa.pyvisa-py") - __version__ = "unknown" try: __version__ = version(__name__) diff --git a/pyvisa_py/tcpip.py b/pyvisa_py/tcpip.py index be4c1446..bde43bae 100644 --- a/pyvisa_py/tcpip.py +++ b/pyvisa_py/tcpip.py @@ -18,7 +18,7 @@ from pyvisa import attributes, constants, errors, rname from pyvisa.constants import BufferOperation, ResourceAttribute, StatusCode -from . import LOGGER, common +from . import common from .protocols import hislip, rpc, vxi11 from .sessions import OpenError, Session, UnknownAttribute, VISARMSession @@ -149,7 +149,7 @@ def after_parsing(self) -> None: sub_address=sub_address, ) except OSError as e: - LOGGER.exception( + common.logger.exception( f"Failed to open HiSLIP connection to {self.parsed.host_address} " f"on port {port} with lan device name {sub_address}" ) @@ -490,7 +490,7 @@ def after_parsing(self) -> None: try: self.interface = Vxi11CoreClient(host_address, port, self.open_timeout) except rpc.RPCError: - LOGGER.exception( + common.logger.exception( f"Failed to open VX11 connection to {host_address} on port {port}" ) raise OpenError() @@ -522,7 +522,7 @@ def close(self) -> StatusCode: try: self.interface.destroy_link(self.link) except (errors.VisaIOError, socket.error, rpc.RPCError) as e: - LOGGER.error("Error closing VISA link: {}".format(e)) + common.logger.error("Error closing VISA link: {}".format(e)) self.interface.close() self.link = 0 @@ -886,7 +886,7 @@ def after_parsing(self) -> None: self.parsed.host_address, port, timeout=self.timeout ) except OSError as e: - LOGGER.exception( + common.logger.exception( f"Failed to open VICP connection to {self.parsed.host_address} " f"on port {port}" ) From 51afa354fe3f16ea549f14e439bf3923c35075c2 Mon Sep 17 00:00:00 2001 From: MatthieuDartiailh Date: Mon, 28 Oct 2024 17:48:37 +0100 Subject: [PATCH 12/17] =?UTF-8?q?=EF=BB=BFhislip:=20respect=20the=20VISA?= =?UTF-8?q?=20spec=20and=20do=20not=20use=20open=5Ftimeout=20for=20socket?= =?UTF-8?q?=20connection?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyvisa_py/__init__.py | 5 +++++ pyvisa_py/tcpip.py | 10 +++++----- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/pyvisa_py/__init__.py b/pyvisa_py/__init__.py index 50d23168..678561de 100644 --- a/pyvisa_py/__init__.py +++ b/pyvisa_py/__init__.py @@ -7,6 +7,7 @@ """ +import logging from importlib.metadata import PackageNotFoundError, version # We need to import all attributes so that __init_subclass__() is executed once @@ -14,6 +15,10 @@ from . import attributes # noqa: F401 from .highlevel import PyVisaLibrary +# Global pyvisa-py logger used to provide more details on errors (VISA error code +# can make some report rather terse). +LOGGER = logging.getLogger("pyvisa.pyvisa-py") + __version__ = "unknown" try: __version__ = version(__name__) diff --git a/pyvisa_py/tcpip.py b/pyvisa_py/tcpip.py index bde43bae..be4c1446 100644 --- a/pyvisa_py/tcpip.py +++ b/pyvisa_py/tcpip.py @@ -18,7 +18,7 @@ from pyvisa import attributes, constants, errors, rname from pyvisa.constants import BufferOperation, ResourceAttribute, StatusCode -from . import common +from . import LOGGER, common from .protocols import hislip, rpc, vxi11 from .sessions import OpenError, Session, UnknownAttribute, VISARMSession @@ -149,7 +149,7 @@ def after_parsing(self) -> None: sub_address=sub_address, ) except OSError as e: - common.logger.exception( + LOGGER.exception( f"Failed to open HiSLIP connection to {self.parsed.host_address} " f"on port {port} with lan device name {sub_address}" ) @@ -490,7 +490,7 @@ def after_parsing(self) -> None: try: self.interface = Vxi11CoreClient(host_address, port, self.open_timeout) except rpc.RPCError: - common.logger.exception( + LOGGER.exception( f"Failed to open VX11 connection to {host_address} on port {port}" ) raise OpenError() @@ -522,7 +522,7 @@ def close(self) -> StatusCode: try: self.interface.destroy_link(self.link) except (errors.VisaIOError, socket.error, rpc.RPCError) as e: - common.logger.error("Error closing VISA link: {}".format(e)) + LOGGER.error("Error closing VISA link: {}".format(e)) self.interface.close() self.link = 0 @@ -886,7 +886,7 @@ def after_parsing(self) -> None: self.parsed.host_address, port, timeout=self.timeout ) except OSError as e: - common.logger.exception( + LOGGER.exception( f"Failed to open VICP connection to {self.parsed.host_address} " f"on port {port}" ) From 16aac061199451ba04f1364c61aefcc368e71612 Mon Sep 17 00:00:00 2001 From: MatthieuDartiailh Date: Mon, 28 Oct 2024 17:48:37 +0100 Subject: [PATCH 13/17] hislip: respect the VISA spec and do not use open_timeout for socket connection --- pyvisa_py/protocols/hislip.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/pyvisa_py/protocols/hislip.py b/pyvisa_py/protocols/hislip.py index 107eeae1..14b6d53b 100644 --- a/pyvisa_py/protocols/hislip.py +++ b/pyvisa_py/protocols/hislip.py @@ -162,6 +162,8 @@ def receive_exact_into(sock: socket.socket, recv_buffer: bytes) -> None: while bytes_recvd < recv_len: request_size = recv_len - bytes_recvd data_len = sock.recv_into(view, request_size) + if data_len == 0: + raise RuntimeError("Connection was dropped by server.") bytes_recvd += data_len view = view[data_len:] @@ -382,20 +384,26 @@ def __init__( # open the synchronous socket and send an initialize packet self._sync = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - self._sync.settimeout(open_timeout) + # The VISA spec does not allow to tune the socket timeout when opening + # a connection. ``open_timeout`` only applies to attempt to acquire a + # lock. + self._sync.settimeout(5.0) self._sync.connect((ip_addr, port)) - self._sync.settimeout(timeout) self._sync.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) init = self.initialize(sub_address=sub_address.encode("ascii")) if init.overlap != 0: print("**** prefer overlap = %d" % init.overlap) + # We set the user timeout once we managed to initialize the connection. + self._sync.settimeout(timeout) # open the asynchronous socket and send an initialize packet self._async = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self._async.connect((ip_addr, port)) - self._async.settimeout(timeout) + self._async.settimeout(5.0) self._async.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) self._async_init = self.async_initialize(session_id=init.session_id) + # We set the user timeout once we managed to initialize the connection. + self._async.settimeout(timeout) # initialize variables self.max_msg_size = DEFAULT_MAX_MSG_SIZE From c5afc7fa7f2a514aa0d8d7b63fa7fd8e4b1e58ad Mon Sep 17 00:00:00 2001 From: MatthieuDartiailh Date: Mon, 28 Oct 2024 17:49:00 +0100 Subject: [PATCH 14/17] =?UTF-8?q?=EF=BB=BFtcpip:=20allow=20more=20exceptio?= =?UTF-8?q?n=20to=20cause=20a=20failure=20to=20open=20a=20connection?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyvisa_py/tcpip.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyvisa_py/tcpip.py b/pyvisa_py/tcpip.py index be4c1446..95b9111a 100644 --- a/pyvisa_py/tcpip.py +++ b/pyvisa_py/tcpip.py @@ -148,8 +148,8 @@ def after_parsing(self) -> None: port=port, sub_address=sub_address, ) - except OSError as e: - LOGGER.exception( + except Exception as e: + common.logger.exception( f"Failed to open HiSLIP connection to {self.parsed.host_address} " f"on port {port} with lan device name {sub_address}" ) From 0afe36cf8cb8a8b9203296cb25edee05d33180a4 Mon Sep 17 00:00:00 2001 From: MatthieuDartiailh Date: Mon, 28 Oct 2024 17:49:33 +0100 Subject: [PATCH 15/17] highlevel: handle return code when connection opening fails --- pyvisa_py/highlevel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyvisa_py/highlevel.py b/pyvisa_py/highlevel.py index baf29ed9..f94598ba 100644 --- a/pyvisa_py/highlevel.py +++ b/pyvisa_py/highlevel.py @@ -168,7 +168,7 @@ def open( try: sess = cls(session, resource_name, parsed, open_timeout) except sessions.OpenError as e: - return VISASession(0), e.error_code + return VISASession(0), self.handle_return_value(None, e.error_code) return self._register(sess), StatusCode.success From a76d3fb9a31d6a81b40d62d68993eabe04bd58ab Mon Sep 17 00:00:00 2001 From: MatthieuDartiailh Date: Tue, 17 Dec 2024 13:28:10 +0100 Subject: [PATCH 16/17] consistently use the logger adapter signaling the message originate from the py backend --- pyvisa_py/__init__.py | 4 ---- pyvisa_py/common.py | 2 +- pyvisa_py/gpib.py | 23 ++++++++++++----------- pyvisa_py/highlevel.py | 18 +++++++++--------- pyvisa_py/protocols/rpc.py | 28 ++++++++++++++-------------- pyvisa_py/serial.py | 7 ++++--- pyvisa_py/sessions.py | 14 +++++++------- pyvisa_py/tcpip.py | 6 +++--- pyvisa_py/usb.py | 6 +++--- 9 files changed, 53 insertions(+), 55 deletions(-) diff --git a/pyvisa_py/__init__.py b/pyvisa_py/__init__.py index 678561de..e40915c8 100644 --- a/pyvisa_py/__init__.py +++ b/pyvisa_py/__init__.py @@ -15,10 +15,6 @@ from . import attributes # noqa: F401 from .highlevel import PyVisaLibrary -# Global pyvisa-py logger used to provide more details on errors (VISA error code -# can make some report rather terse). -LOGGER = logging.getLogger("pyvisa.pyvisa-py") - __version__ = "unknown" try: __version__ = version(__name__) diff --git a/pyvisa_py/common.py b/pyvisa_py/common.py index 05e253b0..7d0d0a2a 100644 --- a/pyvisa_py/common.py +++ b/pyvisa_py/common.py @@ -11,7 +11,7 @@ from pyvisa import logger -logger = logging.LoggerAdapter(logger, {"backend": "py"}) # type: ignore +LOGGER = logging.LoggerAdapter(logger, {"backend": "py"}) # type: ignore class NamedObject(object): diff --git a/pyvisa_py/gpib.py b/pyvisa_py/gpib.py index 97593922..71db3812 100644 --- a/pyvisa_py/gpib.py +++ b/pyvisa_py/gpib.py @@ -11,10 +11,11 @@ from bisect import bisect from typing import Any, Iterator, List, Tuple, Union -from pyvisa import attributes, constants, logger +from pyvisa import attributes, constants from pyvisa.constants import ResourceAttribute, StatusCode from pyvisa.rname import GPIBInstr, GPIBIntfc +from .common import LOGGER from .sessions import Session, UnknownAttribute try: @@ -83,7 +84,7 @@ def _find_boards() -> Iterator[Tuple[int, int]]: try: yield board, gpib.ask(board, 1) except gpib.GpibError as e: - logger.debug("GPIB board %i error in _find_boards(): %s", board, repr(e)) + LOGGER.debug("GPIB board %i error in _find_boards(): %s", board, repr(e)) def _find_listeners() -> Iterator[Tuple[int, int, int]]: @@ -99,7 +100,7 @@ def _find_listeners() -> Iterator[Tuple[int, int, int]]: if gpib.listener(board, i, j): yield board, i, j except gpib.GpibError as e: - logger.debug( + LOGGER.debug( "GPIB board %i paddr %i saddr %i error in _find_listeners(): %s", board, i, @@ -204,7 +205,7 @@ def convert_gpib_error( # feels brittle. As a consequence we only try to be smart when using # gpib-ctypes. However in both cases we log the exception at debug level. else: - logger.debug("Failed to %s.", operation, exc_info=error) + LOGGER.debug("Failed to %s.", operation, exc_info=error) if not GPIB_CTYPES: return StatusCode.error_system_error if error.code == 1: @@ -410,7 +411,7 @@ def write(self, data: bytes) -> Tuple[int, StatusCode]: Return value of the library call. """ - logger.debug("GPIB.write %r" % data) + LOGGER.debug("GPIB.write %r" % data) # INTFC don't have an interface so use the controller ifc = self.interface or self.controller @@ -663,7 +664,7 @@ def clear(self) -> StatusCode: Return value of the library call. """ - logger.debug("GPIB.device clear") + LOGGER.debug("GPIB.device clear") try: self.interface.clear() return StatusCode.success @@ -685,7 +686,7 @@ def assert_trigger(self, protocol: constants.TriggerProtocol) -> StatusCode: Return value of the library call. """ - logger.debug("GPIB.device assert hardware trigger") + LOGGER.debug("GPIB.device assert hardware trigger") try: if protocol == constants.VI_TRIG_PROT_DEFAULT: @@ -838,7 +839,7 @@ def gpib_send_ifc(self) -> StatusCode: Corresponds to viGpibSendIFC function of the VISA library. """ - logger.debug("GPIB.interface clear") + LOGGER.debug("GPIB.interface clear") try: self.controller.interface_clear() return StatusCode.success @@ -862,7 +863,7 @@ def gpib_control_atn(self, mode: constants.ATNLineOperation) -> StatusCode: Return value of the library call. """ - logger.debug("GPIB.control atn") + LOGGER.debug("GPIB.control atn") if mode == constants.VI_GPIB_ATN_ASSERT: status = gpib_lib.ibcac(self.controller.id, 0) elif mode == constants.VI_GPIB_ATN_DEASSERT: @@ -899,11 +900,11 @@ def gpib_pass_control( """ # ibpct need to get the device id matching the primary and secondary address - logger.debug("GPIB.pass control") + LOGGER.debug("GPIB.pass control") try: did = gpib.dev(self.parsed.board, primary_address, secondary_address) except gpib.GpibError: - logger.exception( + LOGGER.exception( "Failed to get id for %s, %d", primary_address, secondary_address ) return StatusCode.error_resource_not_found diff --git a/pyvisa_py/highlevel.py b/pyvisa_py/highlevel.py index f94598ba..e60f2787 100644 --- a/pyvisa_py/highlevel.py +++ b/pyvisa_py/highlevel.py @@ -17,7 +17,7 @@ from pyvisa.util import DebugInfo, LibraryPath from . import sessions -from .common import logger +from .common import LOGGER class PyVisaLibrary(highlevel.VisaLibraryBase): @@ -45,30 +45,30 @@ class PyVisaLibrary(highlevel.VisaLibraryBase): try: from .serial import SerialSession - logger.debug("SerialSession was correctly imported.") + LOGGER.debug("SerialSession was correctly imported.") except Exception as e: - logger.debug("SerialSession was not imported %s." % e) + LOGGER.debug("SerialSession was not imported %s." % e) try: from .usb import USBRawSession, USBSession - logger.debug("USBSession and USBRawSession were correctly imported.") + LOGGER.debug("USBSession and USBRawSession were correctly imported.") except Exception as e: - logger.debug("USBSession and USBRawSession were not imported %s." % e) + LOGGER.debug("USBSession and USBRawSession were not imported %s." % e) try: from .tcpip import TCPIPInstrSession, TCPIPSocketSession - logger.debug("TCPIPSession was correctly imported.") + LOGGER.debug("TCPIPSession was correctly imported.") except Exception as e: - logger.debug("TCPIPSession was not imported %s." % e) + LOGGER.debug("TCPIPSession was not imported %s." % e) try: from .gpib import GPIBSession - logger.debug("GPIBSession was correctly imported.") + LOGGER.debug("GPIBSession was correctly imported.") except Exception as e: - logger.debug("GPIBSession was not imported %s." % e) + LOGGER.debug("GPIBSession was not imported %s." % e) @staticmethod def get_library_paths() -> Iterable[LibraryPath]: diff --git a/pyvisa_py/protocols/rpc.py b/pyvisa_py/protocols/rpc.py index 6553cab9..96710bf5 100644 --- a/pyvisa_py/protocols/rpc.py +++ b/pyvisa_py/protocols/rpc.py @@ -25,7 +25,7 @@ import sys import time -from ..common import logger +from ..common import LOGGER from . import xdrlib #: Version of the protocol @@ -225,7 +225,7 @@ def __init__(self, host, prog, vers, port): def make_call(self, proc, args, pack_func, unpack_func): # Don't normally override this (but see Broadcast) - logger.debug("Make call %r, %r, %r, %r", proc, args, pack_func, unpack_func) + LOGGER.debug("Make call %r, %r, %r, %r", proc, args, pack_func, unpack_func) if pack_func is None and args is not None: raise TypeError("non-null args with null pack_func") @@ -296,7 +296,7 @@ def sendfrag(sock, last, frag): def _sendrecord(sock, record, fragsize=None, timeout=None): - logger.debug("Sending record through %s: %r", sock, record) + LOGGER.debug("Sending record through %s: %r", sock, record) if timeout is not None: r, w, x = select.select([], [sock], [], timeout) if sock not in w: @@ -330,7 +330,7 @@ def _recvrecord(sock, timeout, read_fun=None, min_packages=0): packages_received = 0 if min_packages != 0: - logger.debug("Start receiving at least %i packages" % min_packages) + LOGGER.debug("Start receiving at least %i packages" % min_packages) # minimum is in interval 1 - 100ms based on timeout or for infinite it is # 1 sec @@ -355,11 +355,11 @@ def _recvrecord(sock, timeout, read_fun=None, min_packages=0): if sock in r: read_data = read_fun(exp_length) buffer.extend(read_data) - logger.debug("received %r" % read_data) + LOGGER.debug("received %r" % read_data) # Timeout was reached if not read_data: # no response or empty response if timeout is not None and time.time() >= finish_time: - logger.debug( + LOGGER.debug( ( "Time out encountered in %s." "Already receieved %d bytes. Last fragment is %d " @@ -376,7 +376,7 @@ def _recvrecord(sock, timeout, read_fun=None, min_packages=0): ) raise socket.timeout(msg) elif min_packages != 0 and packages_received >= min_packages: - logger.debug( + LOGGER.debug( "Stop receiving after %i of %i requested packages. Received record through %s: %r", packages_received, min_packages, @@ -404,7 +404,7 @@ def _recvrecord(sock, timeout, read_fun=None, min_packages=0): record.extend(buffer[:exp_length]) buffer = buffer[exp_length:] if last: - logger.debug("Received record through %s: %r", sock, record) + LOGGER.debug("Received record through %s: %r", sock, record) return bytes(record) else: wait_header = True @@ -480,7 +480,7 @@ def make_call(self, proc, args, pack_func, unpack_func): return super(RawTCPClient, self).make_call(proc, args, pack_func, unpack_func) def connect(self, timeout=5.0): - logger.debug( + LOGGER.debug( "RawTCPClient: connecting to socket at (%s, %s)", self.host, self.port ) self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) @@ -488,7 +488,7 @@ def connect(self, timeout=5.0): raise RPCError("can't connect to server") def close(self): - logger.debug("RawTCPClient: closing socket") + LOGGER.debug("RawTCPClient: closing socket") self.sock.close() def do_call(self): @@ -498,7 +498,7 @@ def do_call(self): try: min_packages = int(self.packer.proc == 3) - logger.debug("RawTCPClient: procedure type %i" % self.packer.proc) + LOGGER.debug("RawTCPClient: procedure type %i" % self.packer.proc) # if the command is get_port, we only expect one package. # This is a workaround for misbehaving instruments. except AttributeError: @@ -531,14 +531,14 @@ def __init__(self, host, prog, vers, port): self.connect() def connect(self): - logger.debug( + LOGGER.debug( "RawTCPClient: connecting to socket at (%s, %s)", self.host, self.port ) self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) self.sock.connect((self.host, self.port)) def close(self): - logger.debug("RawTCPClient: closing socket") + LOGGER.debug("RawTCPClient: closing socket") self.sock.close() def do_call(self): @@ -1005,7 +1005,7 @@ def session(self, connection): except EOFError: break except socket.error: - logger.exception("socket error: %r", sys.exc_info()[0]) + LOGGER.exception("socket error: %r", sys.exc_info()[0]) break reply = self.handle(call) if reply is not None: diff --git a/pyvisa_py/serial.py b/pyvisa_py/serial.py index 4710c6c6..d330004a 100644 --- a/pyvisa_py/serial.py +++ b/pyvisa_py/serial.py @@ -10,7 +10,7 @@ import sys from typing import Any, List, Tuple -from pyvisa import attributes, constants, logger, rname +from pyvisa import attributes, constants, rname from pyvisa.constants import ( BufferOperation, ResourceAttribute, @@ -19,6 +19,7 @@ ) from . import common +from .common import LOGGER from .sessions import Session, UnknownAttribute try: @@ -166,7 +167,7 @@ def write(self, data: bytes) -> Tuple[int, StatusCode]: Return value of the library call. """ - logger.debug("Serial.write %r" % data) + LOGGER.debug("Serial.write %r" % data) send_end, _ = self.get_attribute(ResourceAttribute.send_end_enabled) end_out, _ = self.get_attribute(ResourceAttribute.asrl_end_out) data_bits, _ = self.get_attribute(constants.ResourceAttribute.asrl_data_bits) @@ -188,7 +189,7 @@ def write(self, data: bytes) -> Tuple[int, StatusCode]: count = self.interface.write(data) if end_out == SerialTermination.termination_break: - logger.debug("Serial.sendBreak") + LOGGER.debug("Serial.sendBreak") self.interface.sendBreak() return count, StatusCode.success diff --git a/pyvisa_py/sessions.py b/pyvisa_py/sessions.py index edfa792c..1b776086 100644 --- a/pyvisa_py/sessions.py +++ b/pyvisa_py/sessions.py @@ -22,11 +22,11 @@ TypeVar, ) -from pyvisa import attributes, constants, logger, rname +from pyvisa import attributes, constants, rname from pyvisa.constants import ResourceAttribute, StatusCode from pyvisa.typing import VISARMSession -from .common import int_to_byte +from .common import LOGGER, int_to_byte #: Type var used when typing register. T = TypeVar("T", bound=Type["Session"]) @@ -237,7 +237,7 @@ def register( def _internal(python_class): if (interface_type, resource_class) in cls._session_classes: - logger.warning( + LOGGER.warning( "%s is already registered in the " "ResourceManager. Overwriting with %s", (interface_type, resource_class), @@ -280,7 +280,7 @@ class _internal(UnavailableSession): session_issue = msg if (interface_type, resource_class) in cls._session_classes: - logger.warning( + LOGGER.warning( "%s is already registered in the ResourceManager. " "Overwriting with unavailable %s", (interface_type, resource_class), @@ -674,7 +674,7 @@ def get_attribute(self, attribute: ResourceAttribute) -> Tuple[Any, StatusCode]: try: return self._get_attribute(attribute) except UnknownAttribute as e: - logger.exception(str(e)) + LOGGER.exception(str(e)) return 0, StatusCode.error_nonsupported_attribute def set_attribute( @@ -737,10 +737,10 @@ def set_attribute( return StatusCode.error_nonsupported_attribute_state except NotImplementedError: e = UnknownAttribute(attribute) - logger.exception(str(e)) + LOGGER.exception(str(e)) return StatusCode.error_nonsupported_attribute except UnknownAttribute as e: - logger.exception(str(e)) + LOGGER.exception(str(e)) return StatusCode.error_nonsupported_attribute def _read( diff --git a/pyvisa_py/tcpip.py b/pyvisa_py/tcpip.py index 95b9111a..0b8359f1 100644 --- a/pyvisa_py/tcpip.py +++ b/pyvisa_py/tcpip.py @@ -18,7 +18,7 @@ from pyvisa import attributes, constants, errors, rname from pyvisa.constants import BufferOperation, ResourceAttribute, StatusCode -from . import LOGGER, common +from .common import LOGGER, int_to_byte from .protocols import hislip, rpc, vxi11 from .sessions import OpenError, Session, UnknownAttribute, VISARMSession @@ -149,7 +149,7 @@ def after_parsing(self) -> None: sub_address=sub_address, ) except Exception as e: - common.logger.exception( + LOGGER.exception( f"Failed to open HiSLIP connection to {self.parsed.host_address} " f"on port {port} with lan device name {sub_address}" ) @@ -1191,7 +1191,7 @@ def read(self, count: int) -> Tuple[bytes, StatusCode]: chunk_length = self.max_recv_size term_char, _ = self.get_attribute(ResourceAttribute.termchar) - term_byte = common.int_to_byte(term_char) if term_char is not None else b"" + term_byte = int_to_byte(term_char) if term_char is not None else b"" term_char_en, _ = self.get_attribute(ResourceAttribute.termchar_enabled) suppress_end_en, _ = self.get_attribute(ResourceAttribute.suppress_end_enabled) diff --git a/pyvisa_py/usb.py b/pyvisa_py/usb.py index 74116838..17d56a48 100644 --- a/pyvisa_py/usb.py +++ b/pyvisa_py/usb.py @@ -14,7 +14,7 @@ from pyvisa.constants import ResourceAttribute, StatusCode from pyvisa.rname import USBInstr, USBRaw -from .common import logger +from .common import LOGGER from .sessions import Session, UnknownAttribute try: @@ -277,7 +277,7 @@ def list_resources() -> List[str]: "Found a device whose serial number cannot be read." " The partial VISA resource name is: " + fmt ) - logger.warning( + LOGGER.warning( msg, { "board": 0, @@ -334,7 +334,7 @@ def list_resources() -> List[str]: "Found a device whose serial number cannot be read." " The partial VISA resource name is: " + fmt ) - logger.warning( + LOGGER.warning( msg, { "board": 0, From 5842b031fefdc9175ea3b550eca409d7fb257266 Mon Sep 17 00:00:00 2001 From: MatthieuDartiailh Date: Tue, 17 Dec 2024 13:48:13 +0100 Subject: [PATCH 17/17] fix linter complaint --- pyvisa_py/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pyvisa_py/__init__.py b/pyvisa_py/__init__.py index e40915c8..50d23168 100644 --- a/pyvisa_py/__init__.py +++ b/pyvisa_py/__init__.py @@ -7,7 +7,6 @@ """ -import logging from importlib.metadata import PackageNotFoundError, version # We need to import all attributes so that __init_subclass__() is executed once