pytestlab 0.2.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. pytestlab/__init__.py +69 -0
  2. pytestlab/_log.py +62 -0
  3. pytestlab/analysis/__init__.py +7 -0
  4. pytestlab/analysis/fft.py +91 -0
  5. pytestlab/analysis/fr_analysis.py +38 -0
  6. pytestlab/bench.py +618 -0
  7. pytestlab/cli.py +916 -0
  8. pytestlab/common/__init__.py +5 -0
  9. pytestlab/common/enums.py +89 -0
  10. pytestlab/common/health.py +19 -0
  11. pytestlab/compliance/__init__.py +19 -0
  12. pytestlab/compliance/audit.py +88 -0
  13. pytestlab/compliance/patch.py +120 -0
  14. pytestlab/compliance/signature.py +146 -0
  15. pytestlab/compliance/tsa.py +51 -0
  16. pytestlab/config/__init__.py +16 -0
  17. pytestlab/config/_mixins.py +37 -0
  18. pytestlab/config/accuracy.py +62 -0
  19. pytestlab/config/base.py +21 -0
  20. pytestlab/config/bench_config.py +80 -0
  21. pytestlab/config/bench_loader.py +36 -0
  22. pytestlab/config/config.py +20 -0
  23. pytestlab/config/dc_active_load_config.py +98 -0
  24. pytestlab/config/instrument_config.py +31 -0
  25. pytestlab/config/loader.py +179 -0
  26. pytestlab/config/multimeter_config.py +145 -0
  27. pytestlab/config/oscilloscope_config.py +63 -0
  28. pytestlab/config/power_meter_config.py +14 -0
  29. pytestlab/config/power_supply_config.py +78 -0
  30. pytestlab/config/spectrum_analyzer_config.py +18 -0
  31. pytestlab/config/virtual_instrument_config.py +7 -0
  32. pytestlab/config/vna_config.py +16 -0
  33. pytestlab/config/waveform_generator_config.py +38 -0
  34. pytestlab/errors.py +145 -0
  35. pytestlab/experiments/__init__.py +6 -0
  36. pytestlab/experiments/database.py +724 -0
  37. pytestlab/experiments/experiments.py +164 -0
  38. pytestlab/experiments/results.py +357 -0
  39. pytestlab/experiments/sweep.py +658 -0
  40. pytestlab/gui/__init__.py +23 -0
  41. pytestlab/gui/async_utils.py +63 -0
  42. pytestlab/gui/builder.py +209 -0
  43. pytestlab/instruments/AutoInstrument.py +565 -0
  44. pytestlab/instruments/DCActiveLoad.py +361 -0
  45. pytestlab/instruments/Multimeter.py +309 -0
  46. pytestlab/instruments/Oscilloscope.py +1643 -0
  47. pytestlab/instruments/PowerMeter.py +86 -0
  48. pytestlab/instruments/PowerSupply.py +539 -0
  49. pytestlab/instruments/SpectrumAnalyser.py +55 -0
  50. pytestlab/instruments/VectorNetworkAnalyser.py +72 -0
  51. pytestlab/instruments/VirtualInstrument.py +79 -0
  52. pytestlab/instruments/WaveformGenerator.py +1704 -0
  53. pytestlab/instruments/__init__.py +8 -0
  54. pytestlab/instruments/backends/__init__.py +6 -0
  55. pytestlab/instruments/backends/async_visa_backend.py +167 -0
  56. pytestlab/instruments/backends/lamb.py +189 -0
  57. pytestlab/instruments/backends/recording_backend.py +110 -0
  58. pytestlab/instruments/backends/replay_backend.py +162 -0
  59. pytestlab/instruments/backends/session_recording_backend.py +148 -0
  60. pytestlab/instruments/backends/sim_backend.py +658 -0
  61. pytestlab/instruments/backends/visa_backend.py +134 -0
  62. pytestlab/instruments/instrument.py +670 -0
  63. pytestlab/instruments/scpi_engine.py +481 -0
  64. pytestlab/measurements/__init__.py +8 -0
  65. pytestlab/measurements/session.py +365 -0
  66. pytestlab/profiles/__init__.py +0 -0
  67. pytestlab/profiles/keysight/34460A.yaml +27 -0
  68. pytestlab/profiles/keysight/34470A.yaml +30 -0
  69. pytestlab/profiles/keysight/DSOX1202G.yaml +102 -0
  70. pytestlab/profiles/keysight/DSOX1204G.yaml +132 -0
  71. pytestlab/profiles/keysight/DSOX3054G.yaml +122 -0
  72. pytestlab/profiles/keysight/E36313A.yaml +37 -0
  73. pytestlab/profiles/keysight/E5071C_VNA.yaml +13 -0
  74. pytestlab/profiles/keysight/EDU33212A.yaml +53 -0
  75. pytestlab/profiles/keysight/EDU34450A.yaml +176 -0
  76. pytestlab/profiles/keysight/EDU36311A.yaml +122 -0
  77. pytestlab/profiles/keysight/EDU36311A_recorded.yaml +48 -0
  78. pytestlab/profiles/keysight/EL33133A.yaml +126 -0
  79. pytestlab/profiles/keysight/HD304MSO.yaml +148 -0
  80. pytestlab/profiles/keysight/MSOX2024A.yaml +122 -0
  81. pytestlab/profiles/keysight/MXR404A.yaml +102 -0
  82. pytestlab/profiles/keysight/N9000A_SA.yaml +12 -0
  83. pytestlab/profiles/keysight/U2000A_PM.yaml +10 -0
  84. pytestlab/profiles/pytestlab/binary_wave_data.bin +1 -0
  85. pytestlab/profiles/pytestlab/virtual_instrument.yaml +68 -0
  86. pytestlab/profiles/rohdeschwarz/NGE102B.yaml +144 -0
  87. pytestlab/schemas/awg.json +166 -0
  88. pytestlab/schemas/dc_active_load.json +511 -0
  89. pytestlab/schemas/dmm.json +120 -0
  90. pytestlab/schemas/oscilloscope.json +316 -0
  91. pytestlab/schemas/psu.json +86 -0
  92. pytestlab-0.2.1.dist-info/METADATA +317 -0
  93. pytestlab-0.2.1.dist-info/RECORD +95 -0
  94. pytestlab-0.2.1.dist-info/WHEEL +4 -0
  95. pytestlab-0.2.1.dist-info/entry_points.txt +2 -0
pytestlab/__init__.py ADDED
@@ -0,0 +1,69 @@
1
+ """
2
+ pytestlab – scientific measurement toolbox
3
+ =========================================
4
+
5
+ This file now **re-exports** the new high-level measurement builder so that
6
+ users can simply write
7
+
8
+ >>> from pytestlab import Measurement
9
+
10
+ or
11
+
12
+ >>> from pytestlab.measurements import Measurement
13
+ """
14
+
15
+ __version__ = "0.2.1" # Update this line to change the version
16
+
17
+ from importlib import metadata as _metadata
18
+ import logging # Required for set_log_level
19
+ from ._log import get_logger, set_log_level, reinitialize_logging
20
+
21
+
22
+
23
+ # ─── Public re-exports from existing sub-packages ──────────────────────────
24
+ from .config import *
25
+ from .experiments import *
26
+ from .instruments import *
27
+ from .errors import *
28
+ from .bench import Bench
29
+
30
+ # ─── New high-level builder ────────────────────────────────────────────────
31
+ from .measurements.session import Measurement, MeasurementSession # noqa: E402 pylint: disable=wrong-import-position
32
+
33
+ __all__ = [
34
+ # Config
35
+ "OscilloscopeConfig",
36
+ "MultimeterConfig",
37
+ "PowerSupplyConfig",
38
+ "WaveformGeneratorConfig",
39
+ # Instruments
40
+ "Oscilloscope",
41
+ "Multimeter",
42
+ "PowerSupply",
43
+ "WaveformGenerator",
44
+ "AutoInstrument",
45
+ "InstrumentManager",
46
+ # Experiments
47
+ "Experiment",
48
+ "MeasurementResult",
49
+ # Errors
50
+ "InstrumentError",
51
+ "InstrumentConfigurationError",
52
+ "InstrumentParameterError",
53
+ # Bench System
54
+ "Bench",
55
+ # New measurement system
56
+ "Measurement",
57
+ "MeasurementSession",
58
+ "set_log_level",
59
+ ]
60
+
61
+ # Version is defined statically above, but we can still try to get it from metadata
62
+ # try: # pragma: no cover
63
+ # __version__ = _metadata.version(__name__)
64
+ # except _metadata.PackageNotFoundError: # pragma: no cover
65
+ # __version__ = "0.1.0"
66
+
67
+ # needs to be imported after the MeasurementResult class is defined
68
+ from . import compliance
69
+ compliance.initialize()
pytestlab/_log.py ADDED
@@ -0,0 +1,62 @@
1
+ from __future__ import annotations
2
+ import logging
3
+ import sys
4
+ import os
5
+
6
+ _root_logger = logging.getLogger("pytestlab")
7
+
8
+ def setup_logging():
9
+ """
10
+ Set up logging for pytestlab.
11
+ This function is called automatically when the first logger is requested.
12
+ It can also be called manually to reconfigure logging.
13
+ """
14
+ level_name = os.getenv("PYTESTLAB_LOG_LEVEL", os.getenv("PYTESTLAB_LOG", "WARNING")).upper()
15
+ log_level = getattr(logging, level_name, logging.WARNING)
16
+ log_file = os.getenv("PYTESTLAB_LOG_FILE")
17
+
18
+ # Remove all handlers from our logger to reconfigure
19
+ for handler in _root_logger.handlers[:]:
20
+ _root_logger.removeHandler(handler)
21
+
22
+ if log_file:
23
+ handler: logging.Handler = logging.FileHandler(log_file)
24
+ else:
25
+ handler = logging.StreamHandler(sys.stderr)
26
+
27
+ formatter = logging.Formatter("%(asctime)s %(name)s %(levelname)s – %(message)s")
28
+ handler.setFormatter(formatter)
29
+ _root_logger.addHandler(handler)
30
+ _root_logger.setLevel(log_level)
31
+ # Enable propagation so pytest caplog can capture logs
32
+ _root_logger.propagate = True
33
+
34
+ def reinitialize_logging():
35
+ """
36
+ Reinitialize logging configuration, useful for tests that modify environment variables.
37
+ """
38
+ setup_logging()
39
+
40
+ def set_log_level(level: int | str):
41
+ """
42
+ Sets the logging level for the pytestlab logger.
43
+ :param level: The logging level, e.g., "DEBUG", "INFO", logging.DEBUG, logging.INFO.
44
+ """
45
+ try:
46
+ if isinstance(level, str):
47
+ level = level.upper()
48
+ _root_logger.setLevel(level)
49
+ except ValueError as e:
50
+ # Log the warning and keep the current level
51
+ _root_logger.warning(f"Invalid log level: {level}")
52
+
53
+ def get_logger(name: str) -> logging.Logger:
54
+ """
55
+ Retrieves a logger instance, configuring the root logger on first call.
56
+ """
57
+ if not _root_logger.handlers:
58
+ setup_logging()
59
+ return _root_logger.getChild(name)
60
+
61
+ # Initial setup when module is loaded.
62
+ setup_logging()
@@ -0,0 +1,7 @@
1
+ # PyTestLab Analysis Submodule
2
+ # This submodule contains common signal processing and analysis functions.
3
+
4
+ __all__ = ["fft", "fr_analysis"]
5
+
6
+ from . import fft
7
+ from . import fr_analysis
@@ -0,0 +1,91 @@
1
+ # pytestlab/analysis/fft.py
2
+ import numpy as np
3
+ from typing import Tuple, Optional
4
+
5
+ def compute_fft(
6
+ time_array: np.ndarray,
7
+ voltage_array: np.ndarray,
8
+ window: Optional[str] = 'hann' # Example: allow windowing
9
+ ) -> Tuple[np.ndarray, np.ndarray]:
10
+ """
11
+ Computes the Fast Fourier Transform (FFT) of a given time-domain signal.
12
+
13
+ Args:
14
+ time_array: NumPy array of time values.
15
+ voltage_array: NumPy array of voltage (or other signal) values.
16
+ window: Optional windowing function to apply before FFT (e.g., 'hann', 'hamming').
17
+ Set to None or an empty string to disable windowing.
18
+
19
+ Returns:
20
+ A tuple containing:
21
+ - frequency_array: NumPy array of frequency bins.
22
+ - magnitude_array: NumPy array of FFT magnitudes (linear).
23
+ """
24
+ # Ensure time_array and voltage_array are of the same size
25
+ if not isinstance(time_array, np.ndarray) or not isinstance(voltage_array, np.ndarray):
26
+ raise TypeError("Input arrays must be NumPy ndarrays.")
27
+ if time_array.size != voltage_array.size:
28
+ raise ValueError("Time and voltage arrays must have the same size.")
29
+ if time_array.ndim != 1 or voltage_array.ndim != 1:
30
+ raise ValueError("Input arrays must be 1-dimensional.")
31
+
32
+ N = voltage_array.size
33
+ if N == 0:
34
+ return np.array([]), np.array([])
35
+ if N <= 1: # FFT not meaningful for 0 or 1 sample
36
+ return np.array([]), np.array([])
37
+
38
+ # Apply windowing if specified
39
+ if window:
40
+ if window == 'hann':
41
+ voltage_array_windowed = voltage_array * np.hanning(N)
42
+ elif window == 'hamming':
43
+ voltage_array_windowed = voltage_array * np.hamming(N)
44
+ # Add other windows or raise error for unsupported window
45
+ # elif window is None or window == '': # No window
46
+ # voltage_array_windowed = voltage_array
47
+ else:
48
+ raise ValueError(f"Unsupported window function: {window}. Supported: 'hann', 'hamming', None.")
49
+ else: # No window
50
+ voltage_array_windowed = voltage_array
51
+
52
+
53
+ # Compute FFT
54
+ fft_values = np.fft.fft(voltage_array_windowed)
55
+ # Take only positive frequencies (first N // 2 points)
56
+ # For real inputs, the FFT is symmetric, so we only need half.
57
+ fft_magnitudes = np.abs(fft_values)[:N // 2]
58
+
59
+ # Compute frequency bins
60
+ # This requires sampling frequency Fs.
61
+ # A more robust way if time_array is uniformly spaced:
62
+ if N > 1 and (time_array[-1] - time_array[0]) > 0:
63
+ # Total duration T = time_array[-1] - time_array[0]
64
+ # Number of sampling intervals = N - 1
65
+ # Sampling interval dt = T / (N - 1)
66
+ # Sampling frequency Fs = 1 / dt = (N - 1) / T
67
+ dt = (time_array[-1] - time_array[0]) / (N - 1)
68
+ if dt <= 0: # Avoid division by zero or negative dt if time_array is not monotonic
69
+ # This case should ideally be caught by pre-checks or handled by requiring Fs
70
+ return np.array([]), np.array([])
71
+ Fs = 1 / dt
72
+ elif N == 1 and time_array.size == 1: # Single point, Fs is undefined, Nyquist is 0
73
+ # Return empty or a specific representation for a single point "spectrum"
74
+ # For FFT, typically need >1 points.
75
+ # Or, if Fs is *known* (e.g. from instrument settings), it could be passed in.
76
+ # For now, aligning with N<=1 check above.
77
+ return np.array([]), np.array([])
78
+ else: # Cannot determine Fs (e.g., N > 1 but time_array[-1] == time_array[0]), or N is too small
79
+ # Or if time_array is not sorted, (time_array[-1] - time_array[0]) could be non-positive.
80
+ # Consider requiring Fs as an input for more robustness if time_array properties are not guaranteed.
81
+ # For now, returning empty as a safe default.
82
+ return np.array([]), np.array([])
83
+
84
+
85
+ # d = sampling interval = 1/Fs
86
+ frequency_array = np.fft.fftfreq(N, d=1/Fs)[:N // 2]
87
+
88
+ # fft_magnitudes are linear. User can convert to dB if needed:
89
+ # fft_magnitudes_db = 20 * np.log10(fft_magnitudes)
90
+
91
+ return frequency_array, fft_magnitudes
@@ -0,0 +1,38 @@
1
+ # pytestlab/analysis/fr_analysis.py
2
+ # Placeholder for Frequency Response Analysis functions.
3
+
4
+ # Example conceptual function (not implemented yet):
5
+ # import numpy as np
6
+ # from typing import Tuple
7
+ #
8
+ # def compute_frequency_response(
9
+ # input_time_array: np.ndarray,
10
+ # input_voltage_array: np.ndarray,
11
+ # output_time_array: np.ndarray,
12
+ # output_voltage_array: np.ndarray,
13
+ # window: Optional[str] = 'hann'
14
+ # ) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
15
+ # """
16
+ # Computes the frequency response (e.g., gain and phase) from input and output signals.
17
+ #
18
+ # Args:
19
+ # input_time_array: NumPy array of time values for the input signal.
20
+ # input_voltage_array: NumPy array of voltage values for the input signal.
21
+ # output_time_array: NumPy array of time values for the output signal.
22
+ # output_voltage_array: NumPy array of voltage values for the output signal.
23
+ # window: Optional windowing function to apply before FFT.
24
+ #
25
+ # Returns:
26
+ # A tuple containing:
27
+ # - frequency_array: NumPy array of frequency bins.
28
+ # - gain_array: NumPy array of gain values (e.g., in dB).
29
+ # - phase_array: NumPy array of phase values (e.g., in degrees or radians).
30
+ # """
31
+ # # This would involve:
32
+ # # 1. Aligning or ensuring consistent sampling of input and output signals.
33
+ # # 2. Computing FFT of both input and output signals (e.g., using compute_fft from .fft).
34
+ # # 3. Calculating the transfer function H(f) = FFT(output) / FFT(input).
35
+ # # 4. Deriving gain (magnitude of H(f)) and phase (angle of H(f)).
36
+ # pass
37
+
38
+ __all__ = [] # No functions exported yet