diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5d8648ab490..0060e0cde1a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,29 +3,29 @@ repos: # Ruff version. rev: v0.15.11 hooks: - - id: ruff-check - types_or: [python, pyi, jupyter, toml] - args: [ --fix, --exit-non-zero-on-fix ] - - id: ruff-format + - id: ruff-check + types_or: [python, pyi, jupyter, toml] + args: [--fix, --exit-non-zero-on-fix] + - id: ruff-format - repo: https://github.com/pre-commit/pre-commit-hooks rev: v6.0.0 hooks: - - id: trailing-whitespace - - id: end-of-file-fixer - - id: check-ast - - id: check-json - exclude: asv.conf.json - - id: check-toml - - id: check-yaml - - id: check-case-conflict - - id: debug-statements - - id: mixed-line-ending - args: ['--fix=no'] + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-ast + - id: check-json + exclude: asv.conf.json + - id: check-toml + - id: check-yaml + - id: check-case-conflict + - id: debug-statements + - id: mixed-line-ending + args: ["--fix=no"] - repo: https://github.com/gitleaks/gitleaks rev: v8.30.1 hooks: - - id: gitleaks - - repo: https://github.com/jumanjihouse/pre-commit-hooks - rev: 3.0.0 + - id: gitleaks + - repo: https://github.com/shellcheck-py/shellcheck-py + rev: v0.11.0.1 hooks: - - id: shellcheck + - id: shellcheck diff --git a/pyproject.toml b/pyproject.toml index 4515823ee8d..be7cc0a2b2b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -209,11 +209,12 @@ reportUnnecessaryContains = "none" [tool.pytest.ini_options] minversion = "7.2" -testpaths = "tests" +testpaths = ["tests","src"] # we include src to collect doctests addopts = "-n auto --dist=loadfile --cov-config=pyproject.toml" asyncio_default_fixture_loop_scope = "function" markers = "serial" + # we ignore warnings # triggered by third party packages # and error on all other warnings diff --git a/src/qcodes/configuration/config.py b/src/qcodes/configuration/config.py index 70e372d2524..97d1b00f9ce 100644 --- a/src/qcodes/configuration/config.py +++ b/src/qcodes/configuration/config.py @@ -240,7 +240,7 @@ def add( default: default value, stored only in the schema Examples: - >>> defaults.add("trace_color", "blue", "string", "description") + >>> defaults.add("trace_color", "blue", "string", "description") # doctest: +SKIP will update the config: @@ -477,7 +477,10 @@ def __getattr__(self, name: str) -> Any: """ Overwrite ``__getattr__`` to provide dot access """ - return self.__getitem__(name) + try: + return self.__getitem__(name) + except KeyError: + raise AttributeError(name) def __setattr__(self, key: str, value: Any) -> None: """ diff --git a/src/qcodes/dataset/guid_helpers.py b/src/qcodes/dataset/guid_helpers.py index 7fb2e98b3b2..04b2fdaaf24 100644 --- a/src/qcodes/dataset/guid_helpers.py +++ b/src/qcodes/dataset/guid_helpers.py @@ -80,12 +80,10 @@ def guids_from_list_str(s: str) -> tuple[str, ...] | None: For an empty list/tuple/set or empty string an empty tuple is returned. Examples: - >>> guids_from_str( - "['07fd7195-c51e-44d6-a085-fa8274cf00d6', \ - '070d7195-c51e-44d6-a085-fa8274cf00d6']") - will return - ('07fd7195-c51e-44d6-a085-fa8274cf00d6', - '070d7195-c51e-44d6-a085-fa8274cf00d6') + >>> guids_from_list_str( + ... "['07fd7195-c51e-44d6-a085-fa8274cf00d6','070d7195-c51e-44d6-a085-fa8274cf00d6']" + ... ) + ('07fd7195-c51e-44d6-a085-fa8274cf00d6', '070d7195-c51e-44d6-a085-fa8274cf00d6') """ if s == "": diff --git a/src/qcodes/dataset/measurements.py b/src/qcodes/dataset/measurements.py index cb11a9246af..522d49a42d1 100644 --- a/src/qcodes/dataset/measurements.py +++ b/src/qcodes/dataset/measurements.py @@ -186,7 +186,7 @@ def add_result(self, *result_tuples: ResType) -> None: is four dimensional (v1, v2, c1, c2). The corresponding call to this function would be - >>> datasaver.add_result((v1, 0.1), (v2, 0.2), (c1, 5), (c2, -2.1)) + >>> datasaver.add_result((v1, 0.1), (v2, 0.2), (c1, 5), (c2, -2.1)) # doctest: +SKIP For better performance, this function does not immediately write to the database, but keeps the results in memory. Writing happens every diff --git a/src/qcodes/instrument/instrument.py b/src/qcodes/instrument/instrument.py index 8f2c36b3961..eb8e45082e2 100644 --- a/src/qcodes/instrument/instrument.py +++ b/src/qcodes/instrument/instrument.py @@ -192,7 +192,7 @@ def close_all(cls) -> None: closed. Examples: - >>> atexit.register(qc.Instrument.close_all()) + >>> atexit.register(qc.Instrument.close_all()) # doctest: +SKIP """ log.info("Closing all registered instruments") diff --git a/src/qcodes/instrument_drivers/Keithley/_Keithley_2600.py b/src/qcodes/instrument_drivers/Keithley/_Keithley_2600.py index bf90ee188f3..9d0c2148d8a 100644 --- a/src/qcodes/instrument_drivers/Keithley/_Keithley_2600.py +++ b/src/qcodes/instrument_drivers/Keithley/_Keithley_2600.py @@ -922,41 +922,49 @@ def __init__(self, parent: Keithley2600, name: str, channel: str) -> None: **Direct usage (returns ndarray)** - Example 1D:: + Example 1D: + + .. code-block:: python - >>> from qcodes.dataset import LinSweep - >>> keith.smua.setup_fastsweep(LinSweep(keith.smua.volt, 0, 1, 100)) - >>> data = keith.smua.fastsweep() # or: keith.smua.fastsweep.get() - >>> data.shape + from qcodes.dataset import LinSweep + keith.smua.setup_fastsweep(LinSweep(keith.smua.volt, 0, 1, 100)) + data = keith.smua.fastsweep() # or: keith.smua.fastsweep.get() + data.shape (100,) - Example 2D (inner=smub, outer=smua):: + Example 2D (inner=smub, outer=smua): + + .. code-block:: python - >>> from qcodes.dataset import LinSweep - >>> keith.smua.setup_fastsweep( - ... LinSweep(keith.smub.volt, 0, 1, 100), # inner - ... LinSweep(keith.smua.volt, 0, 0.5, 20), # outer - ... ) - >>> data = keith.smub.fastsweep() # call on inner channel - >>> data.shape + from qcodes.dataset import LinSweep + keith.smua.setup_fastsweep( + LinSweep(keith.smub.volt, 0, 1, 100), # inner + LinSweep(keith.smua.volt, 0, 0.5, 20), # outer + ) + data = keith.smub.fastsweep() # call on inner channel + data.shape (20, 100) **Dataset logging with do0d** To log the fast sweep into a QCoDeS dataset, use :func:`do0d` - with the ``fastsweep`` parameter:: + with the ``fastsweep`` parameter: + + .. code-block:: python + + from qcodes.dataset import LinSweep, do0d + keith.smua.setup_fastsweep(LinSweep(keith.smua.volt, 0, 1, 100)) + ds, _, _ = do0d(keith.smua.fastsweep) - >>> from qcodes.dataset import LinSweep, do0d - >>> keith.smua.setup_fastsweep(LinSweep(keith.smua.volt, 0, 1, 100)) - >>> ds, _, _ = do0d(keith.smua.fastsweep) + For a 2D sweep (inner=smub, outer=smua): - For a 2D sweep (inner=smub, outer=smua):: + .. code-block:: python - >>> from qcodes.dataset import LinSweep, do0d - >>> keith.smua.setup_fastsweep( - ... LinSweep(keith.smub.volt, 0, 1, 100), # inner - ... LinSweep(keith.smua.volt, 0, 0.5, 20), # outer - ... ) + from qcodes.dataset import LinSweep, do0d + keith.smua.setup_fastsweep( + LinSweep(keith.smub.volt, 0, 1, 100), # inner + LinSweep(keith.smua.volt, 0, 0.5, 20), # outer + ) """ self.timetrace_npts: Parameter = self.add_parameter( @@ -1065,25 +1073,31 @@ def setup_fastsweep( Example 1D: - >>> from qcodes.dataset import LinSweep - >>> keith.smua.setup_fastsweep(LinSweep(keith.smua.volt, 0, 1, 100)) - >>> ds, _, _ = do0d(keith.smua.fastsweep) + .. code-block:: python + + from qcodes.dataset import LinSweep + keith.smua.setup_fastsweep(LinSweep(keith.smua.volt, 0, 1, 100)) + ds, _, _ = do0d(keith.smua.fastsweep) Example 2D (inner=smua, outer=smub): - >>> keith.smua.setup_fastsweep( - ... LinSweep(keith.smua.volt, 0, 1, 100), # inner - ... LinSweep(keith.smub.volt, 0, 0.5, 20), # outer - ... ) - >>> ds, _, _ = do0d(keith.smua.fastsweep) # call on inner channel + .. code-block:: python + + keith.smua.setup_fastsweep( + LinSweep(keith.smua.volt, 0, 1, 100), # inner + LinSweep(keith.smub.volt, 0, 0.5, 20), # outer + ) + ds, _, _ = do0d(keith.smua.fastsweep) # call on inner channel Example 2D (inner=smub, outer=smua): - >>> keith.smua.setup_fastsweep( - ... LinSweep(keith.smub.volt, 0, 1, 100), # inner - ... LinSweep(keith.smua.volt, 0, 0.5, 20), # outer - ... ) - >>> ds, _, _ = do0d(keith.smub.fastsweep) # call on inner channel + .. code-block:: python + + keith.smua.setup_fastsweep( + LinSweep(keith.smub.volt, 0, 1, 100), # inner + LinSweep(keith.smua.volt, 0, 0.5, 20), # outer + ) + ds, _, _ = do0d(keith.smub.fastsweep) # call on inner channel """ diff --git a/src/qcodes/instrument_drivers/Keysight/keysight_e4980a.py b/src/qcodes/instrument_drivers/Keysight/keysight_e4980a.py index 76fe3c0383b..b8d5e32cd20 100644 --- a/src/qcodes/instrument_drivers/Keysight/keysight_e4980a.py +++ b/src/qcodes/instrument_drivers/Keysight/keysight_e4980a.py @@ -42,9 +42,11 @@ class KeysightE4980AMeasurementPair(MultiParameter): To create a measurement data with capacitance=1.2, and dissipation_factor=3.4. - >>> data = KeysightE4980AMeasurementPair(name="CPD", - names=("capacitance", "dissipation_factor"), - units=("F", "")) + >>> data = KeysightE4980AMeasurementPair( + ... name="CPD", + ... names=("capacitance", "dissipation_factor"), + ... units=("F", ""), + ... ) >>> data.set((1.2, 3.4)) >>> data.get() (1.2, 3.4) diff --git a/src/qcodes/instrument_drivers/Lakeshore/lakeshore_base.py b/src/qcodes/instrument_drivers/Lakeshore/lakeshore_base.py index 433090d1389..9407aaf43ba 100644 --- a/src/qcodes/instrument_drivers/Lakeshore/lakeshore_base.py +++ b/src/qcodes/instrument_drivers/Lakeshore/lakeshore_base.py @@ -629,8 +629,8 @@ def _get_sum_terms(available_terms: "Sequence[int]", number: int) -> list[int]: Example: >>> terms = [1, 16, 32, 64, 128] - >>> get_sum_terms(terms, 96) - ... [64, 32] # This is correct because 96=64+32 + >>> [int(x) for x in LakeshoreBaseSensorChannel._get_sum_terms(terms, 96)] + [64, 32] """ terms_in_number: list[int] = [] diff --git a/src/qcodes/instrument_drivers/conftest.py b/src/qcodes/instrument_drivers/conftest.py new file mode 100644 index 00000000000..7b030db2879 --- /dev/null +++ b/src/qcodes/instrument_drivers/conftest.py @@ -0,0 +1,31 @@ +import os +from pathlib import Path + +# Drivers that require optional third-party libraries not generally available. +# These must be excluded from doctest collection to avoid ImportErrors. +collect_ignore_glob = [ + os.path.join("Galil", "*"), + os.path.join("Minicircuits", "*"), + os.path.join("QuantumDesign", "DynaCoolPPMS", "private", "*"), +] + + +def _find_deprecated_driver_modules() -> list[str]: + """ + Scan for deprecated driver alias modules that emit deprecation warnings + on import. Importing these during doctest collection triggers noisy + warnings and serves no purpose since they contain no doctests. + """ + drivers_dir = Path(__file__).parent + deprecated: list[str] = [] + for path in sorted(drivers_dir.rglob("*.py")): + try: + text = path.read_text(encoding="utf-8") + except Exception: + continue + if "module is deprecated" in text: + deprecated.append(str(path.relative_to(drivers_dir))) + return deprecated + + +collect_ignore = _find_deprecated_driver_modules() diff --git a/src/qcodes/instrument_drivers/rigol/Rigol_DG1062.py b/src/qcodes/instrument_drivers/rigol/Rigol_DG1062.py index ac6f6d7297b..cc28e8c750c 100644 --- a/src/qcodes/instrument_drivers/rigol/Rigol_DG1062.py +++ b/src/qcodes/instrument_drivers/rigol/Rigol_DG1062.py @@ -286,11 +286,19 @@ def apply(self, **kwargs: Any) -> None: """ Public interface to apply a waveform on the channel Example: - >>> gd = RigolDG1062("gd", "TCPIP0::169.254.187.99::inst0::INSTR") - >>> gd.channels[0].apply(waveform="SIN", freq=1E3, ampl=1.0, offset=0, phase=0) + + .. code-block:: python + + gd = RigolDG1062("gd", "TCPIP0::169.254.187.99::inst0::INSTR") + gd.channels[0].apply(waveform="SIN", freq=1E3, ampl=1.0, offset=0, phase=0) + Valid waveforms are: HARM, NOIS, RAMP, SIN, SQU, TRI, USER, DC, ARB To find the correct arguments of each waveform we can e.g. do: - >>> help(gd.channels[0].sin) + + .. code-block:: python + + help(gd.channels[0].sin) + Notice the lower case when accessing the waveform through convenience functions. If not kwargs are given a dictionary with the current waveform diff --git a/src/qcodes/instrument_drivers/stahl/stahl.py b/src/qcodes/instrument_drivers/stahl/stahl.py index afa3637fe84..bf8306fb6b5 100644 --- a/src/qcodes/instrument_drivers/stahl/stahl.py +++ b/src/qcodes/instrument_drivers/stahl/stahl.py @@ -36,8 +36,10 @@ def chain(*functions: Callable[..., Any]) -> Callable[..., Any]: Example: >>> def f(): - >>> return "1.2" - >>> chain(f, float)() # return 1.2 as float + ... return "1.2" + >>> chain(f, float)() + 1.2 + """ diff --git a/src/qcodes/instrument_drivers/stanford_research/SR830.py b/src/qcodes/instrument_drivers/stanford_research/SR830.py index 6ce5413f602..f21c5080080 100644 --- a/src/qcodes/instrument_drivers/stanford_research/SR830.py +++ b/src/qcodes/instrument_drivers/stanford_research/SR830.py @@ -769,10 +769,15 @@ def snap(self, *parameters: str) -> tuple[float, ...]: A tuple of floating point values in the same order as requested. Examples: - >>> lockin.snap('x','y') -> tuple(x,y) - >>> lockin.snap('aux1','aux2','freq','phase') - >>> -> tuple(aux1,aux2,freq,phase) + .. code-block:: python + + lockin.snap('x','y') -> tuple(x,y) + + .. code-block:: python + + lockin.snap('aux1','aux2','freq','phase') + -> tuple(aux1,aux2,freq,phase) Note: Volts for x, y, r, and aux 1-4 diff --git a/src/qcodes/logger/instrument_logger.py b/src/qcodes/logger/instrument_logger.py index ddc32374fcb..35192f74f8f 100644 --- a/src/qcodes/logger/instrument_logger.py +++ b/src/qcodes/logger/instrument_logger.py @@ -32,9 +32,11 @@ class InstrumentLoggerAdapter(logging.LoggerAdapter): instance. The context data gets stored in the `extra` dictionary as a property of the - Adapter. It is filled by the ``__init__`` method:: + Adapter. It is filled by the ``__init__`` method: - >>> LoggerAdapter(log, {'instrument': instrument_instance}) + .. code-block:: python + + LoggerAdapter(log, {'instrument': instrument_instance}) """ @@ -163,11 +165,13 @@ def filter_instrument( the supplied instruments to pass. Example: - >>> h1, h2 = logger.get_console_handler(), logger.get_file_handler() - >>> with logger.filter_instruments((qdac, dmm2), handler=[h1, h2]): - >>> qdac.ch01(1) # logged - >>> v1 = dmm2.v() # logged - >>> v2 = keithley.v() # not logged + .. code-block:: python + + h1, h2 = logger.get_console_handler(), logger.get_file_handler() + with logger.filter_instruments((qdac, dmm2), handler=[h1, h2]): + qdac.ch01(1) # logged + v1 = dmm2.v() # logged + v2 = keithley.v() # not logged Args: instrument: The instrument or sequence of instruments to enable diff --git a/src/qcodes/logger/log_analysis.py b/src/qcodes/logger/log_analysis.py index 2f58b5ed9b3..65fe3ef5cb5 100644 --- a/src/qcodes/logger/log_analysis.py +++ b/src/qcodes/logger/log_analysis.py @@ -161,9 +161,12 @@ def capture_dataframe( Context manager to capture the logs in a :class:`pd.DataFrame` Example: - >>> with logger.capture_dataframe() as (handler, cb): - >>> qdac.ch01(1) # some commands - >>> data_frame = cb() + + .. code-block:: python + + with logger.capture_dataframe() as (handler, cb): + qdac.ch01(1) # some commands + data_frame = cb() Args: level: Level at which to capture. diff --git a/src/qcodes/logger/logger.py b/src/qcodes/logger/logger.py index 8291d4b9dd7..f7f6a3e4c98 100644 --- a/src/qcodes/logger/logger.py +++ b/src/qcodes/logger/logger.py @@ -357,8 +357,10 @@ def handler_level( Context manager to temporarily change the level of handlers. Example: - >>> with logger.handler_level(level=logging.DEBUG, handler=[h1, h1]): - >>> root_logger.debug('this is now visible') + .. code-block:: python + + with logger.handler_level(level=logging.DEBUG, handler=[h1, h1]): + root_logger.debug('this is now visible') Args: level: Level to set the handlers to. @@ -384,8 +386,10 @@ def console_level(level: LevelType) -> "Generator[None, None, None]": handler. Example: - >>> with logger.console_level(level=logging.DEBUG): - >>> root_logger.debug('this is now visible') + .. code-block:: python + + with logger.console_level(level=logging.DEBUG): + root_logger.debug('this is now visible') Args: level: Level to set the console handler to. @@ -404,9 +408,11 @@ class LogCapture: from a specific logger. Example: + >>> import logging >>> with LogCapture() as logs: - >>> code_that_makes_logs(...) - >>> log_str = logs.value + ... logging.warning("test message") + >>> isinstance(logs.value, str) + True """ diff --git a/src/qcodes/math_utils/field_vector.py b/src/qcodes/math_utils/field_vector.py index 032c60c60f8..a84ccd17a18 100644 --- a/src/qcodes/math_utils/field_vector.py +++ b/src/qcodes/math_utils/field_vector.py @@ -195,14 +195,24 @@ def set_vector(self, **new_values: float) -> None: >>> f = FieldVector(x=0, y=2, z=6) >>> f.set_vector(x=9, y=3, z=1) >>> f.set_vector(r=1, theta=30.0, phi=10.0) - # The following should raise a value error: - # "Can only set vector with a complete value set" - >>> f.set_vector(x=9, y=0) - # Although mathematically it is possible to compute the complete - # vector from the values given, this is too hard to implement with - # generality (and not worth it), so the following will raise the - # above-mentioned ValueError too. - >>> f.set_vector(x=9, y=0, r=3) + + The following raises a ValueError because the value set is + incomplete: + + >>> f.set_vector(x=9, y=0) # doctest: +ELLIPSIS + Traceback (most recent call last): + ... + ValueError: ... + + Although mathematically it is possible to compute the complete + vector from the values given, this is too hard to implement with + generality (and not worth it), so the following will raise a + ValueError too: + + >>> f.set_vector(x=9, y=0, r=3) # doctest: +ELLIPSIS + Traceback (most recent call last): + ... + ValueError: ... """ names = sorted(list(new_values.keys())) @@ -221,13 +231,12 @@ def set_component(self, **new_values: float) -> None: other, setting one has to effect the other). Examples: + Since r is part of the set (r, theta, phi) representing + spherical coordinates, setting r means that theta and phi are + kept constant and only r is changed. After changing r, + (x, y, z) values are recomputed, as is the rho coordinate. + >>> f = FieldVector(x=2, y=3, z=4) - # Since r is part of the set (r, theta, phi) representing - # spherical coordinates, setting r means that theta and phi are - # kept constant and only r is changed. After changing r, - # (x, y, z) values are recomputed, as is the rho coordinate. - # Internally we arrange this by setting x, y, z and rho to None - # and calling self._compute_unknowns(). >>> f.set_component(r=10) Args: diff --git a/src/qcodes/parameters/parameter.py b/src/qcodes/parameters/parameter.py index 7bcfbf304a5..0ffd5e39df3 100644 --- a/src/qcodes/parameters/parameter.py +++ b/src/qcodes/parameters/parameter.py @@ -544,12 +544,14 @@ def sweep( iterated over. Examples: - >>> sweep(0, 10, num=5) - [0.0, 2.5, 5.0, 7.5, 10.0] - >>> sweep(5, 10, step=1) + >>> from qcodes.parameters import Parameter + >>> p = Parameter("p", set_cmd=None, get_cmd=None) + >>> list(p.sweep(0, 10, num=5)) + [0.0, 2.5, 5.0, 7.5, 10.0] + >>> list(p.sweep(5, 10, step=1)) [5.0, 6.0, 7.0, 8.0, 9.0, 10.0] - >>> sweep(15, 10.5, step=1.5) - >[15.0, 13.5, 12.0, 10.5] + >>> list(p.sweep(15, 10.5, step=1.5)) + [15.0, 13.5, 12.0, 10.5] """ return SweepFixedValues(self, start=start, stop=stop, step=step, num=num) diff --git a/src/qcodes/parameters/parameter_base.py b/src/qcodes/parameters/parameter_base.py index c009565b011..c68ff029456 100644 --- a/src/qcodes/parameters/parameter_base.py +++ b/src/qcodes/parameters/parameter_base.py @@ -71,11 +71,13 @@ class _SetParamContext: Example usage: - >>> v = dac.voltage() - >>> with dac.voltage.set_to(-1): - ... # Do stuff with the DAC output set to -1 V. - ... - >>> assert abs(dac.voltage() - v) <= tolerance + .. code-block:: python + + v = dac.voltage() + with dac.voltage.set_to(-1): + # Do stuff with the DAC output set to -1 V. + ... + assert abs(dac.voltage() - v) <= tolerance """ @@ -1227,12 +1229,19 @@ def set_to( >>> p = Parameter("p", set_cmd=None, get_cmd=None) >>> p.set(2) >>> with p.set_to(3): - ... print(f"p value in with block {p.get()}") # prints 3 - ... p.set(5) # raises an exception - >>> print(f"p value outside with block {p.get()}") # prints 2 + ... print(f"p value in with block {p.get()}") + ... try: + ... p.set(5) + ... except TypeError: + ... print("raises an exception") + p value in with block 3 + raises an exception + >>> print(f"p value outside with block {p.get()}") + p value outside with block 2 >>> with p.set_to(3, allow_changes=True): - ... p.set(5) # now this works - >>> print(f"value after second block: {p.get()}") # still prints 2 + ... p.set(5) + >>> print(f"value after second block: {p.get()}") + value after second block: 2 """ context_manager = _SetParamContext(self, value, allow_changes=allow_changes) @@ -1249,14 +1258,20 @@ def restore_at_exit(self, allow_changes: bool = True) -> _SetParamContext: unintentionally modifies a parameter. Example: + >>> from qcodes.parameters import Parameter >>> p = Parameter("p", set_cmd=None, get_cmd=None) >>> p.set(2) >>> with p.restore_at_exit(): ... p.set(3) - ... print(f"value inside with block: {p.get()}") # prints 3 - >>> print(f"value after with block: {p.get()}") # prints 2 + ... print(f"value inside with block: {p.get()}") + value inside with block: 3 + >>> print(f"value after with block: {p.get()}") + value after with block: 2 >>> with p.restore_at_exit(allow_changes=False): - ... p.set(5) # raises an exception + ... p.set(5) # doctest: +ELLIPSIS + Traceback (most recent call last): + ... + TypeError: ... """ return self.set_to(self.cache(), allow_changes=allow_changes) @@ -1420,10 +1435,12 @@ class GetLatest(DelegateAttributes, Generic[ParameterDataTypeVar]): ``parameter.get_latest``. Examples: - >>> # Can be called: - >>> param.get_latest() - >>> # Or used as if it were a gettable-only parameter itself: - >>> Loop(...).each(param.get_latest) + .. code-block:: python + + # Can be called: + param.get_latest() + # Or used as if it were a gettable-only parameter itself: + Loop(...).each(param.get_latest) Args: parameter: Parameter to be wrapped. diff --git a/src/qcodes/parameters/scaled_paramter.py b/src/qcodes/parameters/scaled_paramter.py index 917c7b1aafc..66b375fb005 100644 --- a/src/qcodes/parameters/scaled_paramter.py +++ b/src/qcodes/parameters/scaled_paramter.py @@ -24,14 +24,23 @@ class ScaledParameter(Parameter): Examples: Resistive voltage divider - >>> vd = ScaledParameter(dac.chan0, division = 10) + + .. code-block:: python + + vd = ScaledParameter(dac.chan0, division = 10) Voltage multiplier - >>> vb = ScaledParameter(dac.chan0, gain = 30, name = 'Vb') + + .. code-block:: python + + vb = ScaledParameter(dac.chan0, gain = 30, name = 'Vb') Transimpedance amplifier - >>> Id = ScaledParameter(multimeter.amplitude, - ... division = 1e6, name = 'Id', unit = 'A') + + .. code-block:: python + + Id = ScaledParameter(multimeter.amplitude, + division = 1e6, name = 'Id', unit = 'A') Args: output: Physical Parameter that need conversion. diff --git a/src/qcodes/parameters/sweep_values.py b/src/qcodes/parameters/sweep_values.py index 8ce710347bb..491eb585601 100644 --- a/src/qcodes/parameters/sweep_values.py +++ b/src/qcodes/parameters/sweep_values.py @@ -48,7 +48,7 @@ def make_sweep( >>> make_sweep(5, 10, step=1) [5.0, 6.0, 7.0, 8.0, 9.0, 10.0] >>> make_sweep(15, 10.5, step=1.5) - >[15.0, 13.5, 12.0, 10.5] + [15.0, 13.5, 12.0, 10.5] """ if step and num: @@ -90,13 +90,13 @@ class SweepValues(Metadatable): Must be subclassed to provide the sweep values Intended use is to iterate over in a sweep, so it must support: - >>> .__iter__ # (and .__next__ if necessary). - >>> .set # is provided by the base class + - ``.__iter__`` (and ``.__next__`` if necessary). + - ``.set`` is provided by the base class Optionally, it can have a feedback method that allows the sweep to pass measurements back to this object for adaptive sampling: - >>> .feedback(set_values, measured_values) + - ``.feedback(set_values, measured_values)`` Todo: - Link to adawptive sweep @@ -114,12 +114,14 @@ class SweepValues(Metadatable): example usage: - >>> for i, value in eumerate(sv): + .. code-block:: python + + for i, value in enumerate(sv): sv.set(value) sleep(delay) vals = measure() - sv.feedback((i, ), vals) # optional - sweep should not assume - # .feedback exists + sv.feedback((i, ), vals) # optional - sweep should not assume + # .feedback exists note though that sweeps should only require set and __iter__ - ie "for val in sv", so any class that implements these may be used in sweeps. @@ -188,21 +190,25 @@ class SweepFixedValues(SweepValues): A SweepFixedValues object is normally created by slicing a Parameter p: - >>> sv = p[1.2:2:0.01] # slice notation - sv = p[1, 1.1, 1.3, 1.6] # explicit individual values - sv = p[1.2:2:0.01, 2:3:0.02] # sequence of slices - sv = p[logrange(1,10,.01)] # some function that returns a sequence + .. code-block:: python + + sv = p[1.2:2:0.01] # slice notation + sv = p[1, 1.1, 1.3, 1.6] # explicit individual values + sv = p[1.2:2:0.01, 2:3:0.02] # sequence of slices + sv = p[logrange(1,10,.01)] # some function that returns a sequence You can also use list operations to modify these: - >>> sv += p[2:3:.01] # (another SweepFixedValues of the same parameter) - sv += [4, 5, 6] # (a bare sequence) - sv.extend(p[2:3:.01]) - sv.append(3.2) - sv.reverse() - sv2 = reversed(sv) - sv3 = sv + sv2 - sv4 = sv.copy() + .. code-block:: python + + sv += p[2:3:.01] # (another SweepFixedValues of the same parameter) + sv += [4, 5, 6] # (a bare sequence) + sv.extend(p[2:3:.01]) + sv.append(3.2) + sv.reverse() + sv2 = reversed(sv) + sv3 = sv + sv2 + sv4 = sv.copy() note though that sweeps should only require set and __iter__ - ie "for val in sv", so any class that implements these may be used in sweeps. diff --git a/src/qcodes/utils/partial_utils.py b/src/qcodes/utils/partial_utils.py index fa7587a1430..906f76d1389 100644 --- a/src/qcodes/utils/partial_utils.py +++ b/src/qcodes/utils/partial_utils.py @@ -15,12 +15,12 @@ def partial_with_docstring( Consider the follow example why this is needed: - >>> from functools import partial - >>> def f(): - >>> ... pass - >>> g = partial(f) - >>> g.__doc__ = "bla" - >>> help(g) # this will print the docstring of partial and not the docstring set above + >>> from functools import partial # doctest: +SKIP + >>> def f(): # doctest: +SKIP + ... pass + >>> g = partial(f) # doctest: +SKIP + >>> g.__doc__ = "bla" # doctest: +SKIP + >>> help(g) # doctest: +SKIP Args: func: A function that its docstring will be accessed. diff --git a/src/qcodes/utils/threading_utils.py b/src/qcodes/utils/threading_utils.py index 552c51e79a7..327baee49ec 100644 --- a/src/qcodes/utils/threading_utils.py +++ b/src/qcodes/utils/threading_utils.py @@ -19,12 +19,15 @@ class RespondingThread(threading.Thread, Generic[T]): The `output` method joins the thread, then checks for errors and returns the output value. - so, if you have a function `f` where `f(1, 2, a=3) == 4`, then: + so, if you have a function `f` where `f(1, 2, a=3) == 6`, then: + >>> def f(x, y, a=0): + ... return x + y + a >>> thread = RespondingThread(target=f, args=(1, 2), kwargs={'a': 3}) >>> thread.start() - >>> # do other things while this is running - >>> out = thread.output() # out is 4 + >>> out = thread.output() + >>> out + 6 """ def __init__( diff --git a/src/qcodes/validators/validators.py b/src/qcodes/validators/validators.py index 37beb98fbf8..fddaa939e50 100644 --- a/src/qcodes/validators/validators.py +++ b/src/qcodes/validators/validators.py @@ -710,16 +710,19 @@ class MultiType(Validator[Any]): 1. To allow numbers as well as "off": >>> MultiType(Numbers(), Enum("off")) + or: >>> MultiType(Numbers(), Enum("off"), combiner='OR') + 2. To require values that are divisible by 0.001 while >=0.002 and <=50000.0 >>> MultiType(PermissiveMultiples(divisor=1e-3), - >>> Numbers(min_value=2e-3, max_value=5e4), - >>> combiner='AND') + ... Numbers(min_value=2e-3, max_value=5e4), + ... combiner='AND') + Raises: TypeError: If no validators provided. Or if any of the provided @@ -794,6 +797,7 @@ class MultiTypeOr(MultiType): To allow numbers as well as "off": >>> MultiTypeOr(Numbers(), Enum("off")) + Raises: TypeError: If no validators provided. Or if any of the provided @@ -821,9 +825,9 @@ class MultiTypeAnd(MultiType): Example: To require values that are divisible by 0.001 while >=0.002 and <=50000.0 - >>> MultiType(PermissiveMultiples(divisor=1e-3), - >>> Numbers(min_value=2e-3, max_value=5e4), - >>> combiner='AND') + >>> MultiTypeAnd(PermissiveMultiples(divisor=1e-3), + ... Numbers(min_value=2e-3, max_value=5e4)) + Raises: TypeError: If no validators provided. Or if any of the provided diff --git a/tests/test_config.py b/tests/test_config.py index cd877fc06e2..c72e6dfb196 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -9,6 +9,7 @@ import qcodes from qcodes.configuration import Config +from qcodes.configuration.config import DotDict VALID_JSON = "{}" ENV_KEY = "/dev/random" @@ -275,3 +276,17 @@ def test_add_and_describe() -> None: ) assert desc == expected_desc + + +def test_dotdict_missing_key_raises_attribute_error() -> None: + """ + Test that DotDict raises AttributeError (not KeyError) for + missing attributes. This is required for correct behavior of + hasattr() and inspect.unwrap() among other things. + """ + d = DotDict({"a": 1}) + with pytest.raises(AttributeError, match="nonexistent"): + d.nonexistent + assert not hasattr(d, "__wrapped__") + assert hasattr(d, "a") + assert d.a == 1