From 8c3c8134a927c27c1436c10cd60fedde02a6a058 Mon Sep 17 00:00:00 2001 From: Mikhail Astafev Date: Fri, 20 Mar 2026 12:47:38 +0100 Subject: [PATCH 1/2] Drop Python 3.11 from CI matrix QCoDeS already requires Python >=3.12. This PR updates the CI configuration to match: - Updated pytest.yaml: removed 3.11 from matrix, min-version test now uses 3.12 - Updated docs.yaml: removed 3.11, gh-pages deploy uses 3.12 - Updated upload_to_pypi.yaml: build uses 3.12 - Removed obsolete pyvisa-sim deprecation warning filter (was only needed for 3.11) - Bumped minimum dependency versions: numpy>=1.26.0, pandas>=2.2.3 - Applied ruff safe pyupgrade fixes Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/docs.yaml | 6 ++---- .github/workflows/pytest.yaml | 8 ++------ .github/workflows/upload_to_pypi.yaml | 2 +- ...rameterWithSetpoints-Example-with-Dual-Setpoints.ipynb | 2 +- .../writing_drivers/Creating-Instrument-Drivers.ipynb | 4 +--- .../Creating-Simulated-PyVISA-Instruments.ipynb | 2 +- pyproject.toml | 8 +++----- src/qcodes/dataset/dond/do_0d.py | 2 +- src/qcodes/dataset/dond/do_1d.py | 2 +- src/qcodes/instrument/channel.py | 4 +--- src/qcodes/instrument/instrument.py | 4 +--- src/qcodes/instrument/ip.py | 3 +-- src/qcodes/instrument/visa.py | 4 +--- src/qcodes/instrument_drivers/AimTTi/_AimTTi_PL_P.py | 2 +- src/qcodes/instrument_drivers/AlazarTech/ATS.py | 3 +-- src/qcodes/instrument_drivers/CopperMountain/_M5065.py | 2 +- src/qcodes/instrument_drivers/CopperMountain/_M5180.py | 2 +- src/qcodes/instrument_drivers/CopperMountain/_M5xxx.py | 2 +- src/qcodes/instrument_drivers/Galil/dmc_41x3.py | 2 +- src/qcodes/instrument_drivers/HP/HP_8133A.py | 2 +- src/qcodes/instrument_drivers/HP/HP_83650A.py | 2 +- src/qcodes/instrument_drivers/HP/HP_8753D.py | 2 +- src/qcodes/instrument_drivers/Harvard/Decadac.py | 2 +- src/qcodes/instrument_drivers/Keithley/Keithley_2000.py | 3 +-- src/qcodes/instrument_drivers/Keithley/Keithley_2400.py | 2 +- src/qcodes/instrument_drivers/Keithley/Keithley_2450.py | 4 ++-- src/qcodes/instrument_drivers/Keithley/Keithley_3706A.py | 2 +- src/qcodes/instrument_drivers/Keithley/Keithley_6500.py | 3 +-- src/qcodes/instrument_drivers/Keithley/Keithley_7510.py | 3 +-- src/qcodes/instrument_drivers/Keithley/Keithley_s46.py | 2 +- src/qcodes/instrument_drivers/Keithley/_Keithley_2600.py | 2 +- src/qcodes/instrument_drivers/Keysight/Infiniium.py | 3 +-- .../instrument_drivers/Keysight/KeysightAgilent_33XXX.py | 2 +- .../Keysight/Keysight_34410A_submodules.py | 2 +- .../Keysight/Keysight_34411A_submodules.py | 2 +- .../Keysight/Keysight_34460A_submodules.py | 2 +- .../Keysight/Keysight_34461A_submodules.py | 2 +- .../Keysight/Keysight_34465A_submodules.py | 2 +- .../Keysight/Keysight_34470A_submodules.py | 2 +- src/qcodes/instrument_drivers/Keysight/Keysight_B2962A.py | 2 +- src/qcodes/instrument_drivers/Keysight/Keysight_N5183B.py | 2 +- src/qcodes/instrument_drivers/Keysight/Keysight_N5222B.py | 2 +- src/qcodes/instrument_drivers/Keysight/Keysight_N5230C.py | 2 +- src/qcodes/instrument_drivers/Keysight/Keysight_N5245A.py | 2 +- src/qcodes/instrument_drivers/Keysight/Keysight_N6705B.py | 2 +- src/qcodes/instrument_drivers/Keysight/Keysight_N9030B.py | 3 +-- src/qcodes/instrument_drivers/Keysight/Keysight_P5002B.py | 2 +- src/qcodes/instrument_drivers/Keysight/Keysight_P5004B.py | 2 +- src/qcodes/instrument_drivers/Keysight/Keysight_P9374A.py | 2 +- src/qcodes/instrument_drivers/Keysight/KtM960x.py | 2 +- src/qcodes/instrument_drivers/Keysight/KtMAwg.py | 2 +- src/qcodes/instrument_drivers/Keysight/N51x1.py | 2 +- src/qcodes/instrument_drivers/Keysight/N52xx.py | 3 +-- .../instrument_drivers/Keysight/_Keysight_N5232B.py | 2 +- src/qcodes/instrument_drivers/Keysight/keysight_34934a.py | 3 +-- src/qcodes/instrument_drivers/Keysight/keysight_34980a.py | 4 +--- .../Keysight/keysight_34980a_submodules.py | 2 +- src/qcodes/instrument_drivers/Keysight/keysight_b220x.py | 4 +--- src/qcodes/instrument_drivers/Keysight/keysight_e4980a.py | 3 +-- .../Keysight/keysightb1500/KeysightB1500_base.py | 3 +-- .../Keysight/keysightb1500/KeysightB1500_module.py | 4 ++-- .../Keysight/keysightb1500/KeysightB1517A.py | 4 ++-- .../Keysight/keysightb1500/KeysightB1520A.py | 2 +- .../Keysight/keysightb1500/KeysightB1530A.py | 2 +- .../Keysight/private/Keysight_344xxA_submodules.py | 3 +-- .../instrument_drivers/Lakeshore/Lakeshore_model_325.py | 3 +-- .../instrument_drivers/Lakeshore/Lakeshore_model_336.py | 2 +- .../instrument_drivers/Lakeshore/Lakeshore_model_372.py | 2 +- .../instrument_drivers/Lakeshore/_lakeshore_model_335.py | 2 +- src/qcodes/instrument_drivers/Lakeshore/lakeshore_base.py | 3 +-- src/qcodes/instrument_drivers/Minicircuits/Base_SPDT.py | 2 +- src/qcodes/instrument_drivers/Minicircuits/USBHIDMixin.py | 2 +- .../Minicircuits/_minicircuits_rc_sp4t.py | 2 +- .../Minicircuits/_minicircuits_rc_spdt.py | 2 +- .../Minicircuits/_minicircuits_rudat_13g_90.py | 2 +- .../Minicircuits/_minicircuits_usb_spdt.py | 2 +- src/qcodes/instrument_drivers/QDev/QDac_channels.py | 3 +-- .../QuantumDesign/DynaCoolPPMS/DynaCool.py | 3 +-- src/qcodes/instrument_drivers/agilent/Agilent_34401A.py | 2 +- src/qcodes/instrument_drivers/agilent/Agilent_34410A.py | 2 +- src/qcodes/instrument_drivers/agilent/Agilent_34411A.py | 2 +- src/qcodes/instrument_drivers/agilent/Agilent_E8257D.py | 2 +- src/qcodes/instrument_drivers/agilent/Agilent_E8267C.py | 2 +- src/qcodes/instrument_drivers/agilent/_Agilent_344xxA.py | 2 +- .../instrument_drivers/american_magnetics/AMI430_visa.py | 2 +- src/qcodes/instrument_drivers/basel/BaselSP983.py | 2 +- src/qcodes/instrument_drivers/basel/BaselSP983a.py | 2 +- src/qcodes/instrument_drivers/cryomagnetics/_TM620.py | 2 +- .../instrument_drivers/cryomagnetics/_cryomagnetics4g.py | 2 +- src/qcodes/instrument_drivers/ithaco/Ithaco_1211.py | 2 +- .../instrument_drivers/mock_instruments/__init__.py | 3 +-- src/qcodes/instrument_drivers/oxford/MercuryiPS_VISA.py | 3 +-- src/qcodes/instrument_drivers/oxford/triton.py | 2 +- src/qcodes/instrument_drivers/rigol/Rigol_DG1062.py | 2 +- src/qcodes/instrument_drivers/rigol/Rigol_DG4000.py | 2 +- src/qcodes/instrument_drivers/rigol/Rigol_DP821.py | 2 +- src/qcodes/instrument_drivers/rigol/Rigol_DP831.py | 2 +- src/qcodes/instrument_drivers/rigol/Rigol_DP832.py | 2 +- src/qcodes/instrument_drivers/rigol/Rigol_DS1074Z.py | 2 +- src/qcodes/instrument_drivers/rigol/Rigol_DS4000.py | 2 +- src/qcodes/instrument_drivers/rigol/private/DP8xx.py | 3 +-- src/qcodes/instrument_drivers/rohde_schwarz/RTO1000.py | 2 +- src/qcodes/instrument_drivers/rohde_schwarz/SGS100A.py | 2 +- src/qcodes/instrument_drivers/rohde_schwarz/ZNB.py | 2 +- .../signal_hound/SignalHound_USB_SA124B.py | 2 +- src/qcodes/instrument_drivers/stahl/stahl.py | 2 +- src/qcodes/instrument_drivers/stanford_research/SG384.py | 2 +- src/qcodes/instrument_drivers/stanford_research/SR560.py | 2 +- src/qcodes/instrument_drivers/stanford_research/SR830.py | 3 +-- src/qcodes/instrument_drivers/stanford_research/SR860.py | 2 +- src/qcodes/instrument_drivers/stanford_research/SR865.py | 2 +- src/qcodes/instrument_drivers/stanford_research/SR865A.py | 2 +- src/qcodes/instrument_drivers/stanford_research/SR86x.py | 3 +-- src/qcodes/instrument_drivers/tektronix/AWG5014.py | 3 +-- src/qcodes/instrument_drivers/tektronix/AWG5208.py | 2 +- src/qcodes/instrument_drivers/tektronix/AWG70000A.py | 3 +-- src/qcodes/instrument_drivers/tektronix/AWG70002A.py | 2 +- src/qcodes/instrument_drivers/tektronix/DPO7200xx.py | 3 +-- src/qcodes/instrument_drivers/tektronix/TPS2012.py | 4 ++-- .../instrument_drivers/tektronix/Tektronix_70001A.py | 2 +- .../instrument_drivers/tektronix/Tektronix_70001B.py | 2 +- .../instrument_drivers/tektronix/Tektronix_70002B.py | 2 +- src/qcodes/instrument_drivers/weinschel/Weinschel_8320.py | 2 +- src/qcodes/instrument_drivers/yokogawa/GS200.py | 2 +- src/qcodes/instrument_drivers/yokogawa/Yokogawa_GS200.py | 4 +--- tests/test_channels.py | 3 ++- 126 files changed, 135 insertions(+), 180 deletions(-) diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml index 1c6570733619..49c360e6c42c 100644 --- a/.github/workflows/docs.yaml +++ b/.github/workflows/docs.yaml @@ -29,10 +29,8 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest, windows-latest] - python-version: ["3.11", "3.12", "3.13", "3.14"] + python-version: ["3.12", "3.13", "3.14"] exclude: - - os: windows-latest - python-version: 3.11 - os: windows-latest python-version: 3.13 - os: windows-latest @@ -122,7 +120,7 @@ jobs: - name: Download artifact uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: - name: docs_3.11_ubuntu-latest + name: docs_3.12_ubuntu-latest path: build_docs - name: Deploy to gh pages diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml index bacf81d1c720..919c2dc5fd8a 100644 --- a/.github/workflows/pytest.yaml +++ b/.github/workflows/pytest.yaml @@ -30,17 +30,13 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest, windows-latest] - python-version: ["3.11", "3.12", "3.13", "3.14"] + python-version: ["3.12", "3.13", "3.14"] min-version: [false] include: - os: ubuntu-latest - python-version: "3.11" + python-version: "3.12" min-version: true exclude: - - os: ubuntu-latest - python-version: "3.11" - - os: windows-latest - python-version: "3.11" - os: windows-latest python-version: "3.13" - os: windows-latest diff --git a/.github/workflows/upload_to_pypi.yaml b/.github/workflows/upload_to_pypi.yaml index 7dbceac2eb69..5dace54a64b0 100644 --- a/.github/workflows/upload_to_pypi.yaml +++ b/.github/workflows/upload_to_pypi.yaml @@ -25,7 +25,7 @@ jobs: - name: Set up Python uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: - python-version: '3.11' + python-version: '3.12' - name: Install build deps run: pip install --upgrade pip setuptools wheel build - name: Build diff --git a/docs/examples/writing_drivers/A-ParameterWithSetpoints-Example-with-Dual-Setpoints.ipynb b/docs/examples/writing_drivers/A-ParameterWithSetpoints-Example-with-Dual-Setpoints.ipynb index 01d616181a51..038b144185a4 100644 --- a/docs/examples/writing_drivers/A-ParameterWithSetpoints-Example-with-Dual-Setpoints.ipynb +++ b/docs/examples/writing_drivers/A-ParameterWithSetpoints-Example-with-Dual-Setpoints.ipynb @@ -21,7 +21,7 @@ "from typing import TYPE_CHECKING\n", "\n", "if TYPE_CHECKING:\n", - " from typing_extensions import Unpack\n", + " from typing import Unpack\n", "\n", "import numpy as np\n", "import numpy.typing as npt\n", diff --git a/docs/examples/writing_drivers/Creating-Instrument-Drivers.ipynb b/docs/examples/writing_drivers/Creating-Instrument-Drivers.ipynb index ed0e65d11955..62d7174e64c8 100644 --- a/docs/examples/writing_drivers/Creating-Instrument-Drivers.ipynb +++ b/docs/examples/writing_drivers/Creating-Instrument-Drivers.ipynb @@ -21,9 +21,7 @@ "from typing import TYPE_CHECKING, Any\n", "\n", "if TYPE_CHECKING:\n", - " from typing_extensions import (\n", - " Unpack, # can be imported from typing if python >= 3.12\n", - " )\n", + " from typing import Unpack\n", "\n", "import numpy as np\n", "\n", diff --git a/docs/examples/writing_drivers/Creating-Simulated-PyVISA-Instruments.ipynb b/docs/examples/writing_drivers/Creating-Simulated-PyVISA-Instruments.ipynb index 69cbec180a4c..03a841739330 100644 --- a/docs/examples/writing_drivers/Creating-Simulated-PyVISA-Instruments.ipynb +++ b/docs/examples/writing_drivers/Creating-Simulated-PyVISA-Instruments.ipynb @@ -74,7 +74,7 @@ "from qcodes.instrument.visa import VisaInstrument, VisaInstrumentKWArgs\n", "\n", "if TYPE_CHECKING:\n", - " from typing_extensions import Unpack\n", + " from typing import Unpack\n", "\n", "\n", "class Weinschel8320(VisaInstrument):\n", diff --git a/pyproject.toml b/pyproject.toml index e32615e6e03b..6b6f6f936fde 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,7 +13,6 @@ classifiers = [ "Development Status :: 3 - Alpha", "Intended Audience :: Science/Research", "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", @@ -21,7 +20,7 @@ classifiers = [ ] license = "MIT" readme = "README.rst" -requires-python = ">=3.11" +requires-python = ">=3.12" dependencies = [ "broadbean>=0.11.0", "h5netcdf>=0.14.1", @@ -31,9 +30,9 @@ dependencies = [ "jsonschema>=4.9.0", "matplotlib>=3.6.0", "networkx>=3.1", - "numpy>=1.22.4", + "numpy>=1.26.0", "packaging>=20.0", - "pandas>=1.5.0", + "pandas>=2.2.3", "pyarrow>=11.0.0", # will become a requirement of pandas. Installing explicitly silences a warning "pyvisa>=1.11.0, <1.17.0", "ruamel.yaml>=0.16.0,!=0.16.6", @@ -218,7 +217,6 @@ markers = "serial" # and error on all other warnings filterwarnings = [ 'error', - 'ignore:open_binary is deprecated:DeprecationWarning', # pyvisa-sim deprecated in 3.11 un-deprecated in 3.12. Drop filter once we drop support for 3.11 'ignore:unclosed database in:ResourceWarning', # internal should be fixed 'ignore:unclosed\ str: diff --git a/src/qcodes/instrument_drivers/tektronix/TPS2012.py b/src/qcodes/instrument_drivers/tektronix/TPS2012.py index 3b373dc3d1d6..9de61876de19 100644 --- a/src/qcodes/instrument_drivers/tektronix/TPS2012.py +++ b/src/qcodes/instrument_drivers/tektronix/TPS2012.py @@ -1,12 +1,12 @@ import binascii import logging from functools import partial -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING, Any, Unpack import numpy as np import numpy.typing as npt from pyvisa.errors import VisaIOError -from typing_extensions import TypedDict, Unpack +from typing_extensions import TypedDict from qcodes import validators as vals from qcodes.instrument import ( diff --git a/src/qcodes/instrument_drivers/tektronix/Tektronix_70001A.py b/src/qcodes/instrument_drivers/tektronix/Tektronix_70001A.py index 98171d9119e5..967909786862 100644 --- a/src/qcodes/instrument_drivers/tektronix/Tektronix_70001A.py +++ b/src/qcodes/instrument_drivers/tektronix/Tektronix_70001A.py @@ -3,7 +3,7 @@ from .AWG70000A import TektronixAWG70000Base if TYPE_CHECKING: - from typing_extensions import Unpack + from typing import Unpack from qcodes.instrument import VisaInstrumentKWArgs diff --git a/src/qcodes/instrument_drivers/tektronix/Tektronix_70001B.py b/src/qcodes/instrument_drivers/tektronix/Tektronix_70001B.py index f229f516f6a5..571d68876741 100644 --- a/src/qcodes/instrument_drivers/tektronix/Tektronix_70001B.py +++ b/src/qcodes/instrument_drivers/tektronix/Tektronix_70001B.py @@ -3,7 +3,7 @@ from .AWG70000A import TektronixAWG70000Base if TYPE_CHECKING: - from typing_extensions import Unpack + from typing import Unpack from qcodes.instrument import VisaInstrumentKWArgs diff --git a/src/qcodes/instrument_drivers/tektronix/Tektronix_70002B.py b/src/qcodes/instrument_drivers/tektronix/Tektronix_70002B.py index 3c9483ecc8c6..db13806a12f8 100644 --- a/src/qcodes/instrument_drivers/tektronix/Tektronix_70002B.py +++ b/src/qcodes/instrument_drivers/tektronix/Tektronix_70002B.py @@ -3,7 +3,7 @@ from .AWG70000A import TektronixAWG70000Base if TYPE_CHECKING: - from typing_extensions import Unpack + from typing import Unpack from qcodes.instrument import VisaInstrumentKWArgs diff --git a/src/qcodes/instrument_drivers/weinschel/Weinschel_8320.py b/src/qcodes/instrument_drivers/weinschel/Weinschel_8320.py index 47f5b8de7c39..f06b8e1c2c57 100644 --- a/src/qcodes/instrument_drivers/weinschel/Weinschel_8320.py +++ b/src/qcodes/instrument_drivers/weinschel/Weinschel_8320.py @@ -9,7 +9,7 @@ from qcodes.utils.deprecate import QCoDeSDeprecationWarning if TYPE_CHECKING: - from typing_extensions import Unpack + from typing import Unpack class Weinschel8320(VisaInstrument): diff --git a/src/qcodes/instrument_drivers/yokogawa/GS200.py b/src/qcodes/instrument_drivers/yokogawa/GS200.py index 908ab1268b9f..1fcc3731d5ca 100644 --- a/src/qcodes/instrument_drivers/yokogawa/GS200.py +++ b/src/qcodes/instrument_drivers/yokogawa/GS200.py @@ -19,7 +19,7 @@ from qcodes.validators import Bool, Enum, Ints, Numbers if TYPE_CHECKING: - from typing_extensions import Unpack + from typing import Unpack from qcodes.parameters import Parameter diff --git a/src/qcodes/instrument_drivers/yokogawa/Yokogawa_GS200.py b/src/qcodes/instrument_drivers/yokogawa/Yokogawa_GS200.py index dd82635aa478..94dbf4c7b556 100644 --- a/src/qcodes/instrument_drivers/yokogawa/Yokogawa_GS200.py +++ b/src/qcodes/instrument_drivers/yokogawa/Yokogawa_GS200.py @@ -11,9 +11,7 @@ from qcodes.validators import Bool, Enum, Ints, Numbers if TYPE_CHECKING: - from typing import assert_never - - from typing_extensions import Unpack + from typing import Unpack, assert_never from qcodes.parameters import Parameter diff --git a/tests/test_channels.py b/tests/test_channels.py index 9c5cb18f0ae7..b15567a5ec8c 100644 --- a/tests/test_channels.py +++ b/tests/test_channels.py @@ -20,8 +20,9 @@ ) if TYPE_CHECKING: + from typing import Unpack + import pytest_mock - from typing_extensions import Unpack from qcodes.instrument.instrument_base import InstrumentBaseKWArgs From b8ff3fb64646a4a206f0882a796af23f6eb03266 Mon Sep 17 00:00:00 2001 From: Mikhail Astafev Date: Fri, 20 Mar 2026 22:50:34 +0100 Subject: [PATCH 2/2] Apply ruff pyupgrade (UP) fixes for Python 3.12+ Applied ruff unsafe pyupgrade fixes: - UP040: TypeAlias -> PEP 695 type statements - UP046: Generic subclass -> PEP 695 type parameters (where safe) - Added noqa: UP046 for 13 complex multi-inheritance generic classes in the parameter system that cannot be safely auto-converted Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/qcodes/dataset/data_set_cache.py | 2 +- src/qcodes/dataset/data_set_protocol.py | 16 +++++++--------- src/qcodes/dataset/dond/sweeps.py | 4 ++-- src/qcodes/dataset/measurements.py | 4 ++-- src/qcodes/dataset/threading.py | 4 ++-- src/qcodes/extensions/_driver_test_case.py | 2 +- src/qcodes/extensions/infer.py | 8 ++++---- src/qcodes/instrument/instrument.py | 2 +- src/qcodes/instrument_drivers/AlazarTech/ATS.py | 4 ++-- .../instrument_drivers/AlazarTech/ats_api.py | 4 ++-- .../instrument_drivers/AlazarTech/dll_wrapper.py | 2 +- .../instrument_drivers/AlazarTech/utils.py | 2 +- .../Keysight/Keysight_N9030B.py | 2 +- .../instrument_drivers/Keysight/KtM960x.py | 2 +- .../Keysight/keysightb1500/KeysightB1500_base.py | 2 +- .../Keysight/keysightb1500/KeysightB1517A.py | 6 +++--- .../Keysight/keysightb1500/message_builder.py | 2 +- .../instrument_drivers/Minicircuits/Base_SPDT.py | 4 +++- .../instrument_drivers/tektronix/DPO7200xx.py | 2 +- src/qcodes/parameters/array_parameter.py | 2 +- src/qcodes/parameters/command.py | 2 +- src/qcodes/parameters/delegate_parameter.py | 2 +- .../multi_channel_instrument_parameter.py | 4 +++- src/qcodes/parameters/multi_parameter.py | 2 +- src/qcodes/parameters/parameter.py | 2 +- src/qcodes/parameters/parameter_base.py | 2 +- .../parameters/parameter_with_setpoints.py | 2 +- src/qcodes/parameters/val_mapping.py | 2 +- src/qcodes/utils/deep_update_utils.py | 2 +- src/qcodes/utils/threading_utils.py | 2 +- src/qcodes/utils/types.py | 2 +- src/qcodes/validators/validators.py | 2 +- tests/common.py | 2 +- 33 files changed, 53 insertions(+), 51 deletions(-) diff --git a/src/qcodes/dataset/data_set_cache.py b/src/qcodes/dataset/data_set_cache.py index 210baac1d18e..6b99314a2e52 100644 --- a/src/qcodes/dataset/data_set_cache.py +++ b/src/qcodes/dataset/data_set_cache.py @@ -39,7 +39,7 @@ log = logging.getLogger(__name__) -class DataSetCache(Generic[DatasetType_co]): +class DataSetCache[DatasetType_co: "DataSetProtocol"]: """ The DataSetCache contains a in memory representation of the data in this dataset as well a a method to progressively read data diff --git a/src/qcodes/dataset/data_set_protocol.py b/src/qcodes/dataset/data_set_protocol.py index 1098f472e951..d1ba5ecc9b52 100644 --- a/src/qcodes/dataset/data_set_protocol.py +++ b/src/qcodes/dataset/data_set_protocol.py @@ -51,17 +51,15 @@ # twice here convert to set to ensure no duplication. _EXPORT_CALLBACKS = set(entry_points(group="qcodes.dataset.on_export")) -ScalarResTypes: TypeAlias = ( - str | complex | np.integer | np.floating | np.complexfloating -) -ValuesType: TypeAlias = ( +type ScalarResTypes = str | complex | np.integer | np.floating | np.complexfloating +type ValuesType = ( ScalarResTypes | npt.NDArray | Sequence[ScalarResTypes] | Sequence[Sequence[ScalarResTypes]] ) -ResType: TypeAlias = "tuple[ParameterBase | str, ValuesType]" -SetpointsType: TypeAlias = "Sequence[str | ParameterBase]" +type ResType = "tuple[ParameterBase | str, ValuesType]" +type SetpointsType = "Sequence[str | ParameterBase]" # deprecated alias left for backwards compatibility array_like_types = (tuple, list, npt.NDArray) @@ -71,12 +69,12 @@ setpoints_type: TypeAlias = SetpointsType # noqa PYI042 -SPECS: TypeAlias = list[ParamSpec] +type SPECS = list[ParamSpec] # Transition period type: SpecsOrInterDeps. We will allow both as input to # the DataSet constructor for a while, then deprecate SPECS and finally remove # the ParamSpec class -SpecsOrInterDeps: TypeAlias = SPECS | InterDependencies_ -ParameterData: TypeAlias = dict[str, dict[str, npt.NDArray]] +type SpecsOrInterDeps = SPECS | InterDependencies_ +type ParameterData = dict[str, dict[str, npt.NDArray]] LOG = logging.getLogger(__name__) diff --git a/src/qcodes/dataset/dond/sweeps.py b/src/qcodes/dataset/dond/sweeps.py index efef85f1d973..cea4578633e7 100644 --- a/src/qcodes/dataset/dond/sweeps.py +++ b/src/qcodes/dataset/dond/sweeps.py @@ -15,7 +15,7 @@ T = TypeVar("T", bound=np.generic) -class AbstractSweep(ABC, Generic[T]): +class AbstractSweep[T: np.generic](ABC): """ Abstract sweep class that defines an interface for concrete sweep classes. """ @@ -195,7 +195,7 @@ def get_after_set(self) -> bool: return self._get_after_set -class ArraySweep(AbstractSweep, Generic[T]): +class ArraySweep[T: np.generic](AbstractSweep): """ Sweep the values of a given array. diff --git a/src/qcodes/dataset/measurements.py b/src/qcodes/dataset/measurements.py index cb11a9246af3..c61e347893f2 100644 --- a/src/qcodes/dataset/measurements.py +++ b/src/qcodes/dataset/measurements.py @@ -74,8 +74,8 @@ Callable[..., Any], MutableSequence[Any] | MutableMapping[Any, Any] ] -ParameterResultType: TypeAlias = tuple[ParameterBase, ValuesType] -DatasetResultDict: TypeAlias = dict[ParamSpecBase, npt.NDArray] +type ParameterResultType = tuple[ParameterBase, ValuesType] +type DatasetResultDict = dict[ParamSpecBase, npt.NDArray] class ParameterTypeError(Exception): diff --git a/src/qcodes/dataset/threading.py b/src/qcodes/dataset/threading.py index 34a9d9be8cda..a6c52d9acd07 100644 --- a/src/qcodes/dataset/threading.py +++ b/src/qcodes/dataset/threading.py @@ -21,8 +21,8 @@ from qcodes.dataset.data_set_protocol import ValuesType from qcodes.parameters import ParamDataType, ParameterBase -ParamMeasT: TypeAlias = "ParameterBase | Callable[[], None]" -OutType: TypeAlias = "list[tuple[ParameterBase, ValuesType]]" +type ParamMeasT = "ParameterBase | Callable[[], None]" +type OutType = "list[tuple[ParameterBase, ValuesType]]" T = TypeVar("T") diff --git a/src/qcodes/extensions/_driver_test_case.py b/src/qcodes/extensions/_driver_test_case.py index 900ff16065fc..bf229daf5d94 100644 --- a/src/qcodes/extensions/_driver_test_case.py +++ b/src/qcodes/extensions/_driver_test_case.py @@ -28,7 +28,7 @@ T = TypeVar("T", bound="Instrument") -class DriverTestCase(unittest.TestCase, Generic[T]): +class DriverTestCase[T: "Instrument"](unittest.TestCase): # override this in a subclass driver: type[T] | None = None instrument: T diff --git a/src/qcodes/extensions/infer.py b/src/qcodes/extensions/infer.py index d22b4060fd6c..cc5596ac7bbd 100644 --- a/src/qcodes/extensions/infer.py +++ b/src/qcodes/extensions/infer.py @@ -225,7 +225,7 @@ def _merge_user_and_class_attrs( return set.union(set(alt_source_attrs), set(InferAttrs.known_attrs())) -def get_chain_links_of_type( +def get_chain_links_of_type[C: ParameterBase]( link_param_type: type[C] | tuple[type[C], ...], parameter: Parameter ) -> tuple[C, ...]: """Gets all parameters in a chain of linked parameters that match a given type""" @@ -237,7 +237,7 @@ def get_chain_links_of_type( return tuple(chain_links) -def get_sole_chain_link_of_type( +def get_sole_chain_link_of_type[C: ParameterBase]( link_param_type: type[C] | tuple[type[C], ...], parameter: Parameter ) -> C: """Gets the one parameter in a chain of linked parameters that matches a given type""" @@ -256,7 +256,7 @@ def get_sole_chain_link_of_type( return chain_links[0] -def get_parent_instruments_from_chain_of_type( +def get_parent_instruments_from_chain_of_type[TInstrument: InstrumentBase]( instrument_type: type[TInstrument] | tuple[type[TInstrument], ...], parameter: Parameter, ) -> tuple[TInstrument, ...]: @@ -272,7 +272,7 @@ def get_parent_instruments_from_chain_of_type( ) -def get_sole_parent_instrument_from_chain_of_type( +def get_sole_parent_instrument_from_chain_of_type[TInstrument: InstrumentBase]( instrument_type: type[TInstrument] | tuple[type[TInstrument], ...], parameter: Parameter, ) -> TInstrument: diff --git a/src/qcodes/instrument/instrument.py b/src/qcodes/instrument/instrument.py index f3ed65f62ccd..e4cc68cb2316 100644 --- a/src/qcodes/instrument/instrument.py +++ b/src/qcodes/instrument/instrument.py @@ -459,7 +459,7 @@ def ask_raw(self, cmd: str) -> str: ) -def find_or_create_instrument( +def find_or_create_instrument[T: "Instrument"]( instrument_class: type[T], name: str, *args: Any, diff --git a/src/qcodes/instrument_drivers/AlazarTech/ATS.py b/src/qcodes/instrument_drivers/AlazarTech/ATS.py index 87d4d822730c..54459baec9bd 100644 --- a/src/qcodes/instrument_drivers/AlazarTech/ATS.py +++ b/src/qcodes/instrument_drivers/AlazarTech/ATS.py @@ -827,7 +827,7 @@ def __del__(self) -> None: ) -class AcquisitionInterface(Generic[OutputType]): +class AcquisitionInterface[OutputType]: """ This class represents all choices that the end-user has to make regarding the data-acquisition. this class should be subclassed to program these @@ -903,7 +903,7 @@ def buffer_done_callback(self, buffers_completed: int) -> None: pass -class AcquisitionController(Instrument, AcquisitionInterface[Any], Generic[OutputType]): +class AcquisitionController[OutputType](Instrument, AcquisitionInterface[Any]): """ Compatibility class. The methods of :class:`AcquisitionController` have been extracted. This class is the base class fro AcquisitionInterfaces diff --git a/src/qcodes/instrument_drivers/AlazarTech/ats_api.py b/src/qcodes/instrument_drivers/AlazarTech/ats_api.py index ca0f814256d5..6cf7d45418fa 100644 --- a/src/qcodes/instrument_drivers/AlazarTech/ats_api.py +++ b/src/qcodes/instrument_drivers/AlazarTech/ats_api.py @@ -30,9 +30,9 @@ POINTER_c_long = Any -IntOrParam: TypeAlias = "int | Parameter" +type IntOrParam = "int | Parameter" # deprecated alias for backwards compatibility -int_or_param: TypeAlias = IntOrParam # noqa: PYI042 +type int_or_param = IntOrParam # noqa: PYI042 class AlazarATSAPI(WrappedDll): diff --git a/src/qcodes/instrument_drivers/AlazarTech/dll_wrapper.py b/src/qcodes/instrument_drivers/AlazarTech/dll_wrapper.py index 15ef43685039..97264d59a317 100644 --- a/src/qcodes/instrument_drivers/AlazarTech/dll_wrapper.py +++ b/src/qcodes/instrument_drivers/AlazarTech/dll_wrapper.py @@ -43,7 +43,7 @@ def _api_call_task( return retval -def _normalize_params(*args: T) -> list[T]: +def _normalize_params[T](*args: T) -> list[T]: args_out: list[T] = [] for arg in args: if isinstance(arg, ParameterBase): diff --git a/src/qcodes/instrument_drivers/AlazarTech/utils.py b/src/qcodes/instrument_drivers/AlazarTech/utils.py index 34904c0562e1..101c0e5e38a4 100644 --- a/src/qcodes/instrument_drivers/AlazarTech/utils.py +++ b/src/qcodes/instrument_drivers/AlazarTech/utils.py @@ -16,7 +16,7 @@ class TraceParameter( - Parameter[ParameterDataTypeVar, "AlazarTechATS"], Generic[ParameterDataTypeVar] + Parameter[ParameterDataTypeVar, "AlazarTechATS"], Generic[ParameterDataTypeVar] # noqa: UP046 ): """ A parameter that keeps track of if its value has been synced to diff --git a/src/qcodes/instrument_drivers/Keysight/Keysight_N9030B.py b/src/qcodes/instrument_drivers/Keysight/Keysight_N9030B.py index a6dd21a8a3f0..7127995b33f4 100644 --- a/src/qcodes/instrument_drivers/Keysight/Keysight_N9030B.py +++ b/src/qcodes/instrument_drivers/Keysight/Keysight_N9030B.py @@ -62,7 +62,7 @@ def get_raw(self) -> npt.NDArray[np.float64]: class Trace( - ParameterWithSetpoints[ParameterDataTypeVar, _T], Generic[ParameterDataTypeVar, _T] + ParameterWithSetpoints[ParameterDataTypeVar, _T], Generic[ParameterDataTypeVar, _T] # noqa: UP046 ): def __init__( self, diff --git a/src/qcodes/instrument_drivers/Keysight/KtM960x.py b/src/qcodes/instrument_drivers/Keysight/KtM960x.py index bdc9f2579540..1b576db854f6 100644 --- a/src/qcodes/instrument_drivers/Keysight/KtM960x.py +++ b/src/qcodes/instrument_drivers/Keysight/KtM960x.py @@ -22,7 +22,7 @@ class Measure( - MultiParameter[ParameterDataTypeVar, "KeysightM960x"], Generic[ParameterDataTypeVar] + MultiParameter[ParameterDataTypeVar, "KeysightM960x"], Generic[ParameterDataTypeVar] # noqa: UP046 ): def __init__(self, name: str, instrument: "KeysightM960x") -> None: super().__init__( diff --git a/src/qcodes/instrument_drivers/Keysight/keysightb1500/KeysightB1500_base.py b/src/qcodes/instrument_drivers/Keysight/keysightb1500/KeysightB1500_base.py index 1a91dc44cc78..2e5f0bff48ce 100644 --- a/src/qcodes/instrument_drivers/Keysight/keysightb1500/KeysightB1500_base.py +++ b/src/qcodes/instrument_drivers/Keysight/keysightb1500/KeysightB1500_base.py @@ -499,7 +499,7 @@ def enable_smu_filters( class IVSweepMeasurement( MultiParameter[ParameterDataTypeVar, KeysightB1500], StatusMixin, - Generic[ParameterDataTypeVar], + Generic[ParameterDataTypeVar], # noqa: UP046 ): """ IV sweep measurement outputs a list of measured current parameters diff --git a/src/qcodes/instrument_drivers/Keysight/keysightb1500/KeysightB1517A.py b/src/qcodes/instrument_drivers/Keysight/keysightb1500/KeysightB1517A.py index 77d6c25eb7c7..6ce17c3b9544 100644 --- a/src/qcodes/instrument_drivers/Keysight/keysightb1500/KeysightB1517A.py +++ b/src/qcodes/instrument_drivers/Keysight/keysightb1500/KeysightB1517A.py @@ -711,7 +711,7 @@ class IVSweeper(KeysightB1500IVSweeper): class _ParameterWithStatus( - Parameter[ParameterDataTypeVar, "KeysightB1517A"], Generic[ParameterDataTypeVar] + Parameter[ParameterDataTypeVar, "KeysightB1517A"], Generic[ParameterDataTypeVar] # noqa: UP046 ): def __init__(self, *args: Any, **kwargs: Any): super().__init__(*args, **kwargs) @@ -738,7 +738,7 @@ def snapshot_base( class _SpotMeasurementVoltageParameter( - _ParameterWithStatus[ParameterDataTypeVar], Generic[ParameterDataTypeVar] + _ParameterWithStatus[ParameterDataTypeVar], Generic[ParameterDataTypeVar] # noqa: UP046 ): def set_raw(self, value: ParamRawDataType) -> None: smu = self.instrument @@ -781,7 +781,7 @@ def get_raw(self) -> ParamRawDataType: class _SpotMeasurementCurrentParameter( - _ParameterWithStatus[ParameterDataTypeVar], Generic[ParameterDataTypeVar] + _ParameterWithStatus[ParameterDataTypeVar], Generic[ParameterDataTypeVar] # noqa: UP046 ): def set_raw(self, value: ParamRawDataType) -> None: smu = self.instrument diff --git a/src/qcodes/instrument_drivers/Keysight/keysightb1500/message_builder.py b/src/qcodes/instrument_drivers/Keysight/keysightb1500/message_builder.py index 5bf2cd0d07d5..3fe61103406a 100644 --- a/src/qcodes/instrument_drivers/Keysight/keysightb1500/message_builder.py +++ b/src/qcodes/instrument_drivers/Keysight/keysightb1500/message_builder.py @@ -28,7 +28,7 @@ def wrapper(*args: P.args, **kwargs: P.kwargs) -> "MessageBuilder": return wrapper -class CommandList(list[T], Generic[T]): +class CommandList[T](list[T]): def __init__(self) -> None: super().__init__() self.is_final = False diff --git a/src/qcodes/instrument_drivers/Minicircuits/Base_SPDT.py b/src/qcodes/instrument_drivers/Minicircuits/Base_SPDT.py index 8f00d6a9bd73..1d57868e2868 100644 --- a/src/qcodes/instrument_drivers/Minicircuits/Base_SPDT.py +++ b/src/qcodes/instrument_drivers/Minicircuits/Base_SPDT.py @@ -22,7 +22,9 @@ _TINSTR = TypeVar("_TINSTR", bound="MiniCircuitsSPDTBase") -class MiniCircuitsSPDTSwitchChannelBase(InstrumentChannel[_TINSTR], Generic[_TINSTR]): +class MiniCircuitsSPDTSwitchChannelBase[TINSTR: "MiniCircuitsSPDTBase"]( + InstrumentChannel[TINSTR] +): def __init__( self, parent: _TINSTR, diff --git a/src/qcodes/instrument_drivers/tektronix/DPO7200xx.py b/src/qcodes/instrument_drivers/tektronix/DPO7200xx.py index 4dfd9ae614cd..dfb90a05a753 100644 --- a/src/qcodes/instrument_drivers/tektronix/DPO7200xx.py +++ b/src/qcodes/instrument_drivers/tektronix/DPO7200xx.py @@ -962,7 +962,7 @@ def _trigger_type(self, value: str) -> None: class TektronixDPOMeasurementParameter( Parameter[ParameterDataTypeVar, "TektronixDPOMeasurement"], - Generic[ParameterDataTypeVar], + Generic[ParameterDataTypeVar], # noqa: UP046 ): """ A measurement parameter does not only return the instantaneous value diff --git a/src/qcodes/parameters/array_parameter.py b/src/qcodes/parameters/array_parameter.py index 34cca09fc812..695f1c76e00b 100644 --- a/src/qcodes/parameters/array_parameter.py +++ b/src/qcodes/parameters/array_parameter.py @@ -48,7 +48,7 @@ class ArrayParameter( ParameterBase[ParameterDataTypeVar, InstrumentTypeVar_co], - Generic[ParameterDataTypeVar, InstrumentTypeVar_co], + Generic[ParameterDataTypeVar, InstrumentTypeVar_co], # noqa: UP046 ): """ A gettable parameter that returns an array of values. diff --git a/src/qcodes/parameters/command.py b/src/qcodes/parameters/command.py index 5a2ddf5287d7..7250958ce156 100644 --- a/src/qcodes/parameters/command.py +++ b/src/qcodes/parameters/command.py @@ -16,7 +16,7 @@ class NoCommandError(Exception): ParsedOutput = TypeVar("ParsedOutput") -class Command(Generic[Output, ParsedOutput]): +class Command[Output, ParsedOutput]: """ Create a callable command from a string or function. diff --git a/src/qcodes/parameters/delegate_parameter.py b/src/qcodes/parameters/delegate_parameter.py index 0a409a6335bf..509133903b98 100644 --- a/src/qcodes/parameters/delegate_parameter.py +++ b/src/qcodes/parameters/delegate_parameter.py @@ -36,7 +36,7 @@ class DelegateParameter( Parameter[ParameterDataTypeVar, InstrumentTypeVar_co], - Generic[ParameterDataTypeVar, InstrumentTypeVar_co], + Generic[ParameterDataTypeVar, InstrumentTypeVar_co], # noqa: UP046 ): """ The :class:`.DelegateParameter` wraps a given `source` :class:`Parameter`. diff --git a/src/qcodes/parameters/multi_channel_instrument_parameter.py b/src/qcodes/parameters/multi_channel_instrument_parameter.py index b4a8f3af1d47..5382e16fd6ba 100644 --- a/src/qcodes/parameters/multi_channel_instrument_parameter.py +++ b/src/qcodes/parameters/multi_channel_instrument_parameter.py @@ -19,7 +19,9 @@ _LOG = logging.getLogger(__name__) -class MultiChannelInstrumentParameter(MultiParameter, Generic[InstrumentModuleType]): +class MultiChannelInstrumentParameter[InstrumentModuleType: "InstrumentModule"]( + MultiParameter +): """ Parameter to get or set multiple channels simultaneously. diff --git a/src/qcodes/parameters/multi_parameter.py b/src/qcodes/parameters/multi_parameter.py index 613995f5fe0e..2c48b7ccfde4 100644 --- a/src/qcodes/parameters/multi_parameter.py +++ b/src/qcodes/parameters/multi_parameter.py @@ -52,7 +52,7 @@ def _is_nested_sequence_or_none( class MultiParameter( ParameterBase[ParameterDataTypeVar, InstrumentTypeVar_co], - Generic[ParameterDataTypeVar, InstrumentTypeVar_co], + Generic[ParameterDataTypeVar, InstrumentTypeVar_co], # noqa: UP046 ): """ A gettable parameter that returns multiple values with separate names, diff --git a/src/qcodes/parameters/parameter.py b/src/qcodes/parameters/parameter.py index 12928f61c56f..3f59e804f34a 100644 --- a/src/qcodes/parameters/parameter.py +++ b/src/qcodes/parameters/parameter.py @@ -34,7 +34,7 @@ class Parameter( ParameterBase[ParameterDataTypeVar, InstrumentTypeVar_co], - Generic[ParameterDataTypeVar, InstrumentTypeVar_co], + Generic[ParameterDataTypeVar, InstrumentTypeVar_co], # noqa: UP046 ): """ A parameter represents a single degree of freedom. Most often, diff --git a/src/qcodes/parameters/parameter_base.py b/src/qcodes/parameters/parameter_base.py index 1b76f56fe38a..cc0f6e076806 100644 --- a/src/qcodes/parameters/parameter_base.py +++ b/src/qcodes/parameters/parameter_base.py @@ -1483,7 +1483,7 @@ def __call__(self) -> ParameterDataTypeVar: # Does not implement __hash__, not clear it needs to -class ParameterSet(MutableSet[P], Generic[P]): # noqa: PLW1641 +class ParameterSet[P: ParameterBase](MutableSet[P]): # noqa: PLW1641 """A set-like container that preserves the insertion order of its parameters. This class implements the common set interface methods while maintaining diff --git a/src/qcodes/parameters/parameter_with_setpoints.py b/src/qcodes/parameters/parameter_with_setpoints.py index 6bdf14e85b19..e751523703d3 100644 --- a/src/qcodes/parameters/parameter_with_setpoints.py +++ b/src/qcodes/parameters/parameter_with_setpoints.py @@ -27,7 +27,7 @@ class ParameterWithSetpoints( Parameter[ParameterDataTypeVar, InstrumentTypeVar_co], - Generic[ParameterDataTypeVar, InstrumentTypeVar_co], + Generic[ParameterDataTypeVar, InstrumentTypeVar_co], # noqa: UP046 ): """ A parameter that has associated setpoints. The setpoints is nothing diff --git a/src/qcodes/parameters/val_mapping.py b/src/qcodes/parameters/val_mapping.py index d8076179616e..f79a715e8760 100644 --- a/src/qcodes/parameters/val_mapping.py +++ b/src/qcodes/parameters/val_mapping.py @@ -6,7 +6,7 @@ T = TypeVar("T") -def create_on_off_val_mapping( +def create_on_off_val_mapping[T]( on_val: T | bool = True, off_val: T | bool = False ) -> OrderedDict[str | bool, T | bool]: """ diff --git a/src/qcodes/utils/deep_update_utils.py b/src/qcodes/utils/deep_update_utils.py index 6090293e2c44..107d9308fd13 100644 --- a/src/qcodes/utils/deep_update_utils.py +++ b/src/qcodes/utils/deep_update_utils.py @@ -7,7 +7,7 @@ L = TypeVar("L", bound=Hashable) -def deep_update( +def deep_update[K: Hashable, L: Hashable]( dest: MutableMapping[K, Any], update: Mapping[L, Any] ) -> MutableMapping[K | L, Any]: """ diff --git a/src/qcodes/utils/threading_utils.py b/src/qcodes/utils/threading_utils.py index 552c51e79a72..086e4e77c5c3 100644 --- a/src/qcodes/utils/threading_utils.py +++ b/src/qcodes/utils/threading_utils.py @@ -10,7 +10,7 @@ T = TypeVar("T") -class RespondingThread(threading.Thread, Generic[T]): +class RespondingThread[T](threading.Thread): """ Thread subclass for parallelizing execution. Behaves like a regular thread but returns a value from target, and propagates diff --git a/src/qcodes/utils/types.py b/src/qcodes/utils/types.py index 0f0fb0fd5b52..480187b6b3e7 100644 --- a/src/qcodes/utils/types.py +++ b/src/qcodes/utils/types.py @@ -85,7 +85,7 @@ concrete_complex_types = (*numpy_concrete_complex, complex) complex_types = (*numpy_concrete_complex, complex) -NumberType: TypeAlias = int | float | np.integer | np.floating +type NumberType = int | float | np.integer | np.floating """ Python or NumPy real number. """ diff --git a/src/qcodes/validators/validators.py b/src/qcodes/validators/validators.py index 37beb98fbf83..4eb20af3862d 100644 --- a/src/qcodes/validators/validators.py +++ b/src/qcodes/validators/validators.py @@ -65,7 +65,7 @@ def range_str( T = TypeVar("T") -class Validator(Generic[T]): +class Validator[T]: """ Base class for all value validators each validator should implement: diff --git a/tests/common.py b/tests/common.py index 3d2ea62c3f11..0c2da5080b9a 100644 --- a/tests/common.py +++ b/tests/common.py @@ -78,7 +78,7 @@ def func_retry(*args: P.args, **kwargs: P.kwargs) -> T: return retry_until_passes_decorator -def profile(func: Callable[P, T]) -> Callable[P, T]: +def profile[**P, T](func: Callable[P, T]) -> Callable[P, T]: """ Decorator that profiles the wrapped function with cProfile.