Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
4aaf383
Pivot to using ruff for all linting
mtreinish Jan 26, 2026
15d6c0f
Remove out of date comment
mtreinish Jan 26, 2026
d4f417b
Update makefile
mtreinish Jan 26, 2026
dae8132
Enable more rules
mtreinish Jan 27, 2026
a9ecb8b
Revert lambda autofixes
mtreinish Jan 27, 2026
4daba2f
Fix new rules
mtreinish Jan 27, 2026
d6bef4f
Add bandit rules
mtreinish Jan 27, 2026
9cef8e3
Enable Ruff native rules
mtreinish Jan 27, 2026
0dd82d8
Remove pylint disable comments
mtreinish Jan 27, 2026
4f1bc26
Merge remote-tracking branch 'origin/main' into use-ruff
mtreinish Feb 12, 2026
f928646
Enable docstring rules
mtreinish Feb 12, 2026
b0ea4d8
Add categories from old ruff config
mtreinish Feb 17, 2026
3b74a92
Merge remote-tracking branch 'origin/main' into use-ruff
mtreinish Feb 17, 2026
a05a972
Fix from rebase
mtreinish Feb 17, 2026
6cf6eac
Add flake8-raise rule
mtreinish Feb 17, 2026
e96963b
Add flake8-pie rules
mtreinish Feb 17, 2026
69c293f
Add implicit namespace rules
mtreinish Feb 17, 2026
2cb1d4f
Fix deprecation decorator error on import
mtreinish Feb 17, 2026
cb31f8c
Merge branch 'main' into use-ruff
mtreinish Feb 17, 2026
b47daf1
Fix __init__ docstring for deprecated circuit library elements
mtreinish Feb 17, 2026
eebef5e
Revert "Enable docstring rules"
mtreinish Feb 17, 2026
8be7920
Fix typo in latex circuit drawer changes
mtreinish Feb 17, 2026
9b71afe
Fix csp tests for rng change
mtreinish Feb 17, 2026
c310f9c
Merge remote-tracking branch 'origin/main' into use-ruff
mtreinish Feb 20, 2026
78dda2b
Remove unused typing imports
mtreinish Feb 20, 2026
b6610f5
Merge remote-tracking branch 'origin/main' into use-ruff
mtreinish Feb 20, 2026
f8d2c4e
Bump to ruff 0.15.2
mtreinish Feb 20, 2026
6c3b56f
Merge remote-tracking branch 'origin/main' into use-ruff
mtreinish Feb 20, 2026
16e4595
Merge remote-tracking branch 'origin/main' into use-ruff
mtreinish Feb 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
22 changes: 7 additions & 15 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -764,26 +764,18 @@ int test_FILE_NAME()
Qiskit uses three tools for Python code formatting and lint checking. The
first tool is [black](https://github.com/psf/black) which is a code formatting
tool that will automatically update the code formatting to a consistent style.
The second tool is [pylint](https://www.pylint.org/) which is a code linter
The second tool is [ruff](https://docs.astral.sh/ruff/) which is a code linter
which does a deeper analysis of the Python code to find both style issues and
potential bugs and other common issues in Python. The third tool is the linter
[ruff](https://github.com/charliermarsh/ruff), which has been recently
introduced into Qiskit on an experimental basis. Only a very small number
of rules are enabled.
potential bugs and other common issues in Python.

You can check that your local modifications conform to the style rules by
running `tox -elint` which will run `black`, `ruff`, and `pylint` to check the
running `tox -elint` which will run `black` and `ruff` to check the
local code formatting and lint. If black returns a code formatting error you can
run `tox -eblack` to automatically update the code formatting to conform to the
style. However, if `ruff` or `pylint` return any error you will have to fix
these issues by manually updating your code.

Because `pylint` analysis can be slow, there is also a `tox -elint-incr` target,
which runs `black` and `ruff` just as `tox -elint` does, but only applies
`pylint` to files which have changed from the source github. On rare occasions
this will miss some issues that would have been caught by checking the complete
source tree, but makes up for this by being much faster (and those rare
oversights will still be caught by the CI after you open a pull request).
style. However, if `ruff` return any error you will have to fix these issues by
manually updating your code. Sometimes `ruff` will be able to fix failures with
the `--fix` flag. In these cases the output will tell you how many errors can be
automatically fixed

Because they are so fast, it is sometimes convenient to run the tools `black` and `ruff` separately
rather than via `tox`. If you have installed the development packages in your python environment via
Expand Down
9 changes: 3 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ endif

.PHONY: default ruff env lint lint-incr style black test test_randomized pytest pytest_randomized test_ci coverage coverage_erase clean cheader clib ctest cformat fix_cformat cclean

default: ruff style lint-incr test ;
default: style lint-incr test ;

# Dependencies need to be installed on the Anaconda virtual environment.
env:
Expand All @@ -27,18 +27,15 @@ env:
bash -c "source activate Qiskitenv;pip install -r requirements.txt"; \
fi;

# Ignoring generated ones with .py extension.
lint:
pylint -rn qiskit test tools
ruff check qiskit test tools setup.py
tools/verify_headers.py qiskit test tools
tools/find_optional_imports.py
tools/find_stray_release_notes.py
tools/verify_images.py

# Only pylint on files that have changed from origin/main. Also parallelize (disables cyclic-import check)
lint-incr:
-git fetch -q https://github.com/Qiskit/qiskit-terra.git :lint_incr_latest
tools/pylint_incr.py -j4 -rn -sn --paths :/qiskit/*.py :/test/*.py :/tools/*.py
ruff check qiskit test tools setup.py
tools/verify_headers.py qiskit test tools
tools/find_optional_imports.py
tools/verify_images.py
Expand Down
67 changes: 54 additions & 13 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -221,18 +221,62 @@ test-command = "cp -r {project}/test . && stestr --test-path test/python run --a
repair-wheel-command = "cp {wheel} {dest_dir}/. && pipx run abi3audit --strict --report {wheel}"

[tool.ruff]
line-length = 110

[tool.ruff.lint]
select = [
# Rules in alphabetic order
"C4", # category: flake8-comprehensions
"EXE", # Category: flake8-executable
"F631", # assert-tuple
"F632", # is-literal
"F634", # if-tuple
"F823", # undefined-local
"G", # flake8-logging-format
"T10", # category: flake8-debugger
# pycodestyle
"E",
# Pyflakes
"F",
# Pylint
"PL",
# pyupgrade
"UP",
# bandit
"S",
# Ruff native rules
"RUF",
]
ignore = [
"E402", # module-import-not-at-top-of-file false positives with module docs
"F401", # unused-import false triggers on init re-exports with __all__
"E501", # line-too-long rely on black for formatting
"PLR2004", # magic-value-comparison overly pedantic especially in tests
"PLR0911", # too-many-return-statements
"PLR0912", # too-many-branches
"PLR0904", # too-many-public-methods
"PLR0914", # too-many-locals
"PLR0915", # too-many-statements
"PLR1702", # too-many-nested-blocks
"PLR0904", # too-many-public-methods
"PLC0415", # import-outside-top-level
"E731", # lambda-assignment
"PLW1514", # unspecified-encoding do not want to implement
"PLR0913", # too-many-arguments
"PLW1641", # eq-without-hash not everything has to be hashablie
"PLW3301", # nested-min-max
"PLW2901", # redefined-loop-variablke - TODO fix this
"RUF003", # ambiguous-unicode-character-comment
"RUF005", # collection-literal-concatenation
"RUF012", # mutable-class-default"
"RUF001", # ambiguous-unicode-character-string
"RUF002", # ambiguous-unicode-character-docstring
"RUF007", # zip-instead-of-pairwise overly opinionated
]

[tool.ruff.lint.per-file-ignores]
"*.ipynb" = ["F821"]
# Security rules from bandit don't apply to tests and utils
"tools/*" = ["S603"]
"test/*" = ["S110", "S301", "S603", "S311", "S307", "RUF015"]
"test/randomized/*" = ["S101"]
"test/qpy_compat/*" = ["S101"]
"test/benchmarks/*" = ["S101"]
# Visualization functions often subprocess to visualization tools
"qiskit/visualization/*" = ["S603", "S607"]

Comment thread
mtreinish marked this conversation as resolved.

[tool.pylint.main]
extension-pkg-allow-list = [
Comment on lines 488 to 489
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the entire tool.pylint table can go from pyproject.toml now, right?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I opened a PR for this in: #15717

"numpy",
Expand Down Expand Up @@ -263,22 +307,19 @@ disable = [
"protected-access", # disabled as we don't follow the public vs private convention strictly
"duplicate-code", # disabled as it is too verbose
"redundant-returns-doc", # for @abstractmethod, it cannot interpret "pass"
"too-many-lines", "too-many-branches", "too-many-locals", "too-many-nested-blocks", "too-many-statements",
"too-many-instance-attributes", "too-many-arguments", "too-many-public-methods", "too-few-public-methods", "too-many-ancestors",
"too-many-instance-attributes", "too-many-arguments", "too-few-public-methods", "too-many-ancestors",
"unnecessary-pass", # allow for methods with just "pass", for clarity
"unnecessary-dunder-call", # do not want to implement
"no-else-return", # relax "elif" after a clause with a return
"docstring-first-line-empty", # relax docstring style
"import-outside-toplevel", "import-error", # overzealous with our optionals/dynamic packages
"nested-min-max", # this gives false equivalencies if implemented for the current lint version
"consider-using-max-builtin", "consider-using-min-builtin", # unnecessary stylistic opinion
# TODO(#9614): these were added in modern Pylint. Decide if we want to enable them. If so,
# remove from here and fix the issues. Else, move it above this section and add a comment
# with the rationale
"no-member", # for dynamically created members
"not-context-manager",
"unnecessary-lambda-assignment", # do not want to implement
"unspecified-encoding", # do not want to implement
]

enable = [
Expand Down
5 changes: 2 additions & 3 deletions qiskit/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.

# pylint: disable=wrong-import-position,wrong-import-order

"""Main Qiskit public functionality."""

Expand All @@ -31,7 +30,7 @@
# `qiskit/tools` folder in their path, which will appear as a "namespace package" with no valid
# location. We catch that case as "not actually having Qiskit 0.x" as a convenience to devs.
_has_tools = getattr(importlib.util.find_spec("qiskit.tools"), "has_location", False)
_suppress_error = os.environ.get("QISKIT_SUPPRESS_1_0_IMPORT_ERROR", False) == "1"
_suppress_error = os.environ.get("QISKIT_SUPPRESS_1_0_IMPORT_ERROR", "") == "1"
if not _suppress_error and _has_tools:
raise ImportError(
"Qiskit is installed in an invalid environment that has both Qiskit >=1.0"
Expand Down Expand Up @@ -165,6 +164,6 @@
"QiskitError",
"QuantumCircuit",
"QuantumRegister",
"transpile",
"generate_preset_pass_manager",
"transpile",
]
2 changes: 1 addition & 1 deletion qiskit/_numpy_compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
"""

VERSION = np.lib.NumpyVersion(np.__version__)
VERSION_PARTS: typing.Tuple[int, ...]
VERSION_PARTS: tuple[int, ...]
"""The numeric parts of the Numpy release version, e.g. ``(2, 0, 0)``. Does not include pre- or
post-release markers (e.g. ``rc1``)."""
if match := re.fullmatch(_VERSION_PATTERN, np.__version__, flags=re.VERBOSE | re.IGNORECASE):
Expand Down
4 changes: 2 additions & 2 deletions qiskit/circuit/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1374,7 +1374,7 @@ def __array__(self, dtype=None, copy=None):
\end{pmatrix}
"""

from qiskit._accelerate.circuit import ( # pylint: disable=unused-import
from qiskit._accelerate.circuit import (
Bit,
Qubit,
AncillaQubit,
Expand All @@ -1391,7 +1391,7 @@ def __array__(self, dtype=None, copy=None):
from .quantumcircuit import QuantumCircuit
from .gate import Gate

# pylint: disable=cyclic-import

from . import annotation
from .annotation import Annotation
from .controlledgate import ControlledGate
Expand Down
32 changes: 15 additions & 17 deletions qiskit/circuit/_add_control.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,6 @@ def control(
CircuitError: gate contains non-gate in definition
"""

# pylint: disable=cyclic-import
from qiskit.circuit import controlledgate

ctrl_state = _ctrl_state_to_int(ctrl_state, num_ctrl_qubits)
Expand Down Expand Up @@ -262,23 +261,22 @@ def apply_basic_controlled_gate(circuit, gate, controls, target):
circuit.cp(lamb, controls[0], target)
else:
circuit.cu(theta, phi, lamb, 0, controls[0], target)
elif phi == -pi / 2 and lamb == pi / 2:
circuit.mcrx(theta, controls, target, use_basis_gates=False)
elif phi == 0 and lamb == 0:
circuit.mcry(
theta,
controls,
target,
use_basis_gates=False,
)
elif theta == 0 and phi == 0:
circuit.mcp(lamb, controls, target)
else:
if phi == -pi / 2 and lamb == pi / 2:
circuit.mcrx(theta, controls, target, use_basis_gates=False)
elif phi == 0 and lamb == 0:
circuit.mcry(
theta,
controls,
target,
use_basis_gates=False,
)
elif theta == 0 and phi == 0:
circuit.mcp(lamb, controls, target)
else:
circuit.mcrz(lamb, controls, target, use_basis_gates=False)
circuit.mcry(theta, controls, target, use_basis_gates=False)
circuit.mcrz(phi, controls, target, use_basis_gates=False)
circuit.mcp((phi + lamb) / 2, controls[1:], controls[0])
circuit.mcrz(lamb, controls, target, use_basis_gates=False)
circuit.mcry(theta, controls, target, use_basis_gates=False)
circuit.mcrz(phi, controls, target, use_basis_gates=False)
circuit.mcp((phi + lamb) / 2, controls[1:], controls[0])

elif gate.name == "z":
circuit.h(target)
Expand Down
5 changes: 2 additions & 3 deletions qiskit/circuit/_classical_resource_map.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

import typing

from . import Bit, Clbit, ClassicalRegister # pylint: disable=cyclic-import
from . import Bit, Clbit, ClassicalRegister
from .classical import expr


Expand All @@ -38,9 +38,8 @@ class VariableMapper(expr.ExprVisitor[expr.Expr]):

# We don't want docstrings for the inherited visitor methods, which are self-explanatory and
# would just be noise.
# pylint: disable=missing-function-docstring

__slots__ = ("target_cregs", "register_map", "bit_map", "var_map", "add_register")
__slots__ = ("add_register", "bit_map", "register_map", "target_cregs", "var_map")

def __init__(
self,
Expand Down
5 changes: 3 additions & 2 deletions qiskit/circuit/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,8 @@ def _ctrl_state_to_int(ctrl_state, num_ctrl_qubits):
ctrl_state_std = None
if isinstance(ctrl_state, str):
try:
assert len(ctrl_state) == num_ctrl_qubits
if len(ctrl_state) != num_ctrl_qubits:
CircuitError("Invalid number of ctrl qubits for given ctrl state")
ctrl_state = int(ctrl_state, 2)
except ValueError as ex:
raise CircuitError("invalid control bit string: " + ctrl_state) from ex
Expand All @@ -96,7 +97,7 @@ def _ctrl_state_to_int(ctrl_state, num_ctrl_qubits):
elif ctrl_state is None:
ctrl_state_std = 2**num_ctrl_qubits - 1
else:
raise CircuitError(f"invalid control state specification: {repr(ctrl_state)}")
raise CircuitError(f"invalid control state specification: {ctrl_state!r}")
return ctrl_state_std


Expand Down
16 changes: 8 additions & 8 deletions qiskit/circuit/annotated_operation.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from __future__ import annotations

import dataclasses
from typing import Union, List
from typing import Union

from qiskit.circuit.operation import Operation
from qiskit.circuit.parameterexpression import ParameterValueType
Expand Down Expand Up @@ -60,7 +60,7 @@ class PowerModifier(Modifier):
class AnnotatedOperation(Operation):
"""Annotated operation."""

def __init__(self, base_op: Operation, modifiers: Union[Modifier, List[Modifier]]):
def __init__(self, base_op: Operation, modifiers: Modifier | list[Modifier]):
"""
Create a new AnnotatedOperation.

Expand Down Expand Up @@ -99,7 +99,7 @@ def __init__(self, base_op: Operation, modifiers: Union[Modifier, List[Modifier]
"""
self.base_op = base_op
"""The base operation that the modifiers in this annotated operation applies to."""
self.modifiers = modifiers if isinstance(modifiers, List) else [modifiers]
self.modifiers = modifiers if isinstance(modifiers, list) else [modifiers]
"""Ordered sequence of the modifiers to apply to :attr:`base_op`. The modifiers are applied
in order from lowest index to highest index."""

Expand Down Expand Up @@ -131,13 +131,13 @@ def __eq__(self, other) -> bool:
and self.base_op == other.base_op
)

def copy(self) -> "AnnotatedOperation":
def copy(self) -> AnnotatedOperation:
"""Return a copy of the :class:`~.AnnotatedOperation`."""
return AnnotatedOperation(base_op=self.base_op.copy(), modifiers=self.modifiers.copy())

def to_matrix(self):
"""Return a matrix representation (allowing to construct Operator)."""
from qiskit.quantum_info.operators import Operator # pylint: disable=cyclic-import
from qiskit.quantum_info.operators import Operator

operator = Operator(self.base_op)

Expand Down Expand Up @@ -178,7 +178,7 @@ def control(
Returns:
A controlled version of the given operation.
"""
# pylint: disable=unused-argument

extended_modifiers = self.modifiers.copy()
extended_modifiers.append(
ControlModifier(num_ctrl_qubits=num_ctrl_qubits, ctrl_state=ctrl_state)
Expand All @@ -197,7 +197,7 @@ def inverse(self, annotated: bool = True):
Returns:
Inverse version of the given operation.
"""
# pylint: disable=unused-argument

extended_modifiers = self.modifiers.copy()
extended_modifiers.append(InverseModifier())
return AnnotatedOperation(self.base_op, extended_modifiers)
Expand All @@ -215,7 +215,7 @@ def power(self, exponent: float, annotated: bool = False):
Returns:
An operation implementing ``gate^exponent``
"""
# pylint: disable=unused-argument

extended_modifiers = self.modifiers.copy()
extended_modifiers.append(PowerModifier(exponent))
return AnnotatedOperation(self.base_op, extended_modifiers)
Expand Down
7 changes: 4 additions & 3 deletions qiskit/circuit/annotation.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,16 +189,17 @@ def load(self, namespace, payload):
from __future__ import annotations

import abc
from typing import Literal, Iterator
from typing import Literal
from collections.abc import Iterator

from qiskit._accelerate.circuit import Annotation


__all__ = [
"Annotation", # Also exported in `qiskit.circuit`, but for convenience is here too.
"QPYSerializer",
"OpenQASM3Serializer",
"QPYFromOpenQASM3Serializer",
"QPYSerializer",
"iter_namespaces",
]

Expand Down Expand Up @@ -327,7 +328,7 @@ def dump_state(self) -> bytes:
"""
return b""

def load_state(self, namespace: str, payload: bytes): # pylint: disable=unused-argument
def load_state(self, namespace: str, payload: bytes):
"""Initialize the state of the deserializer for a given ``namespace`` key.

When in a QPY loading context, this method will be called exactly once, before all calls to
Expand Down
Loading
Loading