Skip to content

Solve and enforce ruff PYI rules#12533

Merged
berland merged 9 commits intoequinor:mainfrom
berland:ruff_pyi
Dec 22, 2025
Merged

Solve and enforce ruff PYI rules#12533
berland merged 9 commits intoequinor:mainfrom
berland:ruff_pyi

Conversation

@berland
Copy link
Contributor

@berland berland commented Dec 19, 2025

Issue
Resolves many ruff issues

Approach
One commit pr ruff rule.

  • PR title captures the intent of the changes, and is fitting for release notes.
  • Added appropriate release note label
  • Commit history is consistent and clean, in line with the contribution guidelines.
  • Make sure unit tests pass locally after every commit (git rebase -i main --exec 'just rapid-tests')

When applicable

  • When there are user facing changes: Updated documentation
  • New behavior or changes to existing untested code: Ensured that unit tests are added (See Ground Rules).
  • Large PR: Prepare changes in small commits for more convenient review
  • Bug fix: Add regression test for the bug
  • Bug fix: Add backport label to latest release (format: 'backport release-branch-name')

@berland berland self-assigned this Dec 19, 2025
non-self-return-type (PYI034)

Derived from the **flake8-pyi** linter.

Fix is sometimes available.

* What it does
Checks for methods that are annotated with a fixed return type which
should instead be returning `Self`.

* Why is this bad?
If methods that generally return `self` at runtime are annotated with a
fixed return type, and the class is subclassed, type checkers will not be
able to infer the correct return type.

For example:
```python
class Shape:
    def set_scale(self, scale: float) -> Shape:
        self.scale = scale
        return self

class Circle(Shape):
    def set_radius(self, radius: float) -> Circle:
        self.radius = radius
        return self

*  Type checker infers return type as `Shape`, not `Circle`.
Circle().set_scale(0.5)

*  Thus, this expression is invalid, as `Shape` has no attribute `set_radius`.
Circle().set_scale(0.5).set_radius(2.7)
```
* collections-named-tuple (PYI024)

Derived from the **flake8-pyi** linter.

* What it does
Checks for uses of `collections.namedtuple` in stub files.

* Why is this bad?
`typing.NamedTuple` is the "typed version" of `collections.namedtuple`.

Inheriting from `typing.NamedTuple` creates a custom `tuple` subclass in
the same way as using the `collections.namedtuple` factory function.
However, using `typing.NamedTuple` allows you to provide a type annotation
for each field in the class. This means that type checkers will have more
information to work with, and will be able to analyze your code more
precisely.
* redundant-numeric-union (PYI041)

Derived from the **flake8-pyi** linter.

Fix is sometimes available.

* What it does
Checks for parameter annotations that contain redundant unions between
builtin numeric types (e.g., `int | float`).

* Why is this bad?
The [typing specification] states:

> Python’s numeric types `complex`, `float` and `int` are not subtypes of
> each other, but to support common use cases, the type system contains a
> straightforward shortcut: when an argument is annotated as having type
> `float`, an argument of type `int` is acceptable; similar, for an
> argument annotated as having type `complex`, arguments of type `float` or
> `int` are acceptable.

As such, a union that includes both `int` and `float` is redundant in the
specific context of a parameter annotation, as it is equivalent to a union
that only includes `float`. For readability and clarity, unions should omit
redundant elements.
* bad-exit-annotation (PYI036)

Derived from the **flake8-pyi** linter.

Fix is sometimes available.

* What it does
Checks for incorrect function signatures on `__exit__` and `__aexit__`
methods.

* Why is this bad?
Improperly annotated `__exit__` and `__aexit__` methods can cause
unexpected behavior when interacting with type checkers.
* any-eq-ne-annotation (PYI032)

Derived from the **flake8-pyi** linter.

Fix is always available.

* What it does
Checks for `__eq__` and `__ne__` implementations that use `typing.Any` as
the type annotation for their second parameter.

* Why is this bad?
The Python documentation recommends the use of `object` to "indicate that a
value could be any type in a typesafe manner". `Any`, on the other hand,
should be seen as an "escape hatch when you need to mix dynamically and
statically typed code". Since using `Any` allows you to write highly unsafe
code, you should generally only use `Any` when the semantics of your code
would otherwise be inexpressible to the type checker.

The expectation in Python is that a comparison of two arbitrary objects
using `==` or `!=` should never raise an exception. This contract can be
fully expressed in the type system and does not involve requesting unsound
behaviour from a type checker. As such, `object` is a more appropriate
annotation than `Any` for the second parameter of the methods implementing
these comparison operators -- `__eq__` and `__ne__`.
* custom-type-var-for-self (PYI019)

Derived from the **flake8-pyi** linter.

Fix is sometimes available.

* What it does
Checks for methods that use custom [`TypeVar`s][typing_TypeVar] in their
annotations when they could use [`Self`][Self] instead.

* Why is this bad?
While the semantics are often identical, using `Self` is more intuitive
and succinct (per [PEP 673]) than a custom `TypeVar`. For example, the
use of `Self` will typically allow for the omission of type parameters
on the `self` and `cls` arguments.

This check currently applies to instance methods that return `self`,
class methods that return an instance of `cls`, class methods that return
`cls`, and `__new__` methods.
The examples used in literal had no effect on typing, include
them as examples in the pydantic schema insteead.

* redundant-literal-union (PYI051)

Derived from the **flake8-pyi** linter.

* What it does
Checks for redundant unions between a `Literal` and a builtin supertype of
that `Literal`.

* Why is this bad?
Using a `Literal` type in a union with its builtin supertype is redundant,
as the supertype will be strictly more general than the `Literal` type.
For example, `Literal["A"] | str` is equivalent to `str`, and
`Literal[1] | int` is equivalent to `int`, as `str` and `int` are the
supertypes of `"A"` and `1` respectively.
* pep484-style-positional-only-parameter (PYI063)

Derived from the **flake8-pyi** linter.

* What it does
Checks for the presence of [PEP 484]-style positional-only parameters.

* Why is this bad?
Historically, [PEP 484] recommended prefixing parameter names with double
underscores (`__`) to indicate to a type checker that they were
positional-only. However, [PEP 570] (introduced in Python 3.8) introduced
dedicated syntax for positional-only arguments. If a forward slash (`/`) is
present in a function signature on Python 3.8+, all parameters prior to the
slash are interpreted as positional-only.

The new syntax should be preferred as it is more widely used, more concise
and more readable. It is also respected by Python at runtime, whereas the
old-style syntax was only understood by type checkers.
@berland berland added the release-notes:maintenance Automatically categorise as maintenance change in release notes label Dec 19, 2025
@berland berland added this to SCOUT Dec 19, 2025
@berland berland moved this to In Progress in SCOUT Dec 19, 2025
@codecov-commenter
Copy link

codecov-commenter commented Dec 19, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 90.66%. Comparing base (19228a0) to head (e3a3df8).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main   #12533      +/-   ##
==========================================
- Coverage   90.67%   90.66%   -0.02%     
==========================================
  Files         431      431              
  Lines       29720    29724       +4     
==========================================
  Hits        26948    26948              
- Misses       2772     2776       +4     
Flag Coverage Δ
cli-tests 37.62% <93.75%> (+<0.01%) ⬆️
gui-tests 68.77% <93.75%> (+0.03%) ⬆️
performance-and-unit-tests 74.15% <100.00%> (-0.01%) ⬇️
test 38.37% <95.45%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@codspeed-hq
Copy link

codspeed-hq bot commented Dec 19, 2025

CodSpeed Performance Report

Merging #12533 will not alter performance

Comparing berland:ruff_pyi (e3a3df8) with main (56ec2c7)

Summary

✅ 22 untouched

@berland berland moved this from In Progress to Ready for Review in SCOUT Dec 22, 2025
Copy link
Contributor

@andreas-el andreas-el left a comment

Choose a reason for hiding this comment

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

💯

@github-project-automation github-project-automation bot moved this from Ready for Review to Reviewed in SCOUT Dec 22, 2025
@berland berland merged commit 2f38385 into equinor:main Dec 22, 2025
36 checks passed
@github-project-automation github-project-automation bot moved this from Reviewed to Done in SCOUT Dec 22, 2025
@berland berland deleted the ruff_pyi branch March 6, 2026 08:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release-notes:maintenance Automatically categorise as maintenance change in release notes

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

3 participants