1# -*- coding: utf-8 -*- 2"""General utility functions. 3 4This file is part of PyVISA. 5 6:copyright: 2014-2020 by PyVISA Authors, see AUTHORS for more details. 7:license: MIT, see LICENSE for more details. 8 9""" 10import functools 11import inspect 12import io 13import math 14import os 15import platform 16import struct 17import subprocess 18import sys 19import warnings 20from collections import OrderedDict 21from typing import ( 22 Any, 23 Callable, 24 Dict, 25 Iterable, 26 List, 27 Optional, 28 Sequence, 29 Tuple, 30 Type, 31 Union, 32 overload, 33) 34 35from typing_extensions import Literal 36 37from . import constants, logger 38 39try: 40 import numpy as np # type: ignore 41except ImportError: 42 np = None 43 44 45#: Length of the header found before a binary block (ieee or hp) that will 46#: trigger a warning to alert the user of a possibly incorrect answer from the 47#: instrument. In general binary block are not prefixed by a header and finding 48#: a long one may mean that we picked up a # in the bulk of the message. 49DEFAULT_LENGTH_BEFORE_BLOCK = 25 50 51 52def _use_numpy_routines(container: Union[type, Callable]) -> bool: 53 """Should optimized numpy routines be used to extract data.""" 54 if np is None or container in (tuple, list): 55 return False 56 57 if container is np.array or ( 58 inspect.isclass(container) and issubclass(container, np.ndarray) # type: ignore 59 ): 60 return True 61 62 return False 63 64 65def read_user_library_path() -> Optional[str]: 66 """Return the library path stored in one of the following configuration files: 67 68 <sys prefix>/share/pyvisa/.pyvisarc 69 ~/.pyvisarc 70 71 <sys prefix> is the site-specific directory prefix where the platform 72 independent Python files are installed. 73 74 Example configuration file: 75 76 [Paths] 77 visa library=/my/path/visa.so 78 dll_extra_paths=/my/otherpath/;/my/otherpath2 79 80 Return `None` if configuration files or keys are not present. 81 82 """ 83 from configparser import ConfigParser, NoOptionError, NoSectionError 84 85 config_parser = ConfigParser() 86 files = config_parser.read( 87 [ 88 os.path.join(sys.prefix, "share", "pyvisa", ".pyvisarc"), 89 os.path.join(os.path.expanduser("~"), ".pyvisarc"), 90 ] 91 ) 92 93 if not files: 94 logger.debug("No user defined library files") 95 return None 96 97 logger.debug("User defined library files: %s" % files) 98 try: 99 return config_parser.get("Paths", "visa library") 100 except (NoOptionError, NoSectionError): 101 logger.debug("NoOptionError or NoSectionError while reading config file") 102 return None 103 104 105def add_user_dll_extra_paths() -> Optional[List[str]]: 106 """Add paths to search for .dll dependencies on Windows. 107 108 The configuration files are expected to be stored in one of the following location: 109 110 <sys prefix>/share/pyvisa/.pyvisarc 111 ~/.pyvisarc 112 113 <sys prefix> is the site-specific directory prefix where the platform 114 independent Python files are installed. 115 116 Example configuration file: 117 118 [Paths] 119 visa library=/my/path/visa.so 120 dll_extra_paths=/my/otherpath/;/my/otherpath2 121 122 """ 123 from configparser import ConfigParser, NoOptionError, NoSectionError 124 125 # os.add_dll_library_path has been added in Python 3.8 126 if sys.version_info >= (3, 8) and sys.platform == "win32": 127 config_parser = ConfigParser() 128 files = config_parser.read( 129 [ 130 os.path.join(sys.prefix, "share", "pyvisa", ".pyvisarc"), 131 os.path.join(os.path.expanduser("~"), ".pyvisarc"), 132 ] 133 ) 134 135 if not files: 136 logger.debug("No user defined configuration") 137 return None 138 139 logger.debug("User defined configuration files: %s" % files) 140 141 try: 142 dll_extra_paths = config_parser.get("Paths", "dll_extra_paths").split(";") 143 for path in dll_extra_paths: 144 os.add_dll_directory(path) 145 return dll_extra_paths 146 except (NoOptionError, NoSectionError): 147 logger.debug( 148 "NoOptionError or NoSectionError while reading config file for" 149 " dll_extra_paths." 150 ) 151 return None 152 else: 153 logger.debug( 154 "Not loading dll_extra_paths because we are not on Windows " 155 "or Python < 3.8" 156 ) 157 return None 158 159 160class LibraryPath(str): 161 """Object encapsulating information about a VISA dynamic library.""" 162 163 #: Path with which the object was created 164 path: str 165 166 #: Detection method employed to locate the library 167 found_by: str 168 169 #: Architectural information (32, ) or (64, ) or (32, 64) 170 _arch: Optional[Tuple[int, ...]] = None 171 172 def __new__( 173 cls: Type["LibraryPath"], path: str, found_by: str = "auto" 174 ) -> "LibraryPath": 175 obj = super(LibraryPath, cls).__new__(cls, path) # type: ignore 176 obj.path = path 177 obj.found_by = found_by 178 179 return obj 180 181 @property 182 def arch(self) -> Tuple[int, ...]: 183 """Architecture of the library.""" 184 if self._arch is None: 185 try: 186 self._arch = get_arch(self.path) 187 except Exception: 188 self._arch = tuple() 189 190 return self._arch 191 192 @property 193 def is_32bit(self) -> Union[bool, Literal["n/a"]]: 194 """Is the library 32 bits.""" 195 if not self.arch: 196 return "n/a" 197 return 32 in self.arch 198 199 @property 200 def is_64bit(self) -> Union[bool, Literal["n/a"]]: 201 """Is the library 64 bits.""" 202 if not self.arch: 203 return "n/a" 204 return 64 in self.arch 205 206 @property 207 def bitness(self) -> str: 208 """Bitness of the library.""" 209 if not self.arch: 210 return "n/a" 211 return ", ".join(str(a) for a in self.arch) 212 213 214def cleanup_timeout(timeout: Optional[Union[int, float]]) -> int: 215 """Turn a timeout expressed as a float into in interger or the proper constant.""" 216 if timeout is None or math.isinf(timeout): 217 timeout = constants.VI_TMO_INFINITE 218 219 elif timeout < 1: 220 timeout = constants.VI_TMO_IMMEDIATE 221 222 elif not (1 <= timeout <= 4294967294): 223 raise ValueError("timeout value is invalid") 224 225 else: 226 timeout = int(timeout) 227 228 return timeout 229 230 231def warn_for_invalid_kwargs(keyw, allowed_keys): # pragma: no cover 232 warnings.warn("warn_for_invalid_kwargs will be removed in 1.12", FutureWarning) 233 for key in keyw.keys(): 234 if key not in allowed_keys: 235 warnings.warn('Keyword argument "%s" unknown' % key, stacklevel=3) 236 237 238def filter_kwargs(keyw, selected_keys): # pragma: no cover 239 warnings.warn("warn_for_invalid_kwargs will be removed in 1.12", FutureWarning) 240 result = {} 241 for key, value in keyw.items(): 242 if key in selected_keys: 243 result[key] = value 244 return result 245 246 247def split_kwargs(keyw, self_keys, parent_keys, warn=True): # pragma: no cover 248 warnings.warn("warn_for_invalid_kwargs will be removed in 1.12", FutureWarning) 249 self_kwargs = dict() 250 parent_kwargs = dict() 251 self_keys = set(self_keys) 252 parent_keys = set(parent_keys) 253 all_keys = self_keys | parent_keys 254 for key, value in keyw.items(): 255 if warn and key not in all_keys: 256 warnings.warn('Keyword argument "%s" unknown' % key, stacklevel=3) 257 if key in self_keys: 258 self_kwargs[key] = value 259 if key in parent_keys: 260 parent_kwargs[key] = value 261 262 return self_kwargs, parent_kwargs 263 264 265_converters: Dict[str, Callable[[str], Any]] = { 266 "s": str, 267 "b": functools.partial(int, base=2), 268 "c": ord, 269 "d": int, 270 "o": functools.partial(int, base=8), 271 "x": functools.partial(int, base=16), 272 "X": functools.partial(int, base=16), 273 "h": functools.partial(int, base=16), 274 "H": functools.partial(int, base=16), 275 "e": float, 276 "E": float, 277 "f": float, 278 "F": float, 279 "g": float, 280 "G": float, 281} 282 283_np_converters = { 284 "d": "i", 285 "e": "d", 286 "E": "d", 287 "f": "d", 288 "F": "d", 289 "g": "d", 290 "G": "d", 291} 292 293 294ASCII_CONVERTER = Union[ 295 Literal["s", "b", "c", "d", "o", "x", "X", "e", "E", "f", "F", "g", "G"], 296 Callable[[str], Any], 297] 298 299 300def from_ascii_block( 301 ascii_data: str, 302 converter: ASCII_CONVERTER = "f", 303 separator: Union[str, Callable[[str], Iterable[str]]] = ",", 304 container: Callable[ 305 [Iterable[Union[int, float]]], Sequence[Union[int, float]] 306 ] = list, 307) -> Sequence: 308 """Parse ascii data and return an iterable of numbers. 309 310 Parameters 311 ---------- 312 ascii_data : str 313 Data to be parsed. 314 converter : ASCII_CONVERTER, optional 315 Str format of function to convert each value. Default to "f". 316 separator : Union[str, Callable[[str], Iterable[str]]] 317 str or callable used to split the data into individual elements. 318 If a str is given, data.split(separator) is used. Default to ",". 319 container : Union[Type, Callable[[Iterable], Sequence]], optional 320 Container type to use for the output data. Possible values are: list, 321 tuple, np.ndarray, etc, Default to list. 322 323 Returns 324 ------- 325 Sequence 326 Parsed data. 327 328 """ 329 if ( 330 _use_numpy_routines(container) 331 and isinstance(converter, str) 332 and isinstance(separator, str) 333 and converter in _np_converters 334 ): 335 return np.fromstring(ascii_data, _np_converters[converter], sep=separator) 336 337 if isinstance(converter, str): 338 try: 339 converter = _converters[converter] 340 except KeyError: 341 raise ValueError( 342 "Invalid code for converter: %s not in %s" 343 % (converter, str(tuple(_converters.keys()))) 344 ) 345 346 data: Iterable[str] 347 if isinstance(separator, str): 348 data = ascii_data.split(separator) 349 else: 350 data = separator(ascii_data) 351 352 return container([converter(raw_value) for raw_value in data]) 353 354 355def to_ascii_block( 356 iterable: Iterable[Any], 357 converter: Union[str, Callable[[Any], str]] = "f", 358 separator: Union[str, Callable[[Iterable[str]], str]] = ",", 359) -> str: 360 """Turn an iterable of numbers in an ascii block of data. 361 362 Parameters 363 ---------- 364 iterable : Iterable[Any] 365 Data to be formatted. 366 converter : Union[str, Callable[[Any], str]] 367 String formatting code or function used to convert each value. 368 Default to "f". 369 separator : Union[str, Callable[[Iterable[str]], str]] 370 str or callable that join individual elements into a str. 371 If a str is given, separator.join(data) is used. 372 373 """ 374 if isinstance(separator, str): 375 separator = separator.join 376 377 if isinstance(converter, str): 378 converter = "%" + converter 379 block = separator(converter % val for val in iterable) 380 else: 381 block = separator(converter(val) for val in iterable) 382 return block 383 384 385#: Valid binary header when reading/writing binary block of data from an instrument 386BINARY_HEADERS = Literal["ieee", "hp", "empty"] 387 388#: Valid datatype for binary block. See Python standard library struct module for more 389#: details. 390BINARY_DATATYPES = Literal[ 391 "s", "b", "B", "h", "H", "i", "I", "l", "L", "q", "Q", "f", "d" 392] 393 394#: Valid output containers for storing the parsed binary data 395BINARY_CONTAINERS = Union[type, Callable] 396 397 398def parse_ieee_block_header( 399 block: Union[bytes, bytearray], 400 length_before_block: Optional[int] = None, 401 raise_on_late_block: bool = False, 402) -> Tuple[int, int]: 403 """Parse the header of a IEEE block. 404 405 Definite Length Arbitrary Block: 406 #<header_length><data_length><data> 407 408 The header_length specifies the size of the data_length field. 409 And the data_length field specifies the size of the data. 410 411 Indefinite Length Arbitrary Block: 412 #0<data> 413 414 In this case the data length returned will be 0. The actual length can be 415 deduced from the block and the offset. 416 417 Parameters 418 ---------- 419 block : Union[bytes, bytearray] 420 IEEE formatted block of data. 421 length_before_block : Optional[int], optional 422 Number of bytes before the actual start of the block. Default to None, 423 which means that number will be inferred. 424 raise_on_late_block : bool, optional 425 Raise an error in the beginning of the block is not found before 426 DEFAULT_LENGTH_BEFORE_BLOCK, if False use a warning. Default to False. 427 428 Returns 429 ------- 430 int 431 Offset at which the actual data starts 432 int 433 Length of the data in bytes. 434 435 """ 436 begin = block.find(b"#") 437 if begin < 0: 438 raise ValueError( 439 "Could not find hash sign (#) indicating the start of the block. " 440 "The block begin by %r" % block[:25] 441 ) 442 443 length_before_block = length_before_block or DEFAULT_LENGTH_BEFORE_BLOCK 444 if begin > length_before_block: 445 msg = ( 446 "The beginning of the block has been found at %d which " 447 "is an unexpectedly large value. The actual block may " 448 "have been missing a beginning marker but the block " 449 "contained one:\n%s" 450 ) % (begin, repr(block)) 451 if raise_on_late_block: 452 raise RuntimeError(msg) 453 else: 454 warnings.warn(msg, UserWarning) 455 456 try: 457 # int(block[begin+1]) != int(block[begin+1:begin+2]) in Python 3 458 header_length = int(block[begin + 1 : begin + 2]) 459 except ValueError: 460 header_length = 0 461 offset = begin + 2 + header_length 462 463 if header_length > 0: 464 # #3100DATA 465 # 012345 466 data_length = int(block[begin + 2 : offset]) 467 else: 468 # #0DATA 469 # 012 470 data_length = 0 471 472 return offset, data_length 473 474 475def parse_hp_block_header( 476 block: Union[bytes, bytearray], 477 is_big_endian: bool, 478 length_before_block: int = None, 479 raise_on_late_block: bool = False, 480) -> Tuple[int, int]: 481 """Parse the header of a HP block. 482 483 Definite Length Arbitrary Block: 484 #A<data_length><data> 485 486 The header ia always 4 bytes long. 487 The data_length field specifies the size of the data. 488 489 Parameters 490 ---------- 491 block : Union[bytes, bytearray] 492 HP formatted block of data. 493 is_big_endian : bool 494 Is the header in big or little endian order. 495 length_before_block : Optional[int], optional 496 Number of bytes before the actual start of the block. Default to None, 497 which means that number will be inferred. 498 raise_on_late_block : bool, optional 499 Raise an error in the beginning of the block is not found before 500 DEFAULT_LENGTH_BEFORE_BLOCK, if False use a warning. Default to False. 501 502 Returns 503 ------- 504 int 505 Offset at which the actual data starts 506 int 507 Length of the data in bytes. 508 509 """ 510 begin = block.find(b"#A") 511 if begin < 0: 512 raise ValueError( 513 "Could not find the standard block header (#A) indicating the start " 514 "of the block. The block begin by %r" % block[:25] 515 ) 516 517 length_before_block = length_before_block or DEFAULT_LENGTH_BEFORE_BLOCK 518 if begin > length_before_block: 519 msg = ( 520 "The beginning of the block has been found at %d which " 521 "is an unexpectedly large value. The actual block may " 522 "have been missing a beginning marker but the block " 523 "contained one:\n%s" 524 ) % (begin, repr(block)) 525 if raise_on_late_block: 526 raise RuntimeError(msg) 527 else: 528 warnings.warn(msg, UserWarning) 529 530 offset = begin + 4 531 532 data_length = int.from_bytes( 533 block[begin + 2 : offset], byteorder="big" if is_big_endian else "little" 534 ) 535 536 return offset, data_length 537 538 539def from_ieee_block( 540 block: Union[bytes, bytearray], 541 datatype: BINARY_DATATYPES = "f", 542 is_big_endian: bool = False, 543 container: Callable[ 544 [Iterable[Union[int, float]]], Sequence[Union[int, float]] 545 ] = list, 546) -> Sequence[Union[int, float]]: 547 """Convert a block in the IEEE format into an iterable of numbers. 548 549 Definite Length Arbitrary Block: 550 #<header_length><data_length><data> 551 552 The header_length specifies the size of the data_length field. 553 And the data_length field specifies the size of the data. 554 555 Indefinite Length Arbitrary Block: 556 #0<data> 557 558 Parameters 559 ---------- 560 block : Union[bytes, bytearray] 561 IEEE formatted block of data. 562 datatype : BINARY_DATATYPES, optional 563 Format string for a single element. See struct module. 'f' by default. 564 is_big_endian : bool, optional 565 Are the data in big or little endian order. 566 container : Union[Type, Callable[[Iterable], Sequence]], optional 567 Container type to use for the output data. Possible values are: list, 568 tuple, np.ndarray, etc, Default to list. 569 570 Returns 571 ------- 572 Sequence[Union[int, float]] 573 Parsed data. 574 575 """ 576 offset, data_length = parse_ieee_block_header(block) 577 578 # If the data length is not reported takes all the data and do not make 579 # any assumption about the termination character 580 if data_length == 0: 581 data_length = len(block) - offset 582 583 if len(block) < offset + data_length: 584 raise ValueError( 585 "Binary data is incomplete. The header states %d data" 586 " bytes, but %d where received." % (data_length, len(block) - offset) 587 ) 588 589 return from_binary_block( 590 block, offset, data_length, datatype, is_big_endian, container 591 ) 592 593 594def from_hp_block( 595 block: Union[bytes, bytearray], 596 datatype: BINARY_DATATYPES = "f", 597 is_big_endian: bool = False, 598 container: Callable[ 599 [Iterable[Union[int, float]]], Sequence[Union[int, float]] 600 ] = list, 601) -> Sequence[Union[int, float]]: 602 """Convert a block in the HP format into an iterable of numbers. 603 604 Definite Length Arbitrary Block: 605 #A<data_length><data> 606 607 The header ia always 4 bytes long. 608 The data_length field specifies the size of the data. 609 610 Parameters 611 ---------- 612 block : Union[bytes, bytearray] 613 HP formatted block of data. 614 datatype : BINARY_DATATYPES, optional 615 Format string for a single element. See struct module. 'f' by default. 616 is_big_endian : bool, optional 617 Are the data in big or little endian order. 618 container : Union[Type, Callable[[Iterable], Sequence]], optional 619 Container type to use for the output data. Possible values are: list, 620 tuple, np.ndarray, etc, Default to list. 621 622 Returns 623 ------- 624 Sequence[Union[int, float]] 625 Parsed data. 626 627 """ 628 offset, data_length = parse_hp_block_header(block, is_big_endian) 629 630 # If the data length is not reported takes all the data and do not make 631 # any assumption about the termination character 632 if data_length == 0: 633 data_length = len(block) - offset 634 635 if len(block) < offset + data_length: 636 raise ValueError( 637 "Binary data is incomplete. The header states %d data" 638 " bytes, but %d where received." % (data_length, len(block) - offset) 639 ) 640 641 return from_binary_block( 642 block, offset, data_length, datatype, is_big_endian, container 643 ) 644 645 646def from_binary_block( 647 block: Union[bytes, bytearray], 648 offset: int = 0, 649 data_length: Optional[int] = None, 650 datatype: BINARY_DATATYPES = "f", 651 is_big_endian: bool = False, 652 container: Callable[ 653 [Iterable[Union[int, float]]], Sequence[Union[int, float]] 654 ] = list, 655) -> Sequence[Union[int, float]]: 656 """Convert a binary block into an iterable of numbers. 657 658 659 Parameters 660 ---------- 661 block : Union[bytes, bytearray] 662 HP formatted block of data. 663 offset : int 664 Offset at which the actual data starts 665 data_length : int 666 Length of the data in bytes. 667 datatype : BINARY_DATATYPES, optional 668 Format string for a single element. See struct module. 'f' by default. 669 is_big_endian : bool, optional 670 Are the data in big or little endian order. 671 container : Union[Type, Callable[[Iterable], Sequence]], optional 672 Container type to use for the output data. Possible values are: list, 673 tuple, np.ndarray, etc, Default to list. 674 675 Returns 676 ------- 677 Sequence[Union[int, float]] 678 Parsed data. 679 680 """ 681 if data_length is None: 682 data_length = len(block) - offset 683 684 element_length = struct.calcsize(datatype) 685 array_length = int(data_length / element_length) 686 687 endianess = ">" if is_big_endian else "<" 688 689 if _use_numpy_routines(container): 690 return np.frombuffer(block, endianess + datatype, array_length, offset) 691 692 fullfmt = "%s%d%s" % (endianess, array_length, datatype) 693 694 try: 695 raw_data = struct.unpack_from(fullfmt, block, offset) 696 except struct.error: 697 raise ValueError("Binary data was malformed") 698 699 if datatype in "sp": 700 raw_data = raw_data[0] 701 702 return container(raw_data) 703 704 705def to_binary_block( 706 iterable: Sequence[Union[int, float]], 707 header: Union[str, bytes], 708 datatype: BINARY_DATATYPES, 709 is_big_endian: bool, 710) -> bytes: 711 """Convert an iterable of numbers into a block of data with a given header. 712 713 Parameters 714 ---------- 715 iterable : Sequence[Union[int, float]] 716 Sequence of numbers to pack into a block. 717 header : Union[str, bytes] 718 Header which should prefix the binary block 719 datatype : BINARY_DATATYPES 720 Format string for a single element. See struct module. 721 is_big_endian : bool 722 Are the data in big or little endian order. 723 724 Returns 725 ------- 726 bytes 727 Binary block of data preceded by the specified header 728 729 """ 730 array_length = len(iterable) 731 endianess = ">" if is_big_endian else "<" 732 fullfmt = "%s%d%s" % (endianess, array_length, datatype) 733 734 if isinstance(header, str): 735 header = bytes(header, "ascii") 736 737 if datatype in ("s", "p"): 738 block = struct.pack(fullfmt, iterable) 739 740 else: 741 block = struct.pack(fullfmt, *iterable) 742 743 return header + block 744 745 746def to_ieee_block( 747 iterable: Sequence[Union[int, float]], 748 datatype: BINARY_DATATYPES = "f", 749 is_big_endian: bool = False, 750) -> bytes: 751 """Convert an iterable of numbers into a block of data in the IEEE format. 752 753 Parameters 754 ---------- 755 iterable : Sequence[Union[int, float]] 756 Sequence of numbers to pack into a block. 757 datatype : BINARY_DATATYPES, optional 758 Format string for a single element. See struct module. Default to 'f'. 759 is_big_endian : bool, optional 760 Are the data in big or little endian order. Default to False. 761 762 Returns 763 ------- 764 bytes 765 Binary block of data preceded by the specified header 766 767 """ 768 array_length = len(iterable) 769 element_length = struct.calcsize(datatype) 770 data_length = array_length * element_length 771 772 header = "%d" % data_length 773 header = "#%d%s" % (len(header), header) 774 775 return to_binary_block(iterable, header, datatype, is_big_endian) 776 777 778def to_hp_block( 779 iterable: Sequence[Union[int, float]], 780 datatype: BINARY_DATATYPES = "f", 781 is_big_endian: bool = False, 782) -> bytes: 783 """Convert an iterable of numbers into a block of data in the HP format. 784 785 Parameters 786 ---------- 787 iterable : Sequence[Union[int, float]] 788 Sequence of numbers to pack into a block. 789 datatype : BINARY_DATATYPES, optional 790 Format string for a single element. See struct module. Default to 'f'. 791 is_big_endian : bool, optional 792 Are the data in big or little endian order. Default to False. 793 794 Returns 795 ------- 796 bytes 797 Binary block of data preceded by the specified header 798 799 """ 800 array_length = len(iterable) 801 element_length = struct.calcsize(datatype) 802 data_length = array_length * element_length 803 804 header = b"#A" + ( 805 int.to_bytes(data_length, 2, "big" if is_big_endian else "little") 806 ) 807 808 return to_binary_block(iterable, header, datatype, is_big_endian) 809 810 811def get_system_details(backends: bool = True) -> Dict[str, str]: 812 """Return a dictionary with information about the system.""" 813 buildno, builddate = platform.python_build() 814 if sys.maxunicode == 65535: 815 # UCS2 build (standard) 816 unitype = "UCS2" 817 else: 818 # UCS4 build (most recent Linux distros) 819 unitype = "UCS4" 820 bits, linkage = platform.architecture() 821 822 from . import __version__ 823 824 d = { 825 "platform": platform.platform(), 826 "processor": platform.processor(), 827 "executable": sys.executable, 828 "implementation": getattr(platform, "python_implementation", lambda: "n/a")(), 829 "python": platform.python_version(), 830 "compiler": platform.python_compiler(), 831 "buildno": buildno, 832 "builddate": builddate, 833 "unicode": unitype, 834 "bits": bits, 835 "pyvisa": __version__, 836 "backends": OrderedDict(), 837 } 838 839 if backends: 840 from . import highlevel 841 842 for backend in highlevel.list_backends(): 843 if backend.startswith("pyvisa-"): 844 backend = backend[7:] 845 846 try: 847 cls = highlevel.get_wrapper_class(backend) 848 except Exception as e: 849 d["backends"][backend] = [ 850 "Could not instantiate backend", 851 "-> %s" % str(e), 852 ] 853 continue 854 855 try: 856 d["backends"][backend] = cls.get_debug_info() 857 except Exception as e: 858 d["backends"][backend] = [ 859 "Could not obtain debug info", 860 "-> %s" % str(e), 861 ] 862 863 return d 864 865 866def system_details_to_str(d: Dict[str, str], indent: str = "") -> str: 867 """Convert the system details to a str. 868 869 System details can be obtained by `get_system_details`. 870 871 """ 872 873 details = [ 874 "Machine Details:", 875 " Platform ID: %s" % d.get("platform", "n/a"), 876 " Processor: %s" % d.get("processor", "n/a"), 877 "", 878 "Python:", 879 " Implementation: %s" % d.get("implementation", "n/a"), 880 " Executable: %s" % d.get("executable", "n/a"), 881 " Version: %s" % d.get("python", "n/a"), 882 " Compiler: %s" % d.get("compiler", "n/a"), 883 " Bits: %s" % d.get("bits", "n/a"), 884 " Build: %s (#%s)" 885 % (d.get("builddate", "n/a"), d.get("buildno", "n/a")), 886 " Unicode: %s" % d.get("unicode", "n/a"), 887 "", 888 "PyVISA Version: %s" % d.get("pyvisa", "n/a"), 889 "", 890 ] 891 892 def _to_list(key, value, indent_level=0): 893 sp = " " * indent_level * 3 894 895 if isinstance(value, str): 896 if key: 897 return ["%s%s: %s" % (sp, key, value)] 898 else: 899 return ["%s%s" % (sp, value)] 900 901 elif isinstance(value, dict): 902 if key: 903 al = ["%s%s:" % (sp, key)] 904 else: 905 al = [] 906 907 for k, v in value.items(): 908 al.extend(_to_list(k, v, indent_level + 1)) 909 return al 910 911 elif isinstance(value, (tuple, list)): 912 if key: 913 al = ["%s%s:" % (sp, key)] 914 else: 915 al = [] 916 917 for v in value: 918 al.extend(_to_list(None, v, indent_level + 1)) 919 920 return al 921 922 else: 923 return ["%s" % value] 924 925 details.extend(_to_list("Backends", d["backends"])) 926 927 joiner = "\n" + indent 928 return indent + joiner.join(details) + "\n" 929 930 931@overload 932def get_debug_info(to_screen: Literal[True] = True) -> None: 933 pass 934 935 936@overload 937def get_debug_info(to_screen: Literal[False]) -> str: 938 pass 939 940 941def get_debug_info(to_screen=True): 942 """Get the PyVISA debug information.""" 943 out = system_details_to_str(get_system_details()) 944 if not to_screen: 945 return out 946 print(out) 947 948 949def pip_install(package): # pragma: no cover 950 warnings.warn("warn_for_invalid_kwargs will be removed in 1.12", FutureWarning) 951 try: 952 import pip # type: ignore 953 954 return pip.main(["install", package]) 955 except ImportError: 956 print(system_details_to_str(get_system_details())) 957 raise RuntimeError("Please install pip to continue.") 958 959 960machine_types = { 961 0: "UNKNOWN", 962 0x014C: "I386", 963 0x0162: "R3000", 964 0x0166: "R4000", 965 0x0168: "R10000", 966 0x0169: "WCEMIPSV2", 967 0x0184: "ALPHA", 968 0x01A2: "SH3", 969 0x01A3: "SH3DSP", 970 0x01A4: "SH3E", 971 0x01A6: "SH4", 972 0x01A8: "SH5", 973 0x01C0: "ARM", 974 0x01C2: "THUMB", 975 0x01C4: "ARMNT", 976 0x01D3: "AM33", 977 0x01F0: "POWERPC", 978 0x01F1: "POWERPCFP", 979 0x0200: "IA64", 980 0x0266: "MIPS16", 981 0x0284: "ALPHA64", 982 # 0x0284: 'AXP64', # same 983 0x0366: "MIPSFPU", 984 0x0466: "MIPSFPU16", 985 0x0520: "TRICORE", 986 0x0CEF: "CEF", 987 0x0EBC: "EBC", 988 0x8664: "AMD64", 989 0x9041: "M32R", 990 0xC0EE: "CEE", 991} 992 993 994def get_shared_library_arch(filename: str) -> str: 995 """Get the architecture of shared library.""" 996 with io.open(filename, "rb") as fp: 997 dos_headers = fp.read(64) 998 _ = fp.read(4) 999 1000 magic, skip, offset = struct.unpack("=2s58sl", dos_headers) 1001 1002 if magic != b"MZ": 1003 raise Exception("Not an executable") 1004 1005 fp.seek(offset, io.SEEK_SET) 1006 pe_header = fp.read(6) 1007 1008 sig, skip, machine = struct.unpack(str("=2s2sH"), pe_header) 1009 1010 if sig != b"PE": 1011 raise Exception("Not a PE executable") 1012 1013 return machine_types.get(machine, "UNKNOWN") 1014 1015 1016def get_arch(filename: str) -> Tuple[int, ...]: 1017 """Get the architecture of the platform.""" 1018 this_platform = sys.platform 1019 if this_platform.startswith("win"): 1020 machine_type = get_shared_library_arch(filename) 1021 if machine_type == "I386": 1022 return (32,) 1023 elif machine_type in ("IA64", "AMD64"): 1024 return (64,) 1025 else: 1026 return () 1027 elif this_platform not in ("linux2", "linux3", "linux", "darwin"): 1028 raise OSError("Unsupported platform: %s" % this_platform) 1029 1030 res = subprocess.run(["file", filename], capture_output=True) 1031 out = res.stdout.decode("ascii") 1032 ret = [] 1033 if this_platform.startswith("linux"): 1034 if "32-bit" in out: 1035 ret.append(32) 1036 if "64-bit" in out: 1037 ret.append(64) 1038 else: # darwin 1039 if "(for architecture i386)" in out: 1040 ret.append(32) 1041 if "(for architecture x86_64)" in out: 1042 ret.append(64) 1043 1044 return tuple(ret) 1045