Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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 @@ -787,26 +787,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`. You can install all the lint dependencies using the `lint` or `dev`
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

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
190 changes: 174 additions & 16 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -188,9 +188,7 @@ build = ["setuptools>=77.0", "setuptools-rust"]
# We use quite tight pins on pylint and astroid because they have a habit of adding new
# on-by-default lints that are harder to deal with.
lint = [
"pylint~=3.2.0",
"astroid~=3.2.0",
"ruff==0.0.267",
"ruff==0.15.2",
"reno>=4.1.0", # duplicated with `doc`
"black[jupyter]~=25.1",
]
Expand Down Expand Up @@ -312,17 +310,180 @@ 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",
# flake8-logging-format
"G",
# flake8-executable
"EXE",
# flake8-comprehensions
"C4",
# flake8-debugger
"T10",
# flake8-raise
"RSE",
# flake8-pie
"PIE",
# flake8-no-pep420
"INP",
]
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",
# Docstring rules don't apply to tools as they're not documented
"D100",
"D101",
"D102",
"D103",
"D104",
"D105",
"D106",
"D107",
"D200",
"D201",
"D202",
"D203",
"D204",
"D205",
"D206",
"D207",
"D208",
"D209",
"D210",
"D211",
"D212",
"D213",
"D214",
"D215",
"D300",
"D301",
"D400",
"D401",
"D402",
"D403",
"D404",
"D405",
"D406",
"D407",
"D408",
"D409",
"D410",
"D411",
"D412",
"D413",
"D414",
"D415",
"D416",
"D417",
"D418",
"D419",
# Implicit namespace rules don't apply to tools because they're all standalone scripts
"INP001",
]
"test/*" = [
"S110",
"S301",
"S603",
"S311",
"S307",
"RUF015",
# Docstring rules don't apply to tests as they're not documented
"D100",
"D101",
"D102",
"D103",
"D104",
"D105",
"D106",
"D107",
"D200",
"D201",
"D202",
"D203",
"D204",
"D205",
"D206",
"D207",
"D208",
"D209",
"D210",
"D211",
"D212",
"D213",
"D214",
"D215",
"D300",
"D301",
"D400",
"D401",
"D402",
"D403",
"D404",
"D405",
"D406",
"D407",
"D408",
"D409",
"D410",
"D411",
"D412",
"D413",
"D414",
"D415",
"D416",
"D417",
"D418",
"D419",
]
"test/randomized/*" = ["S101"]
"test/qpy_compat/*" = ["S101", "INP001"]
"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

Expand Down Expand Up @@ -354,22 +515,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

# The documentation of the root namespace is manual in `docs/apidoc/root.rst`, so that the
# :mod:`qiskit` Sphinx cross-reference can more easily point to the top-level API table in our
Expand All @@ -34,7 +33,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 @@ -172,6 +171,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
Loading