Compare commits

...

165 Commits

Author SHA1 Message Date
Ibraheem Ahmed
992e77e4d0 suggestions from code review 2025-08-22 19:36:40 -04:00
Ibraheem Ahmed
8d05367d60 reload Project after deserialization 2025-08-22 19:09:22 -04:00
Ibraheem Ahmed
2402831223 experimental persistent caching 2025-08-22 19:09:21 -04:00
Ibraheem Ahmed
7abc41727b [ty] Shrink size of AstNodeRef (#20028)
## Summary

Removes the `module_ptr` field from `AstNodeRef` in release mode, and
change `NodeIndex` to a `NonZeroU32` to reduce the size of
`Option<AstNodeRef<_>>` fields.

I believe CI runs in debug mode, so this won't show up in the memory
report, but this reduces memory by ~2% in release mode.
2025-08-22 17:03:22 -04:00
chiri
886c4e4773 [flake8-use-pathlib] Fix PTH211 autofix (#20049)
## Summary
Part of #20009
2025-08-22 13:35:08 -05:00
Alex Waygood
bc6ea68733 [ty] Add precise iteration and unpacking inference for string literals and bytes literals (#20023)
## Summary

Previously we held off from doing this because we weren't sure that it
was worth the added complexity cost. But our code has changed in the
months since we made that initial decision, and I think the structure of
the code is such that it no longer really leads to much added complexity
to add precise inference when unpacking a string literal or a bytes
literal.

The improved inference we gain from this has real benefits to users (see
the mypy_primer report), and this PR doesn't appear to have a
performance impact.

## Test plan

mdtests
2025-08-22 19:33:08 +01:00
Micha Reiser
796819e7a0 [ty] Disallow std::env and io methods in most ty crates (#20046)
## Summary

We use the `System` abstraction in ty to abstract away the host/system
on which ty runs.
This has a few benefits:

* Tests can run in full isolation using a memory system (that uses an
in-memory file system)
* The LSP has a custom implementation where `read_to_string` returns the
content as seen by the editor (e.g. unsaved changes) instead of always
returning the content as it is stored on disk
* We don't require any file system polyfills for wasm in the browser


However, it does require extra care that we don't accidentally use
`std::fs` or `std::env` (etc.) methods in ty's code base (which is very
easy).

This PR sets up Clippy and disallows the most common methods, instead
pointing users towards the corresponding `System` methods.

The setup is a bit awkward because clippy doesn't support inheriting
configurations. That means, a crate can only override the entire
workspace configuration or not at all.
The approach taken in this PR is:

* Configure the disallowed methods at the workspace level
* Allow `disallowed_methods` at the workspace level
* Enable the lint at the crate level using the warn attribute (in code)


The obvious downside is that it won't work if we ever want to disallow
other methods, but we can figure that out once we reach that point.

What about false positives: Just add an `allow` and move on with your
life :) This isn't something that we have to enforce strictly; the goal
is to catch accidental misuse.

## Test Plan

Clippy found a place where we incorrectly used `std::fs::read_to_string`
2025-08-22 11:13:47 -07:00
Vivek Dasari
5508e8e528 Add testing helper to compare stable vs preview snapshots (#19715)
## Summary
This PR implements a diff test helper `assert_diagnostics_diff` as
described in #19351. The diff file includes both the settings ( e.g.
`+linter.preview = enabled`) and the snapshot data itself.

The current implementation looks for each old diagnostic in the new
snapshot. This works when the preview behavior adds/removes a couple
diagnostics. This implementation does not work well when every
diagnostic is modified (e.g. a "fix" is added).
https://github.com/astral-sh/ruff/pull/19715#discussion_r2259410763 has
ideas for future improvements to this implementation.

The example usage in this PR writes the diff to `preview_diff` file
instead of `preview` file, which might be a useful convention to keep.


## Test Plan
- Included a unit test at:
https://github.com/astral-sh/ruff/pull/19715/files#diff-d49487fe3e8a8585529f62c2df2a2b0a4c44267a1f93d1e859dff1d9f8771d36R523
- Example usage of this new test helper:
https://github.com/astral-sh/ruff/pull/19715/files#diff-2a33ac11146d1794c01a29549a6041d3af6fb6f9b423a31ade12a88d1951b0c2R1
2025-08-22 12:49:34 -05:00
chiri
0be3e1fbbf [flake8-use-pathlib] Add autofix for PTH211 (#20009)
## Summary
Part of https://github.com/astral-sh/ruff/issues/2331
2025-08-22 12:38:37 -05:00
Micha Reiser
5d217b7f46 [ty] Add type as detail to completion items (#20047)
## Summary

@BurntSushi was so kind as to find me an easy task to do some coding
before I'm off to PTO.

This PR adds the type to completion items (see the gray little text at
the end of a completion item).



https://github.com/user-attachments/assets/c0a86061-fa12-47b4-b43c-3c646771a69d
2025-08-22 12:32:53 -04:00
Dylan
0b6ce1c788 [ruff] Handle empty t-strings in unnecessary-empty-iterable-within-deque-call (RUF037) (#20045)
Adds a method to `TStringValue` to detect whether the t-string is empty
_as an iterable_. Note the subtlety here that, unlike f-strings, an
empty t-string is still truthy (i.e. `bool(t"")==True`).

Closes #19951
2025-08-22 10:23:49 -05:00
Matthew Mckee
0e9d77e43a Fix incorrect lsp inlay hint type (#20044) 2025-08-22 17:12:49 +02:00
Carl Meyer
8b827c3c6c [ty] rename BareTypeAliasType to ManualPEP695TypeAliasType (#20037)
## Summary

Rename `TypeAliasType::Bare` to `TypeAliasType::ManualPEP695`, and
`BareTypeAliasType` to `ManualPEP695TypeAliasType`.

Why?

Both existing variants of `TypeAliasType` are specific to features added
in PEP 695 (which introduced both the `type` statement and
`types.TypeAliasType`), so it doesn't make sense to name one with the
name `PEP695` and not the other.

A "bare" type alias, in my mind, is a legacy type alias like `IntOrStr =
int | str`, which is "bare" in that there is nothing at all
distinguishing it as a type alias. I will want to use the "bare" name
for this variant, in a future PR.

The renamed variant here describes a type alias created with `IntOrStr =
types.TypeAliasType("IntOrStr", int | str)`, which is not "bare", it's
just "manually" instantiated instead of using the `type` statement
syntax sugar. (This is useful when using the `typing_extensions`
backport of `TypeAliasType` on older Python versions.)

## Test Plan

Pure rename, existing tests pass.
2025-08-22 07:40:29 -07:00
Max Mynter
c22395dbc6 [ruff] Fix false positive for t-strings in default-factory-kwarg (RUF026) (#20032)
Closes #19993

## Summary
Recognize t strings as never being callable to avoid false positives on
RUF026.
2025-08-22 09:29:42 -05:00
Micha Reiser
11f521c768 [ty] Close signature help after ) (#20017) 2025-08-22 16:09:22 +02:00
Micha Reiser
c5e05df966 [ty] Cancel background tasks when shutdown is requested (#20039) 2025-08-22 10:20:13 +02:00
github-actions[bot]
7a44ea680e [ty] Sync vendored typeshed stubs (#20031)
Co-authored-by: typeshedbot <>
Co-authored-by: Alex Waygood <alex.waygood@gmail.com>
2025-08-21 21:32:48 +00:00
Alex Waygood
f82025d919 [ty] Improve diagnostics for bad calls to functions (#20022) 2025-08-21 22:00:44 +01:00
Micha Reiser
365f521c37 [ty] Fix incorrect docstring in call signature completion (#20021)
## Summary

This PR fixes https://github.com/astral-sh/ty/issues/1071

The core issue is that `CallableType` is a salsa interned but
`Signature` (which `CallableType` stores) ignores the `Definition` in
its `Eq` and `Hash` implementation.

This PR tries to simplest fix by removing the custom `Eq` and `Hash`
implementation. The main downside of this fix is that it can increase
memory usage because `CallableType`s that are equal except for their
`Definition` are now interned separately.

The alternative is to remove `Definition` from `CallableType` and
instead, call `bindings` directly on the callee (call_expression.func).
However, this would require
addressing the TODO 

here
39ee71c2a5/crates/ty_python_semantic/src/types.rs (L4582-L4586)

This might probably be worth addressing anyway, but is the more involved
fix. That's why I opted for removing the custom `Eq` implementation.

We already "ignore" the definition during normalization, thank's to
Alex's work in https://github.com/astral-sh/ruff/pull/19615

## Test Plan



https://github.com/user-attachments/assets/248d1cb1-12fd-4441-adab-b7e0866d23eb
2025-08-21 16:36:40 -04:00
Aria Desires
fc5321e000 [ty] fix GotoTargets for keyword args in nested function calls (#20013)
While implementing similar logic for initializers I noticed that this
code appeared to be walking the ancestors in the wrong direction, and so
if you have nested function calls it would always grab the outermost one
instead of the closest-ancestor.

The four copies of the test are because there's something really evil in
our caching that can't seem to be demonstrated in our cursor testing
framework, which I'm filing a followup for.
2025-08-21 20:19:52 +00:00
Dylan
c68ff8d90b Bump 0.12.10 (#20025) 2025-08-21 13:09:31 -05:00
Andrew Gallant
5931a5207d [ty] Stop running every mdtest twice
This was an accidental oversight introduced in commit
468eb37d75.
2025-08-21 13:37:08 -04:00
Brent Westbrook
692be72f5a Move diff rendering to ruff_db (#20006)
Summary
--

This is a preparatory PR in support of #19919. It moves our `Diff`
rendering code from `ruff_linter` to `ruff_db`, where we have direct
access to the `DiagnosticStylesheet` used by our other diagnostic
rendering code. As shown by the tests, this shouldn't cause any visible
changes. The colors aren't exactly the same, as I note in a TODO
comment, but I don't think there's any existing way to see those, even
in tests.

The `Diff` implementation is mostly unchanged. I just switched from a
Ruff-specific `SourceFile` to a `DiagnosticSource` (removing an
`expect_ruff_source_file` call) and updated the `LineStyle` struct and
other styling calls to use `fmt_styled` and our existing stylesheet.

In support of these changes, I added three styles to our stylesheet:
`insertion` and `deletion` for the corresponding diff operations, and
`underline`, which apparently we _can_ use, as I hoped on Discord. This
isn't supported in all terminals, though. It worked in ghostty but not
in st for me.

I moved the `calculate_print_width` function from the now-deleted
`diff.rs` to a method on `OneIndexed`, where it was available everywhere
we needed it. I'm not sure if that's desirable, or if my other changes
to the function are either (using `ilog10` instead of a loop). This does
make it `const` and slightly simplifies things in my opinion, but I'm
happy to revert it if preferred.

I also inlined a version of `show_nonprinting` from the
`ShowNonprinting` trait in `ruff_linter`:


f4be05a83b/crates/ruff_linter/src/text_helpers.rs (L3-L5)

This trait is now only used in `source_kind.rs`, so I'm not sure it's
worth having the trait or the macro-generated implementation (which is
only called once). This is obviously closely related to our unprintable
character handling in diagnostic rendering, but the usage seems
different enough not to try to combine them.


f4be05a83b/crates/ruff_db/src/diagnostic/render.rs (L990-L998)

We could also move the trait to another crate where we can use it in
`ruff_db` instead of inlining here, of course.

Finally, this PR makes `TextEmitter` a very thin wrapper around a
`DisplayDiagnosticsConfig`. It's still used in a few places, though,
unlike the other emitters we've replaced, so I figured it was worth
keeping around. It's a pretty nice API for setting all of the options on
the config and then passing that along to a `DisplayDiagnostics`.

Test Plan
--

Existing snapshot tests with diffs
2025-08-21 09:47:00 -04:00
Douglas Creager
14fe1228e7 [ty] Perform assignability etc checks using new Constraints trait (#19838)
"Why would you do this? This looks like you just replaced `bool` with an
overly complex trait"

Yes that's correct!

This should be a no-op refactoring. It replaces all of the logic in our
assignability, subtyping, equivalence, and disjointness methods to work
over an arbitrary `Constraints` trait instead of only working on `bool`.

The methods that `Constraints` provides looks very much like what we get
from `bool`. But soon we will add a new impl of this trait, and some new
methods, that let us express "fuzzy" constraints that aren't always true
or false. (In particular, a constraint will express the upper and lower
bounds of the allowed specializations of a typevar.)

Even once we have that, most of the operations that we perform on
constraint sets will be the usual boolean operations, just on sets.
(`false` becomes empty/never; `true` becomes universe/always; `or`
becomes union; `and` becomes intersection; `not` becomes negation.) So
it's helpful to have this separate PR to refactor how we invoke those
operations without introducing the new functionality yet.

Note that we also have translations of `Option::is_some_and` and
`is_none_or`, and of `Iterator::any` and `all`, and that the `and`,
`or`, `when_any`, and `when_all` methods are meant to short-circuit,
just like the corresponding boolean operations. For constraint sets,
that depends on being able to implement the `is_always` and `is_never`
trait methods.

---------

Co-authored-by: Carl Meyer <carl@astral.sh>
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
2025-08-21 09:30:09 -04:00
Micha Reiser
045cba382a [ty] Use dedent in cursor tests (#20019) 2025-08-21 10:31:54 +02:00
Brent Westbrook
a5cbca156c Fix rust feature activation (#20012) 2025-08-21 09:26:06 +02:00
Dhruv Manilawala
d43a3d34dd [ty] Avoid unnecessary argument type expansion (#19999)
## Summary

Part of: https://github.com/astral-sh/ty/issues/868

This PR adds a heuristic to avoid argument type expansion if it's going
to eventually lead to no matching overload.

This is done by checking whether the non-expandable argument types are
assignable to the corresponding annotated parameter type. If one of them
is not assignable to all of the remaining overloads, then argument type
expansion isn't going to help.

## Test Plan

Add mdtest that would otherwise take a long time because of the number
of arguments that it would need to expand (30).
2025-08-21 06:13:11 +00:00
Aria Desires
99111961c0 [ty] Add link for namespaces being partial (#20015)
As requested
2025-08-20 21:28:57 -07:00
Aria Desires
859475f017 [ty] add docstrings to completions based on type (#20008)
This is a fairly simple but effective way to add docstrings to like 95%
of completions from initial experimentation.

Fixes https://github.com/astral-sh/ty/issues/1036

Although ironically this approach *does not* work specifically for
`print` and I haven't looked into why.
2025-08-20 17:00:09 -04:00
Igor Drokin
7b75aee21d [pyupgrade] Avoid reporting __future__ features as unnecessary when they are used (UP010) (#19769)
## Summary
Resolves #19561

Fixes the [unnecessary-future-import
(UP010)](https://docs.astral.sh/ruff/rules/unnecessary-future-import/)
rule to correctly identify when imported __future__ modules are actually
used in the code, preventing false positives.

I assume there is no way to check usage in `analyze::statements`,
because we don't have any usage bindings for imports. To determine
unused imports, we have to fully scan the file to create bindings and
then check usage, similar to [unused-import
(F401)](https://docs.astral.sh/ruff/rules/unused-import/#unused-import-f401).
So, `Rule::UnnecessaryFutureImport` was moved from the
`analyze::statements` to the `analyze::deferred_scopes` stage. This
caused the need to change the logic of future import handling to a
bindings-based approach.

Also, the diagnostic report was changed.
Before
```
  |
1 | from __future__ import nested_scopes, generators
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP010
```
after
```
  |
1 | from __future__ import nested_scopes, generators
  |                        ^^^^^^^^^^^^^ UP010
```

I believe this is the correct way, because `generators` may be used, but
`nested_scopes` is not.

### Special case
I've found out about some specific case.
```python
from __future__ import nested_scopes

nested_scopes = 1
```
Here we can treat `nested_scopes` as an unused import because the
variable `nested_scopes` shadows it and we can safely remove the future
import (my fix does it).

But
[F401](https://docs.astral.sh/ruff/rules/unused-import/#unused-import-f401)
not triggered for such case
([sandbox](https://play.ruff.rs/296d9c7e-0f02-4659-b0c0-78cc21f3de76))
```
from foo import print_function

print_function = 1
```
In my mind, `print_function` here is an unused import and should be
deleted (my IDE highlight it). What do you think?

## Test Plan

Added test cases and snapshots:
- Split test file into separate _0 and _1 files for appropriate checks.
- Added test cases to verify fixes when future module are used.

---------

Co-authored-by: Igor Drokin <drokinii1017@gmail.com>
2025-08-20 15:22:03 -04:00
chiri
d04dcd991b [flake8-use-pathlib] Add fixes for PTH102 and PTH103 (#19514)
## Summary

Part of https://github.com/astral-sh/ruff/issues/2331

## Test Plan

<!-- How was it tested? -->
`cargo nextest run flake8_use_pathlib`
2025-08-20 14:36:07 -04:00
Leandro Braga
39ee71c2a5 [ty] correctly ignore field specifiers when not specified (#20002)
This commit corrects the type checker's behavior when handling
`dataclass_transform` decorators that don't explicitly specify
`field_specifiers`. According to [PEP 681 (Data Class
Transforms)](https://peps.python.org/pep-0681/#dataclass-transform-parameters),
when `field_specifiers` is not provided, it defaults to an empty tuple,
meaning no field specifiers are supported and
`dataclasses.field`/`dataclasses.Field` calls should be ignored.

Fixes https://github.com/astral-sh/ty/issues/980
2025-08-20 11:33:23 -07:00
Brent Westbrook
1a38831d53 Option::unwrap is now const (#20007)
Summary
--

I noticed while working on #20006 that we had a custom `unwrap` function
for `Option`. This has been const on stable since 1.83
([docs](https://doc.rust-lang.org/std/option/enum.Option.html#method.unwrap),
[release notes](https://blog.rust-lang.org/2024/11/28/Rust-1.83.0/)), so
I think it's safe to use now. I grepped a bit for related todos and
found this one for `AsciiCharSet` but no others.

Test Plan
--

Existing tests
2025-08-20 13:40:49 -04:00
Andrew Gallant
ddd4bab67c [ty] Re-arrange "list modules" implementation for Salsa caching
This basically splits `list_modules` into a higher level "aggregation"
routine and a lower level "get modules for one search path" routine.
This permits Salsa to cache the lower level components, e.g., many
search paths refer to directories that rarely change. This saves us
interaction with the system.

This did require a fair bit of surgery in terms of being careful about
adding file roots. Namely, now that we rely even more on file roots
existing for correct handling of cache invalidation, there were several
spots in our code that needed to be updated to add roots (that we
weren't previously doing). This feels Not Great, and it would be better
if we had some kind of abstraction that handled this for us. But it
isn't clear to me at this time what that looks like.
2025-08-20 10:41:47 -04:00
Andrew Gallant
468eb37d75 [ty] Test "list modules" versus "resolve module" in every mdtest
This ensures there is some level of consistency between the APIs.

This did require exposing a couple more things on `Module` for good
error messages. This also motivated a switch to an interned struct
instead of a tracked struct. This ensures that `list_modules` and
`resolve_modules` reuse the same `Module` values when the inputs are the
same.

Ref https://github.com/astral-sh/ruff/pull/19883#discussion_r2272520194
2025-08-20 10:27:54 -04:00
Andrew Gallant
2e9c241d7e [ty] Wire up "list modules" API to make module completions work
This makes `import <CURSOR>` and `from <CURSOR>` completions work.

This also makes `import os.<CURSOR>` and `from os.<CURSOR>`
completions work. In this case, we are careful to only offer
submodule completions.
2025-08-20 10:27:54 -04:00
Andrew Gallant
05478d5cc7 [ty] Tweak some completion tests
These tests were added as a regression check that a panic
didn't occur. So we were asserting a bit more than necessary.
In particular, these will soon return completions for modules,
which creates large snapshots that we don't need.

So modify these to just check there is sensible output that
doesn't panic.
2025-08-20 10:27:54 -04:00
Andrew Gallant
4db20f459c [ty] Add "list modules" implementation
The actual implementation wasn't too bad. It's not long
but pretty fiddly. I copied over the tests from the existing
module resolver and adapted them to work with this API. Then
I added a number of my own tests as well.
2025-08-20 10:27:54 -04:00
Andrew Gallant
ec7c2efef9 [ty] Lightly expose FileModule and NamespacePackage fields
This will make it easier to emit this info into snapshots for
testing.
2025-08-20 10:27:54 -04:00
Andrew Gallant
79b2754215 [ty] Add some more helper routines to ModulePath 2025-08-20 10:27:54 -04:00
Andrew Gallant
a0ddf1f7c4 [ty] Fix a bug when converting ModulePath to ModuleName
Previously, if the module was just `foo-stubs`, we'd skip over
stripping the `-stubs` suffix which would lead to us returning
`None`.

This function is now a little convoluted and could be simpler
if we did an intermediate allocation. But I kept the iterative
approach and added a special case to handle `foo-stubs`.
2025-08-20 10:27:54 -04:00
Andrew Gallant
5b00ec981b [ty] Split out another constructor for ModuleName
This makes it a little more flexible to call. For example,
we might have a `StmtImport` and not a `StmtImportFrom`.
2025-08-20 10:27:54 -04:00
Andrew Gallant
306ef3bb02 [ty] Add stub-file tests to existing module resolver
These tests capture existing behavior.

I added these when I stumbled upon what I thought was an
oddity: we prioritize `foo.pyi` over `foo.py`, but
prioritize `foo/__init__.py` over `foo.pyi`.

(I plan to investigate this more closely in follow-up
work. Particularly, to look at other type checkers. It
seems like we may want to change this to always prioritize
stubs.)
2025-08-20 10:27:54 -04:00
Andrew Gallant
a4cd13c6e2 [ty] Expose some routines in the module resolver
We'll want to use these when implementing the
"list modules" API.
2025-08-20 10:27:54 -04:00
Andrew Gallant
e0c98874e2 [ty] Add more path helper functions
This makes it easier to do exhaustive case analysis
on a `SearchPath` depending on whether it is a vendored
or system path.
2025-08-20 10:27:54 -04:00
Andrey
f4be05a83b [flake8-annotations] Remove unused import in example (ANN401) (#20000)
## Summary

Remove unused import in the  "Use instead" example.

## Test Plan

It's just a text description, no test needed
2025-08-20 09:19:18 -04:00
Aria Desires
1d2128f918 [ty] distinguish base conda from child conda (#19990)
This is a port of the logic in https://github.com/astral-sh/uv/pull/7691

The basic idea is we use CONDA_DEFAULT_ENV as a signal for whether
CONDA_PREFIX is just the ambient system conda install, or the user has
explicitly activated a custom one. If the former, then the conda is
treated like a system install (having lowest priority). If the latter,
the conda is treated like an activated venv (having priority over
everything but an Actual activated venv).

Fixes https://github.com/astral-sh/ty/issues/611
2025-08-20 09:07:42 -04:00
Micha Reiser
276405b44e [ty] Fix server hang (#19991) 2025-08-20 10:28:30 +02:00
Dhruv Manilawala
f019cfd15f [ty] Use specialized parameter type for overload filter (#19964)
## Summary

Closes: https://github.com/astral-sh/ty/issues/669

(This turned out to be simpler that I thought :))

## Test Plan

Update existing test cases.

### Ecosystem report

Most of them are basically because ty has now started inferring more
precise types for the return type to an overloaded call and a lot of the
types are defined using type aliases, here's some examples:

<details><summary>Details</summary>
<p>

> attrs (https://github.com/python-attrs/attrs)
> + tests/test_make.py:146:14: error[unresolved-attribute] Type
`Literal[42]` has no attribute `default`
> - Found 555 diagnostics
> + Found 556 diagnostics

This is accurate now that we infer the type as `Literal[42]` instead of
`Unknown` (Pyright infers it as `int`)

> optuna (https://github.com/optuna/optuna)
> + optuna/_gp/search_space.py:181:53: error[invalid-argument-type]
Argument to function `_round_one_normalized_param` is incorrect:
Expected `tuple[int | float, int | float]`, found `tuple[Unknown |
ndarray[Unknown, <class 'float'>], Unknown | ndarray[Unknown, <class
'float'>]]`
> + optuna/_gp/search_space.py:181:83: error[invalid-argument-type]
Argument to function `_round_one_normalized_param` is incorrect:
Expected `int | float`, found `Unknown | ndarray[Unknown, <class
'float'>]`
> + tests/gp_tests/test_search_space.py:109:13:
error[invalid-argument-type] Argument to function
`_unnormalize_one_param` is incorrect: Expected `tuple[int | float, int
| float]`, found `Unknown | ndarray[Unknown, <class 'float'>]`
> + tests/gp_tests/test_search_space.py:110:13:
error[invalid-argument-type] Argument to function
`_unnormalize_one_param` is incorrect: Expected `int | float`, found
`Unknown | ndarray[Unknown, <class 'float'>]`
> - Found 559 diagnostics
> + Found 563 diagnostics

Same as above where ty is now inferring a more precise type like
`Unknown | ndarray[tuple[int, int], <class 'float'>]` instead of just
`Unknown` as before

> jinja (https://github.com/pallets/jinja)
> + src/jinja2/bccache.py:298:39: error[invalid-argument-type] Argument
to bound method `write_bytecode` is incorrect: Expected `IO[bytes]`,
found `_TemporaryFileWrapper[str]`
> - Found 186 diagnostics
> + Found 187 diagnostics

This requires support for type aliases to match the correct overload.

> hydra-zen (https://github.com/mit-ll-responsible-ai/hydra-zen)
> + src/hydra_zen/wrapper/_implementations.py:945:16:
error[invalid-return-type] Return type does not match returned value:
expected `DataClass_ | type[@Todo(type[T] for protocols)] | ListConfig |
DictConfig`, found `@Todo(unsupported type[X] special form) | (((...) ->
Any) & dict[Unknown, Unknown]) | (DataClass_ & dict[Unknown, Unknown]) |
dict[Any, Any] | (ListConfig & dict[Unknown, Unknown]) | (DictConfig &
dict[Unknown, Unknown]) | (((...) -> Any) & list[Unknown]) | (DataClass_
& list[Unknown]) | list[Any] | (ListConfig & list[Unknown]) |
(DictConfig & list[Unknown])`
> + tests/annotations/behaviors.py:60:28: error[call-non-callable]
Object of type `Path` is not callable
> + tests/annotations/behaviors.py:64:21: error[call-non-callable]
Object of type `Path` is not callable
> + tests/annotations/declarations.py:167:17: error[call-non-callable]
Object of type `Path` is not callable
> + tests/annotations/declarations.py:524:17:
error[unresolved-attribute] Type `<class 'int'>` has no attribute
`_target_`
> - Found 561 diagnostics
> + Found 566 diagnostics

Same as above, this requires support for type aliases to match the
correct overload.

> paasta (https://github.com/yelp/paasta)
> + paasta_tools/utils.py:4188:19: warning[redundant-cast] Value is
already of type `list[str]`
> - Found 888 diagnostics
> + Found 889 diagnostics

This is correct.

> colour (https://github.com/colour-science/colour)
> + colour/plotting/diagrams.py:448:13: error[invalid-argument-type]
Argument to bound method `__init__` is incorrect: Expected
`Sequence[@Todo(Support for `typing.TypeAlias`)]`, found
`ndarray[tuple[int, int, int], dtype[Unknown]]`
> + colour/plotting/diagrams.py:462:13: error[invalid-argument-type]
Argument to bound method `__init__` is incorrect: Expected
`Sequence[@Todo(Support for `typing.TypeAlias`)]`, found
`ndarray[tuple[int, int, int], dtype[Unknown]]`
> + colour/plotting/models.py:419:13: error[invalid-argument-type]
Argument to bound method `__init__` is incorrect: Expected
`Sequence[@Todo(Support for `typing.TypeAlias`)]`, found
`ndarray[tuple[int, int, int], dtype[Unknown]]`
> + colour/plotting/temperature.py:230:9: error[invalid-argument-type]
Argument to bound method `__init__` is incorrect: Expected
`Sequence[@Todo(Support for `typing.TypeAlias`)]`, found
`ndarray[tuple[int, int, int], dtype[Unknown]]`
> + colour/plotting/temperature.py:474:13: error[invalid-argument-type]
Argument to bound method `__init__` is incorrect: Expected
`Sequence[@Todo(Support for `typing.TypeAlias`)]`, found
`ndarray[tuple[int, int, int], dtype[Unknown]]`
> + colour/plotting/temperature.py:495:17: error[invalid-argument-type]
Argument to bound method `__init__` is incorrect: Expected
`Sequence[@Todo(Support for `typing.TypeAlias`)]`, found
`ndarray[tuple[int, int, int], dtype[Unknown]]`
> + colour/plotting/temperature.py:513:13: error[invalid-argument-type]
Argument to bound method `text` is incorrect: Expected `int | float`,
found `ndarray[@Todo(Support for `typing.TypeAlias`), dtype[Unknown]]`
> + colour/plotting/temperature.py:514:13: error[invalid-argument-type]
Argument to bound method `text` is incorrect: Expected `int | float`,
found `ndarray[@Todo(Support for `typing.TypeAlias`), dtype[Unknown]]`
> - Found 480 diagnostics
> + Found 488 diagnostics

Most of them are correct except for the last two diagnostics which I'm
not sure
what's happening, it's trying to index into an `np.ndarray` type (which
is
inferred correctly) but I think it might be picking up an incorrect
overload
for the `__getitem__` method.

Scipy's diagnostics also requires support for type alises to pick the
correct overload.

</p>
</details>
2025-08-20 09:39:05 +05:30
Eric Mark Martin
33030b34cd [ty] linear variance inference for PEP-695 type parameters (#18713)
## Summary

Implement linear-time variance inference for type variables
(https://github.com/astral-sh/ty/issues/488).

Inspired by Martin Huschenbett's [PyCon 2025
Talk](https://www.youtube.com/watch?v=7uixlNTOY4s&t=9705s).

## Test Plan

update tests, add new tests, including for mutually recursive classes

---------

Co-authored-by: Carl Meyer <carl@astral.sh>
2025-08-19 17:54:09 -07:00
Alex Waygood
656fc335f2 [ty] Strict validation of protocol members (#17750) 2025-08-19 22:45:41 +00:00
Dan Parizher
e0f4cec7a1 [pyupgrade] Handle nested Optionals (UP045) (#19770)
## Summary

Fixes #19746

---------

Co-authored-by: Brent Westbrook <brentrwestbrook@gmail.com>
2025-08-19 18:12:15 -04:00
Alex Waygood
662d18bd05 [ty] Add precise inference for unpacking a TypeVar if the TypeVar has an upper bound with a precise tuple spec (#19985) 2025-08-19 22:11:30 +01:00
Aria Desires
c82e255ca8 [ty] Fix namespace packages that behave like partial stubs (#19994)
In implementing partial stubs I had observed that this continue in the
namespace package code seemed erroneous since the same continue for
partial stubs didn't work. Unfortunately I wasn't confident enough to
push on that hunch. Fortunately I remembered that hunch to make this an
easy fix.

The issue with the continue is that it bails out of the current
search-path without testing any .py files. This breaks when for example
`google` and `google-stubs`/`types-google` are both in the same
site-packages dir -- failing to find a module in `types-google` has us
completely skip over `google`!

Fixes https://github.com/astral-sh/ty/issues/520
2025-08-19 16:34:39 -04:00
Eric Jolibois
58efd19f11 [ty] apply KW_ONLY sentinel only to local fields (#19986)
fix https://github.com/astral-sh/ty/issues/1047

## Summary

This PR fixes how `KW_ONLY` is applied in dataclasses. Previously, the
sentinel leaked into subclasses and incorrectly marked their fields as
keyword-only; now it only affects fields declared in the same class.

```py
from dataclasses import dataclass, KW_ONLY

@dataclass
class D:
    x: int
    _: KW_ONLY
    y: str

@dataclass
class E(D):
    z: bytes

# This should work: x=1 (positional), z=b"foo" (positional), y="foo" (keyword-only)
E(1, b"foo", y="foo")

reveal_type(E.__init__)  # revealed: (self: E, x: int, z: bytes, *, y: str) -> None
```

<!-- What's the purpose of the change? What does it do, and why? -->

## Test Plan

<!-- How was it tested? -->
mdtests
2025-08-19 11:01:35 -07:00
Aria Desires
c6dcfe36d0 [ty] introduce multiline pretty printer (#19979)
Requires some iteration, but this includes the most tedious part --
threading a new concept of DisplaySettings through every type display
impl. Currently it only holds a boolean for multiline, but in the future
it could also take other things like "render to markdown" or "here's
your base indent if you make a newline".

For types which have exposed display functions I've left the old
signature as a compatibility polyfill to avoid having to audit
everywhere that prints types right off the bat (notably I originally
tried doing multiline functions unconditionally and a ton of things
churned that clearly weren't ready for multi-line (diagnostics).

The only real use of this API in this PR is to multiline render function
types in hovers, which is the highest impact (see snapshot changes).

Fixes https://github.com/astral-sh/ty/issues/1000
2025-08-19 17:31:44 +00:00
Avasam
59b078b1bf Update outdated links to https://typing.python.org/en/latest/source/stubs.html (#19992) 2025-08-19 18:12:08 +01:00
Andrew Gallant
5e943d3539 [ty] Ask the LSP client to watch all project search paths
This change rejiggers how we register globs for file watching with the
LSP client. Previously, we registered a few globs like `**/*.py`,
`**/pyproject.toml` and more. There were two problems with this
approach.

Firstly, it only watches files within the project root. Search paths may
be outside the project root. Such as virtualenv directory.

Secondly, there is variation on how tools interact with virtual
environments. In the case of uv, depending on its link mode, we might
not get any file change notifications after running `uv add foo` or
`uv remove foo`.

To remedy this, we instead just list for file change notifications on
all files for all search paths. This simplifies the globs we use, but
does potentially increase the number of notifications we'll get.
However, given the somewhat simplistic interface supported by the LSP
protocol, I think this is unavoidable (unless we used our own file
watcher, which has its own considerably downsides). Moreover, this is
seemingly consistent with how `ty check --watch` works.

This also required moving file watcher registration to *after*
workspaces are initialized, or else we don't know what the right search
paths are.

This change is in service of #19883, which in order for cache
invalidation to work right, the LSP client needs to send notifications
whenever a dependency is added or removed. This change should make that
possible.

I tried this patch with #19883 in addition to my work to activate Salsa
caching, and everything seems to work as I'd expect. That is,
completions no longer show stale results after a dependency is added or
removed.
2025-08-19 10:57:07 -04:00
renovate[bot]
0967e7e088 Update Rust crate glob to v0.3.3 (#19959)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [glob](https://redirect.github.com/rust-lang/glob) |
workspace.dependencies | patch | `0.3.2` -> `0.3.3` |

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

---

### Release Notes

<details>
<summary>rust-lang/glob (glob)</summary>

###
[`v0.3.3`](https://redirect.github.com/rust-lang/glob/blob/HEAD/CHANGELOG.md#033---2025-08-11)

[Compare
Source](https://redirect.github.com/rust-lang/glob/compare/v0.3.2...v0.3.3)

- Optimize memory allocations
([#&#8203;147](https://redirect.github.com/rust-lang/glob/pull/147))
- Bump the MSRV to 1.63
([#&#8203;172](https://redirect.github.com/rust-lang/glob/pull/172))
- Fix spelling in pattern documentation
([#&#8203;164](https://redirect.github.com/rust-lang/glob/pull/164))
- Fix version numbers and some formatting
([#&#8203;157](https://redirect.github.com/rust-lang/glob/pull/157))
- Style fixes
([#&#8203;137](https://redirect.github.com/rust-lang/glob/pull/137))

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "before 4am on Monday" (UTC),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/astral-sh/ruff).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MS43MS4xIiwidXBkYXRlZEluVmVyIjoiNDEuNzEuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiaW50ZXJuYWwiXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Brent Westbrook <brentrwestbrook@gmail.com>
2025-08-19 10:39:23 -04:00
Alex Waygood
600245478c [ty] Look for site-packages directories in <sys.prefix>/lib64/ as well as <sys.prefix>/lib/ on non-Windows systems (#19978) 2025-08-19 11:53:06 +00:00
Alex Waygood
e5c091b850 [ty] Fix protocol interface inference for stub protocols and subprotocols (#19950) 2025-08-19 10:31:11 +00:00
David Peter
10301f6190 [ty] Enable virtual terminal on Windows (#19984)
## Summary

Should hopefully fix https://github.com/astral-sh/ty/issues/1045
2025-08-19 09:13:03 +00:00
Alex Waygood
4242905b36 [ty] Detect NamedTuple classes where fields without default values follow fields with default values (#19945) 2025-08-19 08:56:08 +00:00
Aria Desires
c20d906503 [ty] improve goto/hover for definitions (#19976)
By computing the actual Definition for, well, definitions, we unlock a
bunch of richer machinery in the goto/hover subsystems for free.

Fixes https://github.com/astral-sh/ty/issues/1001
Fixes https://github.com/astral-sh/ty/issues/1004
2025-08-18 21:42:53 -04:00
Carl Meyer
a04375173c [ty] fix unpacking a type alias with detailed tuple spec (#19981)
## Summary

Fixes https://github.com/astral-sh/ty/issues/1046

We special-case iteration of certain types because they may have a more
detailed tuple-spec. Now that type aliases are a distinct type variant,
we need to handle them as well.

I don't love that `Type::TypeAlias` means we have to remember to add a
case for it basically anywhere we are special-casing a certain kind of
type, but at the moment I don't have a better plan. It's another
argument for avoiding fallback cases in `Type` matches, which we usually
prefer; I've updated this match statement to be comprehensive.

## Test Plan

Added mdtest.
2025-08-18 17:54:05 -07:00
Alex Waygood
e6dcdd29f2 [ty] Add a Todo-type branch for type[P] where P is a protocol class (#19947) 2025-08-18 20:38:19 +00:00
Matthew Mckee
24f6d2dc13 [ty] Infer the correct type of Enum __eq__ and __ne__ comparisions (#19666)
## Summary

Resolves https://github.com/astral-sh/ty/issues/920

## Test Plan

Update `enums.md`

---------

Co-authored-by: David Peter <mail@david-peter.de>
2025-08-18 19:45:44 +02:00
Alex Waygood
3314cf90ed [ty] Add more regression tests for tuple (#19974) 2025-08-18 18:30:05 +01:00
Aria Desires
0cb1abc1fc [ty] Implement partial stubs (#19931)
Fixes https://github.com/astral-sh/ty/issues/184
2025-08-18 13:14:13 -04:00
Brent Westbrook
f6491cacd1 Add full output format changes to the changelog (#19968)
Summary
--

I thought this might warrant a small blog-style writeup, especially
since we already got a question about it (#19966), but I'm happy to
switch back to a one-liner under `### Other changes` if preferred.

I'll copy whatever we add here to the release notes too.

Do we need a note at the top about the late addition?
2025-08-18 11:46:16 -04:00
Alex Waygood
e4f1b587cc Upgrade mypy_primer pin (#19967) 2025-08-18 13:27:54 +01:00
Alex Waygood
fbf24be8ae [ty] Detect illegal multiple inheritance with NamedTuple (#19943) 2025-08-18 12:03:01 +00:00
Micha Reiser
5e4fa9e442 [ty] Speedup tracing checks (#19965) 2025-08-18 12:56:06 +02:00
Micha Reiser
67529edad6 [ty] Short-circuit inlayhints request if disabled in settings (#19963) 2025-08-18 10:35:40 +00:00
Alex Waygood
4ac2b2c222 [ty] Have SemanticIndex::place_table() and SemanticIndex::use_def_map return references (#19944) 2025-08-18 11:30:52 +01:00
renovate[bot]
083bb85d9d Update actions/checkout to v5.0.0 (#19952)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Micha Reiser <micha@reiser.io>
2025-08-18 07:31:07 +00:00
Micha Reiser
c7af595fc1 [ty] Use debug builds for conformance tests and run them single threaded (#19938) 2025-08-18 07:20:49 +00:00
Micha Reiser
7d8f7c20da [ty] Log server version at info level (#19961) 2025-08-18 07:16:53 +00:00
renovate[bot]
76c933d10e Update dependency ruff to v0.12.9 (#19954)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-18 08:54:23 +02:00
renovate[bot]
d423191d94 Update Rust crate bitflags to v2.9.2 (#19957)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-18 08:54:09 +02:00
renovate[bot]
c8d155b2b9 Update Rust crate clap to v4.5.45 (#19958)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-18 08:53:51 +02:00
renovate[bot]
a5339a52c3 Update Rust crate libc to v0.2.175 (#19960)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-18 08:53:31 +02:00
renovate[bot]
48772c04d7 Update Rust crate anyhow to v1.0.99 (#19956)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-18 08:53:10 +02:00
renovate[bot]
510a07dee2 Update PyO3/maturin-action action to v1.49.4 (#19955)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-18 08:44:00 +02:00
gkowzan
47d44e5f7b Fix description of global config file discovery strategy (#19143) (#19188)
Contrary to docs, ruff uses etcetera's base strategy rather than the
native strategy.
2025-08-17 18:35:37 -05:00
Alex Waygood
ec3163781c [ty] Remove unused code (#19949) 2025-08-17 18:54:24 +01:00
Douglas Creager
b892e4548e [ty] Track when type variables are inferable or not (#19786)
`Type::TypeVar` now distinguishes whether the typevar in question is
inferable or not.

A typevar is _not inferable_ inside the body of the generic class or
function that binds it:

```py
def f[T](t: T) -> T:
    return t
```

The infered type of `t` in the function body is `TypeVar(T,
NotInferable)`. This represents how e.g. assignability checks need to be
valid for all possible specializations of the typevar. Most of the
existing assignability/etc logic only applies to non-inferable typevars.

Outside of the function body, the typevar is _inferable_:

```py
f(4)
```

Here, the parameter type of `f` is `TypeVar(T, Inferable)`. This
represents how e.g. assignability doesn't need to hold for _all_
specializations; instead, we need to find the constraints under which
this specific assignability check holds.

This is in support of starting to perform specialization inference _as
part of_ performing the assignability check at the call site.

In the [[POPL2015][]] paper, this concept is called _monomorphic_ /
_polymorphic_, but I thought _non-inferable_ / _inferable_ would be
clearer for us.

Depends on #19784 

[POPL2015]: https://doi.org/10.1145/2676726.2676991

---------

Co-authored-by: Carl Meyer <carl@astral.sh>
2025-08-16 18:25:03 -04:00
Alex Waygood
9ac39cee98 [ty] Ban protocols from inheriting from non-protocol generic classes (#19941) 2025-08-16 19:38:43 +01:00
Alex Waygood
f4d8826428 [ty] Fix error message for invalidly providing type arguments to NamedTuple when it occurs in a type expression (#19940) 2025-08-16 17:45:15 +00:00
Micha Reiser
527a690a73 [ty] Fix example in environment docs (#19937) 2025-08-16 14:37:28 +00:00
Dan Parizher
f0e9c1d8f9 [isort] Handle multiple continuation lines after module docstring (I002) (#19818)
## Summary

Fixes #19815

---------

Co-authored-by: Brent Westbrook <36778786+ntBre@users.noreply.github.com>
2025-08-15 17:17:50 -04:00
Frazer McLean
2e1d6623cd [flake8-simplify] Implement fix for maxsplit without separator (SIM905) (#19851)
**Stacked on top of #19849; diff will include that PR until it is
merged.**

---

<!--
Thank you for contributing to Ruff/ty! To help us out with reviewing,
please consider the following:

- Does this pull request include a summary of the change? (See below.)
- Does this pull request include a descriptive title? (Please prefix
with `[ty]` for ty pull
  requests.)
- Does this pull request include references to any relevant issues?
-->

## Summary

As part of #19849, I noticed this fix could be implemented.

## Test Plan

Tests added based on CPython behaviour.
2025-08-15 15:18:06 -04:00
Dan Parizher
2dc2f68b0f [pycodestyle] Make E731 fix unsafe instead of display-only for class assignments (#19700)
## Summary

Fixes #19650

---------

Co-authored-by: Brent Westbrook <brentrwestbrook@gmail.com>
2025-08-15 19:09:55 +00:00
Alex Waygood
26d6c3831f [ty] Represent NamedTuple as an opaque special form, not a class (#19915) 2025-08-15 18:20:14 +01:00
Alex Waygood
9ced219ffc [ty] Remove incorrect type narrowing for if type(x) is C[int] (#19926) 2025-08-15 17:52:14 +01:00
Micha Reiser
f344dda82c Bump Rust MSRV to 1.87 (#19924) 2025-08-15 17:55:38 +02:00
Alex Waygood
6de84ed56e Add else-branch narrowing for if type(a) is A when A is @final (#19925) 2025-08-15 14:52:30 +01:00
github-actions[bot]
bd4506aac5 [ty] Sync vendored typeshed stubs (#19923)
Close and reopen this PR to trigger CI

---------

Co-authored-by: typeshedbot <>
Co-authored-by: Carl Meyer <carl@astral.sh>
2025-08-14 18:09:35 -07:00
Shunsuke Shibayama
0e5577ab56 [ty] fix lazy snapshot sweeping in nested scopes (#19908)
## Summary

This PR closes astral-sh/ty#955.

## Test Plan

New test cases in `narrowing/conditionals/nested.md`.
2025-08-14 17:52:52 -07:00
Andrii Turov
957320c0f1 [ty] Add diagnostics for invalid await expressions (#19711)
## Summary

This PR adds a new lint, `invalid-await`, for all sorts of reasons why
an object may not be `await`able, as discussed in astral-sh/ty#919.
Precisely, `__await__` is guarded against being missing, possibly
unbound, or improperly defined (expects additional arguments or doesn't
return an iterator).

Of course, diagnostics need to be fine-tuned. If `__await__` cannot be
called with no extra arguments, it indicates an error (or a quirk?) in
the method signature, not at the call site. Without any doubt, such an
object is not `Awaitable`, but I feel like talking about arguments for
an *implicit* call is a bit leaky.
I didn't reference any actual diagnostic messages in the lint
definition, because I want to hear feedback first.

Also, there's no mention of the actual required method signature for
`__await__` anywhere in the docs. The only reference I had is the
`typing` stub. I basically ended up linking `[Awaitable]` to ["must
implement
`__await__`"](https://docs.python.org/3/library/collections.abc.html#collections.abc.Awaitable),
which is insufficient on its own.

## Test Plan

The following code was tested:
```python
import asyncio
import typing


class Awaitable:
    def __await__(self) -> typing.Generator[typing.Any, None, int]:
        yield None
        return 5


class NoDunderMethod:
    pass


class InvalidAwaitArgs:
    def __await__(self, value: int) -> int:
        return value


class InvalidAwaitReturn:
    def __await__(self) -> int:
        return 5


class InvalidAwaitReturnImplicit:
    def __await__(self):
        pass


async def main() -> None:
    result = await Awaitable()  # valid
    result = await NoDunderMethod()  # `__await__` is missing
    result = await InvalidAwaitReturn()  # `__await__` returns `int`, which is not a valid iterator 
    result = await InvalidAwaitArgs()  # `__await__` expects additional arguments and cannot be called implicitly
    result = await InvalidAwaitReturnImplicit()  # `__await__` returns `Unknown`, which is not a valid iterator


asyncio.run(main())
```

---------

Co-authored-by: Carl Meyer <carl@astral.sh>
2025-08-14 14:38:33 -07:00
Alex Waygood
f6093452ed [ty] Synthesize read-only properties for all declared members on NamedTuple classes (#19899) 2025-08-14 21:25:45 +00:00
Alex Waygood
82350a398e [ty] Remove use of ClassBase::try_from_type from super() machinery (#19902) 2025-08-14 22:14:31 +01:00
Micha Reiser
ce938fe205 [ty] Speedup project file discovery (#19913) 2025-08-14 19:38:39 +01:00
Brent Westbrook
7f8f1ab2c1 [pyflakes] Add secondary annotation showing previous definition (F811) (#19900)
## Summary

This is a second attempt at a first use of a new diagnostic feature
after #19886. I'll blame rustc for this one because it also has a
similar diagnostic:

<img width="735" height="335" alt="image"
src="https://github.com/user-attachments/assets/572fe1c3-1742-4ce4-b575-1d9196ff0932"
/>

We end up with a very similar diagnostic:

<img width="764" height="401" alt="image"
src="https://github.com/user-attachments/assets/01eaf0c7-2567-467b-a5d8-a27206b2c74c"
/>

## Test Plan

New snapshots and manual tests above
2025-08-14 13:23:43 -04:00
Brent Westbrook
ef422460de Bump 0.12.9 (#19917) 2025-08-14 11:54:44 -04:00
justin
dc2e8ab377 [ty] support kw_only=True for dataclass() and field() (#19677)
## Summary
https://github.com/astral-sh/ty/issues/111

adds support for `@dataclass(kw_only=True)`
(https://docs.python.org/3/library/dataclasses.html)

## Test Plan
- new mdtests
- triaged conformance diffs (notes here:
https://diffswarm.dev/d-01k2gknwyq82f6x17zqf3apjxc)
- `mypy_primer` no-op
2025-08-14 08:02:55 -07:00
ffgan
9aaa82d037 Feature/build riscv64 bin (#19819) 2025-08-14 16:11:14 +02:00
Alex Waygood
3288ac2dfb [ty] Add caching to CodeGeneratorKind::matches() (#19912) 2025-08-14 11:54:11 +01:00
Dhruv Manilawala
1167ed61cf [ty] Rename functionArgumentNames to callArgumentNames inlay hint setting (#19911)
## Summary

This PR renames `ty.inlayHints.functionArgumentNames` to
`ty.inlayHints.callArgumentNames` which would contain both function
calls and class initialization calls i.e., it represents a generic call
expression.
2025-08-14 14:21:38 +05:30
Dhruv Manilawala
2ee47d87b6 [ty] Default ty.inlayHints.* server settings to true (#19910)
## Summary

This PR changes the default of `ty.inlayHints.*` settings to `true`.

I somehow missed this in my initial PR.

This is marked as `internal` because it's not yet released.
2025-08-14 14:12:03 +05:30
Alex Waygood
d324cedfc2 [ty] Remove py-fuzzer skips for seeds that are no longer slow (#19906) 2025-08-14 00:23:45 +01:00
Carl Meyer
5a570c8e6d [ty] fix deferred name loading in PEP695 generic classes/functions (#19888)
## Summary

For PEP 695 generic functions and classes, there is an extra "type
params scope" (a child of the outer scope, and wrapping the body scope)
in which the type parameters are defined; class bases and function
parameter/return annotations are resolved in that type-params scope.

This PR fixes some longstanding bugs in how we resolve name loads from
inside these PEP 695 type parameter scopes, and also defers type
inference of PEP 695 typevar bounds/constraints/default, so we can
handle cycles without panicking.

We were previously treating these type-param scopes as lazy nested
scopes, which is wrong. In fact they are eager nested scopes; the class
`C` here inherits `int`, not `str`, and previously we got that wrong:

```py
Base = int

class C[T](Base): ...

Base = str
```

But certain syntactic positions within type param scopes (typevar
bounds/constraints/defaults) are lazy at runtime, and we should use
deferred name resolution for them. This also means they can have cycles;
in order to handle that without panicking in type inference, we need to
actually defer their type inference until after we have constructed the
`TypeVarInstance`.

PEP 695 does specify that typevar bounds and constraints cannot be
generic, and that typevar defaults can only reference prior typevars,
not later ones. This reduces the scope of (valid from the type-system
perspective) cycles somewhat, although cycles are still possible (e.g.
`class C[T: list[C]]`). And this is a type-system-only restriction; from
the runtime perspective an "invalid" case like `class C[T: T]` actually
works fine.

I debated whether to implement the PEP 695 restrictions as a way to
avoid some cycles up-front, but I ended up deciding against that; I'd
rather model the runtime name-resolution semantics accurately, and
implement the PEP 695 restrictions as a separate diagnostic on top.
(This PR doesn't yet implement those diagnostics, thus some `# TODO:
error` in the added tests.)

Introducing the possibility of cyclic typevars made typevar display
potentially stack overflow. For now I've handled this by simply removing
typevar details (bounds/constraints/default) from typevar display. This
impacts display of two kinds of types. If you `reveal_type(T)` on an
unbound `T` you now get just `typing.TypeVar` instead of
`typing.TypeVar("T", ...)` where `...` is the bound/constraints/default.
This matches pyright and mypy; pyrefly uses `type[TypeVar[T]]` which
seems a bit confusing, but does include the name. (We could easily
include the name without cycle issues, if there's a syntax we like for
that.)

It also means that displaying a generic function type like `def f[T:
int](x: T) -> T: ...` now displays as `f[T](x: T) -> T` instead of `f[T:
int](x: T) -> T`. This matches pyright and pyrefly; mypy does include
bound/constraints/defaults of typevars in function/callable type
display. If we wanted to add this, we would either need to thread a
visitor through all the type display code, or add a `decycle` type
transformation that replaced recursive reoccurrence of a type with a
marker.

## Test Plan

Added mdtests and modified existing tests to improve their correctness.

After this PR, there's only a single remaining py-fuzzer seed in the
0-500 range that panics! (Before this PR, there were 10; the fuzzer
likes to generate cyclic PEP 695 syntax.)

## Ecosystem report

It's all just the changes to `TypeVar` display.
2025-08-13 15:51:59 -07:00
Douglas Creager
baadb5a78d [ty] Add some additional type safety to CycleDetector (#19903)
This PR adds a type tag to the `CycleDetector` visitor (and its
aliases).

There are some places where we implement e.g. an equivalence check by
making a disjointness check. Both `is_equivalent_to` and
`is_disjoint_from` use a `PairVisitor` to handle cycles, but they should
not use the same visitor. I was finding it tedious to remember when it
was appropriate to pass on a visitor and when not to. This adds a
`PhantomData` type tag to ensure that we can't pass on one method's
visitor to a different method.

For `has_relation` and `apply_type_mapping`, we have an existing type
that we can use as the tag. For the other methods, I've added empty
structs (`Normalized`, `IsDisjointFrom`, `IsEquivalentTo`) to use as
tags.
2025-08-13 17:32:35 -04:00
Roman Kitaev
df0648aae0 [flake8-blind-except] Fix BLE001 false-positive on raise ... from None (#19755)
## Summary

- Refactored `BLE001` logic for clarity and minor speed-up.
- Improved documentation and comments (previously, `BLE001` docs claimed
it catches bare `except:`s, but it doesn't).
- Fixed a false-positive bug with `from None` cause:

```python
# somefile.py

try:
    pass
except BaseException as e:
    raise e from None
```

### main branch
```
somefile.py:3:8: BLE001 Do not catch blind exception: `BaseException`
  |
1 | try:
2 |     pass
3 | except BaseException as e:
  |        ^^^^^^^^^^^^^ BLE001
4 |     raise e from None
  |

Found 1 error.
```

### this change

```cargo run -p ruff -- check somefile.py --no-cache --select=BLE001```

```
All checks passed!
```

## Test Plan

- Added a test case to cover `raise X from Y` clause
- Added a test case to cover `raise X from None` clause
2025-08-13 13:01:47 -04:00
Aria Desires
f0b03c3e86 [ty] resolve docstrings for modules (#19898)
This also reintroduces the `ResolvedDefinition::Module` variant because
reverse-engineering it in several places is a bit confusing. In an ideal
world we wouldn't have `ResolvedDefinition::FileWithRange` as it kinda
kills the ability to do richer analysis, so I want to chip away at its
scope wherever I can (currently it's used to point at asname parts of
import statements when doing `ImportAliasResolution::PreserveAliases`,
and also keyword arguments).

This also makes a kind of odd change to allow a hover to *only* produce
a docstring. This works around an oddity where hovering over a module
name in an import fails to resolve to a `ty` even though hovering over
uses of that imported name *does*.

The two fixed tests reflect the two interesting cases here.
2025-08-13 12:24:01 -04:00
Alex Waygood
9f6146a13d [ty] Add precise inference for indexing, slicing and unpacking NamedTuple instances (#19560)
Co-authored-by: Brent Westbrook <brentrwestbrook@gmail.com>
2025-08-13 15:19:44 +00:00
Brent Westbrook
11d2cb6d56 Add rule code to GitLab description (#19896)
## Summary

Fixes #19881. While I was here, I also made a couple of related tweaks
to the output format. First, we don't need to strip the `SyntaxError: `
prefix anymore since that's not added directly to the diagnostic message
after #19644. Second, we can use `secondary_code_or_id` to fall back on
the lint ID for syntax errors, which changes the `check_name` from
`syntax-error` to `invalid-syntax`. And then the main change requested
in the issue, prepending the `check_name` to the description.

## Test Plan

Existing tests and a new screenshot from GitLab:

<img width="362" height="113" alt="image"
src="https://github.com/user-attachments/assets/97654ad4-a639-4489-8c90-8661c7355097"
/>
2025-08-13 11:19:26 -04:00
Aria Desires
d59282ebb5 [ty] render docstrings in hover (#19882)
This PR has several components:

* Introduce a Docstring String wrapper type that has render_plaintext
and render_markdown methods, to force docstring handlers to pick a
rendering format
* Implement [PEP-257](https://peps.python.org/pep-0257/) docstring
trimming for it
* The markdown rendering just renders the content in a plaintext
codeblock for now (followup work)
* Introduce a `DefinitionsOrTargets` type representing the partial
evaluation of `GotoTarget::get_definition_targets` to ideally stop at
getting `ResolvedDefinitions`
* Add `declaration_targets`, `definition_targets`, and `docstring`
methods to `DefinitionsOrTargets` for the 3 usecases we have for this
operation
* `docstring` is of course the key addition here, it uses the same basic
logic that `signature_help` was using: first check the goto-declaration
for docstrings, then check the goto-definition for docstrings.
* Refactor `signature_help` to use the new APIs instead of implementing
it itself
* Not fixed in this PR: an issue I found where `signature_help` will
erroneously cache docs between functions that have the same type (hover
docs don't have this bug)
* A handful of new tests and additions to tests to add docstrings in
various places and see which get caught


Examples of it working with stdlib, third party, and local definitions:
<img width="597" height="120" alt="Screenshot 2025-08-12 at 2 13 55 PM"
src="https://github.com/user-attachments/assets/eae54efd-882e-4b50-b5b4-721595224232"
/>
<img width="598" height="281" alt="Screenshot 2025-08-12 at 2 14 06 PM"
src="https://github.com/user-attachments/assets/5c9740d5-a06b-4c22-9349-da6eb9a9ba5a"
/>
<img width="327" height="180" alt="Screenshot 2025-08-12 at 2 14 18 PM"
src="https://github.com/user-attachments/assets/3b5647b9-2cdd-4c5b-bb7d-da23bff1bcb5"
/>

Notably modules don't work yet (followup work):
<img width="224" height="83" alt="Screenshot 2025-08-12 at 2 14 37 PM"
src="https://github.com/user-attachments/assets/7e9dcb70-a10e-46d9-a85c-9fe52c3b7e7b"
/>

Notably we don't show docs for an item if you hover its actual
definition (followup work, but also, not the most important):
<img width="324" height="69" alt="Screenshot 2025-08-12 at 2 16 54 PM"
src="https://github.com/user-attachments/assets/d4ddcdd8-c3fc-4120-ac93-cefdf57933b4"
/>
2025-08-13 14:59:20 +00:00
Carl Meyer
e12747a903 [ty] simplify return type of place_from_declarations (#19884)
## Summary

A [passing
comment](https://github.com/astral-sh/ruff/pull/19711#issuecomment-3169312014)
led me to explore why we didn't report a class attribute as possibly
unbound if it was a method and defined in two different conditional
branches.

I found that the reason was because of our handling of "conflicting
declarations" in `place_from_declarations`. It returned a `Result` which
would be `Err` in case of conflicting declarations.

But we only actually care about conflicting declarations when we are
actually doing type inference on that scope and might emit a diagnostic
about it. And in all cases (including that one), we want to otherwise
proceed with the union of the declared types, as if there was no
conflict.

In several cases we were failing to handle the union of declared types
in the same way as a normal declared type if there was a declared-types
conflict. The `Result` return type made this mistake really easy to
make, as we'd match on e.g. `Ok(Place::Type(...))` and do one thing,
then match on `Err(...)` and do another, even though really both of
those cases should be handled the same.

This PR refactors `place_from_declarations` to instead return a struct
which always represents the declared type we should use in the same way,
as well as carrying the conflicting declared types, if any. This struct
has a method to allow us to explicitly ignore the declared-types
conflict (which is what we want in most cases), as well as a method to
get the declared type and the conflict information, in the case where we
want to emit a diagnostic on the conflict.

## Test Plan

Existing CI; added a test showing that we now understand a
multiply-conditionally-defined method as possibly-unbound.

This does trigger issues on a couple new fuzzer seeds, but the issues
are just new instances of an already-known (and rarely occurring)
problem which I already plan to address in a future PR, so I think it's
OK to land as-is.

I happened to build this initially on top of
https://github.com/astral-sh/ruff/pull/19711, which adds invalid-await
diagnostics, so I also updated some invalid-syntax tests to not await on
an invalid type, since the purpose of those tests is to check the
syntactic location of the `await`, not the validity of the awaited type.
2025-08-13 14:17:08 +00:00
Alex Waygood
5725c4b17f [ty] Various minor cleanups to tuple internals (#19891) 2025-08-13 13:46:22 +00:00
Alex Waygood
2f3c7ad1fc [ty] Improve sys.version_info special casing (#19894) 2025-08-13 14:39:13 +01:00
Brent Westbrook
79c949f0f7 Don't cache files with diagnostics (#19869)
Summary
--

To take advantage of the new diagnostics, we need to update our caching
model to include all of the information supported by `ruff_db`'s
diagnostic type. Instead of trying to serialize all of this information,
Micha suggested simply not caching files with diagnostics, like we
already do for files with syntax errors. This PR is an attempt at that
approach.

This has the added benefit of trimming down our `Rule` derives since
this was the last place the `FromStr`/`strum_macros::EnumString`
implementation was used, as well as the (de)serialization macros and
`CacheKey`.

Test Plan
--

Existing tests, with their input updated not to include a diagnostic,
plus a new test showing that files with lint diagnostics are not cached.

Benchmarks
--

In addition to tests, we wanted to check that this doesn't degrade
performance too much. I posted part of this new analysis in
https://github.com/astral-sh/ruff/issues/18198#issuecomment-3175048672,
but I'll duplicate it here. In short, there's not much difference
between `main` and this branch for projects with few diagnostics
(`home-assistant`, `airflow`), as expected. The difference for projects
with many diagnostics (`cpython`) is quite a bit bigger (~300 ms vs ~220
ms), but most projects that run ruff regularly are likely to have very
few diagnostics, so this may not be a problem practically.

I guess GitHub isn't really rendering this as I intended, but the extra
separator line is meant to separate the benchmarks on `main` (above the
line) from this branch (below the line).

| Command | Mean [ms] | Min [ms] | Max [ms] |

|:--------------------------------------------------------------|----------:|---------:|---------:|
| `ruff check cpython --no-cache --isolated --exit-zero` | 322.0 | 317.5
| 326.2 |
| `ruff check cpython --isolated --exit-zero` | 217.3 | 209.8 | 237.9 |
| `ruff check home-assistant --no-cache --isolated --exit-zero` | 279.5
| 277.0 | 283.6 |
| `ruff check home-assistant --isolated --exit-zero` | 37.2 | 35.7 |
40.6 |
| `ruff check airflow --no-cache --isolated --exit-zero` | 133.1 | 130.4
| 146.4 |
| `ruff check airflow --isolated --exit-zero` | 34.7 | 32.9 | 41.6 |

|:--------------------------------------------------------------|----------:|---------:|---------:|
| `ruff check cpython --no-cache --isolated --exit-zero` | 330.1 | 324.5
| 333.6 |
| `ruff check cpython --isolated --exit-zero` | 309.2 | 306.1 | 314.7 |
| `ruff check home-assistant --no-cache --isolated --exit-zero` | 288.6
| 279.4 | 302.3 |
| `ruff check home-assistant --isolated --exit-zero` | 39.8 | 36.9 |
42.4 |
| `ruff check airflow --no-cache --isolated --exit-zero` | 134.5 | 131.3
| 140.6 |
| `ruff check airflow --isolated --exit-zero` | 39.1 | 37.2 | 44.3 |

I had Claude adapt one of the
[scripts](https://github.com/sharkdp/hyperfine/blob/master/scripts/plot_whisker.py)
from the hyperfine repo to make this plot, so it's not quite perfect,
but maybe it's still useful. The table is probably more reliable for
close comparisons. I'll put more details about the benchmarks below for
the sake of future reproducibility.

<img width="4472" height="2368" alt="image"
src="https://github.com/user-attachments/assets/1c42d13e-818a-44e7-b34c-247340a936d7"
/>

<details><summary>Benchmark details</summary>
<p>

The versions of each project:
- CPython: 6322edd260e8cad4b09636e05ddfb794a96a0451, the 3.10 branch
from the contributing docs
- `home-assistant`: 5585376b406f099fb29a970b160877b57e5efcb0
- `airflow`: 29a1cb0cfde9d99b1774571688ed86cb60123896

The last two are just the main branches at the time I cloned the repos.

I don't think our Ruff config should be applied since I used
`--isolated`, but these are cloned into my copy of Ruff at
`crates/ruff_linter/resources/test`, and I trimmed the
`./target/release/` prefix from each of the commands, but these are
builds of Ruff in release mode.

And here's the script with the `hyperfine` invocation:

```shell
#!/bin/bash

cargo build --release --bin ruff

# git clone --depth 1 https://github.com/home-assistant/core crates/ruff_linter/resources/test/home-assistant
# git clone --depth 1 https://github.com/apache/airflow crates/ruff_linter/resources/test/airflow

bin=./target/release/ruff
resources=./crates/ruff_linter/resources/test
cpython=$resources/cpython
home_assistant=$resources/home-assistant
airflow=$resources/airflow

base=${1:-bench}

hyperfine --warmup 10 --export-json $base.json --export-markdown $base.md \
		  "$bin check $cpython --no-cache --isolated --exit-zero" \
		  "$bin check $cpython --isolated --exit-zero" \
		  "$bin check $home_assistant --no-cache --isolated --exit-zero" \
		  "$bin check $home_assistant --isolated --exit-zero" \
		  "$bin check $airflow --no-cache --isolated --exit-zero" \
		  "$bin check $airflow --isolated --exit-zero"
```

I ran this once on `main` (`baseline` in the graph, top half of the
table) and once on this branch (`nocache` and bottom of the table).

</p>
</details>
2025-08-12 15:28:44 -04:00
Carl Meyer
13bdba5d28 [ty] support recursive type aliases (#19805)
## Summary

Support recursive type aliases by adding a `Type::TypeAlias` type
variant, which allows referring to a type alias directly as a type
without eagerly unpacking it to its value.

We still unpack type aliases when they are added to intersections and
unions, so that we can simplify the intersection/union appropriately
based on the unpacked value of the type alias.

This introduces new possible recursive types, and so also requires
expanding our usage of recursion-detecting visitors in Type methods. The
use of these visitors is still not fully comprehensive in this PR, and
will require further expansion to support recursion in more kinds of
types (I already have further work on this locally), but I think it may
be better to do this incrementally in multiple PRs.

## Test Plan

Added some recursive type-alias tests and made them pass.
2025-08-12 09:03:10 -07:00
Alex Waygood
d76fd103ae [ty] Remove unsafe salsa::Update implementations in tuple.rs (#19880) 2025-08-12 15:53:34 +01:00
Matthew Mckee
ad28b80f96 [ty] Function argument inlay hints (#19269) 2025-08-12 13:56:54 +00:00
Alex Waygood
3458f365da [ty] Remove Salsa interning for TypedDictType (#19879) 2025-08-12 14:35:26 +01:00
Harutaka Kawamura
94cfdf4b40 Fix lint.future-annotations link (#19876) 2025-08-12 14:45:06 +02:00
Alex Waygood
498a04804d [ty] Reduce memory usage of TupleSpec and TupleType (#19872) 2025-08-12 12:51:16 +01:00
Ibraheem Ahmed
f34b65b7a0 [ty] Track heap usage of salsa structs (#19790)
Co-authored-by: Micha Reiser <micha@reiser.io>
2025-08-12 13:28:44 +02:00
Micha Reiser
6a05d46ef6 Update salsa to pull in tracked struct changes (#19843) 2025-08-12 13:17:46 +02:00
Carl Meyer
28820db1cd [ty] simplify CycleDetector::visit signature (#19873)
## Summary

After https://github.com/astral-sh/ruff/pull/19871, I realized that now
that we are passing around shared references to `CycleDetector`
visitors, we can now also simplify the `visit` callback signature; we
don't need to smuggle a single visitor reference through it anymore.
This is a pretty minor simplification, and it doesn't really make
anything shorter since I typically used a very short name (`v`) for the
smuggled reference, but I think it reduces cognitive overhead in reading
these `visit` usages; the extra variable would likely be confusing
otherwise for a reader.

## Test Plan

Existing CI.
2025-08-11 17:12:26 -07:00
Carl Meyer
ea1aa9ebfe [ty] use interior mutability in type visitors (#19871)
## Summary

Type visitors are conceptually immutable, they just internally track the
types they've seen (and some maintain a cache of results.) Passing
around mutable visitors everywhere can get us into borrow-checker
trouble in some cases, where we need to recursively pass along the
visitor inside more than one closure with non-disjoint lifetime.

Use interior mutability (via `RefCell` and `Cell`) inside the visitors
instead, to allow us to pass around shared references.

## Test Plan

Existing tests.
2025-08-11 15:42:53 -07:00
Anh-Dung Nguyen
e72f10be2d [ty] Fix tool name is None when no ty path is given in ty_benchmark (#19870)
## Summary

When running the ty_benchmark, I found out that the Ty Tool name is None
when no ty_path is given as str(None)='None'
<img width="1011" height="168" alt="image"
src="https://github.com/user-attachments/assets/cf3e6d98-2329-48e9-b180-c72e4f01ccb6"
/>

## Test Plan
Minor fix, tested local
<img width="1105" height="218" alt="image"
src="https://github.com/user-attachments/assets/173128c9-dcfa-49f1-a58d-1b39a6c6b53b"
/>
2025-08-11 21:26:30 +00:00
Alex Waygood
d2fbf2af8f [ty] Remove Type::Tuple (#19669) 2025-08-11 22:03:32 +01:00
Micha Reiser
2abd683376 [ty] Short circuit ReachabilityConstraints::analyze_single for dynamic types (#19867) 2025-08-11 21:58:34 +02:00
Douglas Creager
dc84645c36 [ty] Use separate Rust types for bound and unbound type variables (#19796)
This PR creates separate Rust types for bound and unbound type
variables, as proposed in https://github.com/astral-sh/ty/issues/926.

Closes https://github.com/astral-sh/ty/issues/926

---------

Co-authored-by: Carl Meyer <carl@astral.sh>
2025-08-11 15:29:58 -04:00
Alex Waygood
f3f4db7104 [ty] Add static-frame as a walltime benchmark (#19844) 2025-08-11 15:38:56 +01:00
Matthew Mckee
5063a73d7f [ty] Update goto range for attribute access to only target the attribute (#19848) 2025-08-11 16:24:14 +02:00
Sneha Prabhu
6bc52f2855 Add AIR301 rule (#17707)
<!--
Thank you for contributing to Ruff! To help us out with reviewing,
please consider the following:

- Does this pull request include a summary of the change? (See below.)
- Does this pull request include a descriptive title?
- Does this pull request include references to any relevant issues?
-->

## Summary

Add "airflow.secrets.cache.SecretCache" →
"airflow.sdk.cache.SecretCache" rule

<!-- What's the purpose of the change? What does it do, and why? -->

## Test Plan

<!-- How was it tested? -->

---------

Co-authored-by: Wei Lee <weilee.rx@gmail.com>
2025-08-11 09:14:43 -04:00
Brent Westbrook
c433865801 Avoid underflow in default ranges before a BOM (#19839)
Summary
--

This fixes a regression caused by the BOM handling in #19806. Most
diagnostics already account for the BOM in their ranges, but those that
use `TextRange::default` to mean the beginning of the file do not,
causing an underflow in `RenderableAnnotation::new` when subtracting the
BOM-shifted `snippet_start` from the annotation range.

I ran into this when trying to run benchmarks on CPython in preparation
for caching work. The file `cpython/Lib/test/bad_coding2.py` was causing
a crash because it had a default-range `I002` diagnostic, with a BOM.


7cc3f1ebe9/crates/ruff_linter/src/rules/isort/rules/add_required_imports.rs (L122-L126)

The fix here is just to saturate to zero instead of panicking. I
considered adding a `TextRange::saturating_sub` method, but I wasn't
sure it was worth it for this one use. I'm happy to do that if
preferred, though.

Saturating seemed easier than shifting the affected annotations over,
but that could be another solution.

Test Plan
--

A new `ruff_db` test that reproduced the issue and manual testing
against the CPython file mentioned above
2025-08-11 08:52:27 -04:00
renovate[bot]
5b6d0d17f1 Update actions/download-artifact digest to de96f46 (#19852)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Micha Reiser <micha@reiser.io>
2025-08-11 06:34:09 +00:00
renovate[bot]
5124cb393f Update docker/login-action action to v3.5.0 (#19860)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-11 08:33:58 +02:00
renovate[bot]
11eb8d8f9f Update rui314/setup-mold digest to 7344740 (#19853)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-11 08:33:39 +02:00
renovate[bot]
37617d1e37 Update cargo-bins/cargo-binstall action to v1.14.4 (#19855)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-11 08:33:30 +02:00
renovate[bot]
14f6a3f133 Update actions/cache action to v4.2.4 (#19854)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-11 08:32:58 +02:00
renovate[bot]
ec65ca379d Update Rust crate hashbrown to v0.15.5 (#19858)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-11 08:26:45 +02:00
renovate[bot]
02c0db6781 Update Rust crate camino to v1.1.11 (#19857)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-11 08:25:44 +02:00
renovate[bot]
18f2b27a55 Update Rust crate proc-macro2 to v1.0.96 (#19859)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-11 08:25:34 +02:00
renovate[bot]
618692cfd2 Update dependency ruff to v0.12.8 (#19856)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-11 08:25:19 +02:00
Frazer McLean
b8a9b1994b SIM905: Fix handling of U+001C..U+001F whitespace (#19849)
Fixes #19845

## Summary

The linked issue explains it well, Rust and Python do not agree on what
whitespace is for the purposes of `str.split`.
2025-08-11 03:43:04 +00:00
Frazer McLean
4d8ccb6125 RUF064: offer a safe fix for multi-digit zeros (#19847)
Fixes #19010

## Summary

See #19010. `0` was not considered a violation, but `000` was. The
latter will now be fixed to `0o000`.
2025-08-10 20:35:27 +00:00
Brent Westbrook
8230b79829 Clean up unused rendering code in ruff_linter (#19832)
## Summary

This is a follow-up to
https://github.com/astral-sh/ruff/pull/19415#discussion_r2263456740 to
remove some unused code. As Micha noticed,
`GroupedEmitter::with_show_source` was only used in local unit tests[^1]
and was safe to remove. This allowed deleting `MessageCodeFrame` and a
lot more helper code previously shared with the `full` output format.

I also moved some other code from `text.rs` and `message/mod.rs` into
`grouped.rs` that is now only used for the `grouped` format. With a
little refactoring of the `concise` rendering logic in `ruff_db`, we
could probably remove `RuleCodeAndBody` too. The only difference I see
from the `concise` output is whether we print the filename next to the
row and column or not:

```shell
> ruff check --output-format concise
try.py:1:8: F401 [*] `math` imported but unused
> ruff check --output-format grouped
try.py:
  1:8 F401 [*] `math` imported but unused
```

But I didn't try to do that here.

## Test Plan

Existing tests, with the source code no longer displayed. I also deleted
one test, as it was now a duplicate of the `default` test.

[^1]: "Local unit tests" as opposed to all of our linter snapshot tests,
as is the case for `TextEmitter::with_show_fix_diff`. We also want to
expose that to users eventually
(https://github.com/astral-sh/ruff/issues/7352), which I don't believe
is the case for the `grouped` format.
2025-08-09 14:20:48 -04:00
Alex Waygood
5a116e48c3 [ty] Add Salsa caching to TupleType::to_class_type (#19840) 2025-08-09 09:29:26 +01:00
Douglas Creager
3a542a80f6 [ty] Handle cycles when finding implicit attributes (#19833)
The [minimal
reproduction](https://gist.github.com/dcreager/fc53c59b30d7ce71d478dcb2c1c56444)
of https://github.com/astral-sh/ty/issues/948 is an example of a class
with implicit attributes whose types end up depending on themselves. Our
existing cycle detection for `infer_expression_types` is usually enough
to handle this situation correctly, but when there are very many of
these implicit attributes, we get a combinatorial explosion of running
time and memory usage.

Adding a separate cycle handler for `ClassLiteral::implicit_attribute`
lets us catch and recover from this situation earlier.

Closes https://github.com/astral-sh/ty/issues/948
2025-08-08 17:01:17 -04:00
Aria Desires
4be6fc0979 [ty] fix goto-definition on imports (#19834)
The stub mapper wasn't being passed into this codepath. It is now being
used. A previously messed up test result I intentionally checked in was
subsequently fixed.
2025-08-08 16:46:28 -04:00
Aria Desires
7cc3f1ebe9 [ty] Implement stdlib stub mapping (#19529)
by using essentially the same logic for system site-packages, on the
assumption that system site-packages are always a subdir of the stdlib
we were looking for.
2025-08-08 15:52:15 -04:00
Dan Parizher
0ec4801b0d [flake8-comprehensions] Fix false positive for C420 with attribute, subscript, or slice assignment targets (#19513)
## Summary

Fixes #19511
2025-08-08 15:02:30 -04:00
Eric Jolibois
0095ff4c1a [ty] Implement module-level __getattr__ support (#19791)
fix https://github.com/astral-sh/ty/issues/943

## Summary

Add module-level `__getattr__` support for ty's type checker, fixing
issue https://github.com/astral-sh/ty/issues/943.
Module-level `__getattr__` functions ([PEP
562](https://peps.python.org/pep-0562/)) are now respected when
resolving dynamic attributes, matching the behavior of mypy and pyright.

## Implementation

Thanks @sharkdp for the guidance in
https://github.com/astral-sh/ty/issues/943#issuecomment-3157566579
- Adds module-specific `__getattr__` resolution in
`ModuleLiteral.static_member()`
- Maintains proper attribute precedence: explicit attributes >
submodules > `__getattr__`

## Test Plan
- New mdtest covering basic functionality, type annotations, attribute
precedence, and edge cases
(run ```cargo nextest run -p ty_python_semantic
mdtest__import_module_getattr```)
- All new tests pass, verifying `__getattr__` is called correctly and
returns proper types
  - Existing test suite passes, ensuring no regressions introduced
2025-08-08 10:39:37 -07:00
Brent Westbrook
44755e6e86 Move full diagnostic rendering to ruff_db (#19415)
## Summary

This PR switches the `full` output format in Ruff over to use the
rendering code
in `ruff_db`. As proposed in the design doc, this involves a lot of
changes to the snapshot output.

I also had to comment out this assertion with a TODO to replace it after
https://github.com/astral-sh/ruff/issues/19688 because many of Ruff's
"file-level" annotations aren't actually file-level. They just happen to
occur at the start of the file, especially in tests with very short
snippets.


529d81daca/crates/ruff_annotate_snippets/src/renderer/display_list.rs (L1204-L1208)

I broke up the snapshot commits at the end into several blocks, but I
don't think it's enough to help with review. The first few (notebooks,
syntax errors, and test rules) are small enough to look at, but I
couldn't really think of other categories beyond that. I'm happy to
break those up or pick out specific examples beyond what I have below,
if that would help.

The minimal code changes are in this
[range](abd28f1e77),
with the snapshot commits following. Moving the `FullRenderer` and
updating the `EmitterFlags` aren't strictly necessary either. I even
dropped the renderer commit this morning but figured it made sense to
keep it since we have the `full` module for tests. I don't feel strongly
either way.

## Test Plan

I did actually click through all 1700 snapshots individually instead of
accepting them all at once, although I moved through them quickly. There
are a
few main categories:

### Lint diagnostics

```diff
-unused.py:8:19: F401 [*] `pathlib` imported but unused
+F401 [*] `pathlib` imported but unused
+  --> unused.py:8:19
    |
  7 | # Unused, _not_ marked as required (due to the alias).
  8 | import pathlib as non_alias
-   |                   ^^^^^^^^^ F401
+   |                   ^^^^^^^^^
  9 |
 10 | # Unused, marked as required.
    |
-   = help: Remove unused import: `pathlib`
+help: Remove unused import: `pathlib`
```

- The filename and line numbers are moved to the second line
- The second noqa code next to the underline is removed

### Syntax errors

These are much like the above.

```diff
-    -:1:16: invalid-syntax: Expected one or more symbol names after import
+    invalid-syntax: Expected one or more symbol names after import
+     --> -:1:16
       |
     1 | from foo import
       |                ^
```

One thing I noticed while reviewing some of these, but I don't think is
strictly syntax-error-related, is that some of the new diagnostics have
a little less context after the error. I don't think this is a problem,
but it's one small discrepancy I hadn't noticed before. Here's a minor
example:

```diff
-syntax_errors.py:1:15: invalid-syntax: Expected one or more symbol names after import
+invalid-syntax: Expected one or more symbol names after import
+ --> syntax_errors.py:1:15
   |
 1 | from os import
   |               ^
 2 |
 3 | if call(foo
-4 |     def bar():
   |
```

And one of the biggest examples:

```diff
-E30_syntax_error.py:18:11: invalid-syntax: Expected ')', found newline
+invalid-syntax: Expected ')', found newline
+  --> E30_syntax_error.py:18:11
    |
 16 |         pass
 17 |
 18 | foo = Foo(
    |           ^
-19 |
-20 |
-21 | def top(
    |
```

Similarly, a few of the lint diagnostics showed that the cut indicator
calculation for overly long lines is also slightly different, but I
think that's okay too.

### Full-file diagnostics

```diff
-comment.py:1:1: I002 [*] Missing required import: `from __future__ import annotations`
+I002 [*] Missing required import: `from __future__ import annotations`
+--> comment.py:1:1
+help: Insert required import: `from __future__ import annotations`
+
```

As noted above, these will be much more rare after #19688 too. This case
isn't a true full-file diagnostic and will render a snippet in the
future, but you can see that we're now rendering the help message that
would have been discarded before. In contrast, this is a true full-file
diagnostic and should still look like this after #19688:

```diff
-__init__.py:1:1: A005 Module `logging` shadows a Python standard-library module
+A005 Module `logging` shadows a Python standard-library module
+--> __init__.py:1:1
```

### Jupyter notebooks

There's nothing particularly different about these, just showing off the
cell index again.

```diff
-    Jupyter.ipynb:cell 3:1:7: F821 Undefined name `x`
+    F821 Undefined name `x`
+     --> Jupyter.ipynb:cell 3:1:7
       |
     1 | print(x)
-      |       ^ F821
+      |       ^
       |
```
2025-08-08 12:56:23 -04:00
Alex Waygood
8489816edc [ty] Improve ability to solve TypeVars when they appear in unions (#19829) 2025-08-08 17:50:37 +01:00
Micha Reiser
6b0eadfb4d Update salsa (#19827) 2025-08-08 17:51:51 +02:00
Brent Westbrook
8199154d54 [ty] Fix a few more diagnostic differences from Ruff (#19806)
## Summary

Fixes the remaining range reporting differences between the `ruff_db`
diagnostic rendering and Ruff's existing rendering, as noted in
https://github.com/astral-sh/ruff/pull/19415#issuecomment-3160525595.

This PR is structured as a series of three pairs. The first commit in
each pair adds a test showing the previous behavior, followed by a fix
and the updated snapshot. It's quite a small PR, but that might be
helpful just for the contrast.

You can also look at [this
range](052e656c6c..c3ea51030d)
of commits from #19415 to see the impact on real Ruff diagnostics. I
spun these commits out of that PR.

## Test Plan

New `ruff_db` tests
2025-08-08 11:31:19 -04:00
ember91
50e1ecc086 [pylint] Use lowercase hex characters to match the formatter (PLE2513) (#19808)
PLE2513 --fix changes ESC and SUB to uppercase hexadecimal values such
as \x1B while the formatter changes them to lowercase \x1b

<!--
Thank you for contributing to Ruff/ty! To help us out with reviewing,
please consider the following:

- Does this pull request include a summary of the change? (See below.)
- Does this pull request include a descriptive title? (Please prefix
with `[ty]` for ty pull
  requests.)
- Does this pull request include references to any relevant issues?
-->

## Summary

<!-- What's the purpose of the change? What does it do, and why? -->

## Test Plan

<!-- How was it tested? -->

---------

Co-authored-by: Brent Westbrook <brentrwestbrook@gmail.com>
2025-08-08 12:25:11 +00:00
Micha Reiser
fd35435281 [ty] Improve performance of subtyping and assignability checks for protocols (#19824) 2025-08-08 13:05:12 +02:00
Dhruv Manilawala
fc72ff4a94 [ty] Send a single request for registrations/unregistrations (#19822)
## Summary

This is a small refactor to update the server to send a single request
to perform registrations and unregistrations of dynamic capabilities.

## Test Plan

Existing E2E test cases pass, add a new test case to verify multiple
registrations.
2025-08-08 08:42:48 +00:00
2847 changed files with 95625 additions and 65898 deletions

View File

@@ -49,7 +49,7 @@ jobs:
- name: "Prep README.md"
run: python scripts/transform_readme.py --target pypi
- name: "Build sdist"
uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380 # v1.49.4
with:
command: sdist
args: --out dist
@@ -79,7 +79,7 @@ jobs:
- name: "Prep README.md"
run: python scripts/transform_readme.py --target pypi
- name: "Build wheels - x86_64"
uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380 # v1.49.4
with:
target: x86_64
args: --release --locked --out dist
@@ -121,7 +121,7 @@ jobs:
- name: "Prep README.md"
run: python scripts/transform_readme.py --target pypi
- name: "Build wheels - aarch64"
uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380 # v1.49.4
with:
target: aarch64
args: --release --locked --out dist
@@ -177,7 +177,7 @@ jobs:
- name: "Prep README.md"
run: python scripts/transform_readme.py --target pypi
- name: "Build wheels"
uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380 # v1.49.4
with:
target: ${{ matrix.platform.target }}
args: --release --locked --out dist
@@ -230,7 +230,7 @@ jobs:
- name: "Prep README.md"
run: python scripts/transform_readme.py --target pypi
- name: "Build wheels"
uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380 # v1.49.4
with:
target: ${{ matrix.target }}
manylinux: auto
@@ -292,6 +292,8 @@ jobs:
maturin_docker_options: -e JEMALLOC_SYS_WITH_LG_PAGE=16
- target: arm-unknown-linux-musleabihf
arch: arm
- target: riscv64gc-unknown-linux-gnu
arch: riscv64
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
@@ -304,7 +306,7 @@ jobs:
- name: "Prep README.md"
run: python scripts/transform_readme.py --target pypi
- name: "Build wheels"
uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380 # v1.49.4
with:
target: ${{ matrix.platform.target }}
manylinux: auto
@@ -319,7 +321,7 @@ jobs:
githubToken: ${{ github.token }}
install: |
apt-get update
apt-get install -y --no-install-recommends python3 python3-pip
apt-get install -y --no-install-recommends python3 python3-pip libatomic1
pip3 install -U pip
run: |
pip3 install ${{ env.PACKAGE_NAME }} --no-index --find-links dist/ --force-reinstall
@@ -370,7 +372,7 @@ jobs:
- name: "Prep README.md"
run: python scripts/transform_readme.py --target pypi
- name: "Build wheels"
uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380 # v1.49.4
with:
target: ${{ matrix.target }}
manylinux: musllinux_1_2
@@ -435,7 +437,7 @@ jobs:
- name: "Prep README.md"
run: python scripts/transform_readme.py --target pypi
- name: "Build wheels"
uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380 # v1.49.4
with:
target: ${{ matrix.platform.target }}
manylinux: musllinux_1_2

View File

@@ -40,7 +40,7 @@ jobs:
- uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
- uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
- uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
@@ -131,7 +131,7 @@ jobs:
type=pep440,pattern={{ version }},value=${{ fromJson(inputs.plan).announcement_tag }}
type=pep440,pattern={{ major }}.{{ minor }},value=${{ fromJson(inputs.plan).announcement_tag }}
- uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
- uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
@@ -169,7 +169,7 @@ jobs:
steps:
- uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
- uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
- uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
@@ -276,7 +276,7 @@ jobs:
type=pep440,pattern={{ version }},value=${{ fromJson(inputs.plan).announcement_tag }}
type=pep440,pattern={{ major }}.{{ minor }},value=${{ fromJson(inputs.plan).announcement_tag }}
- uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
- uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0
with:
registry: ghcr.io
username: ${{ github.repository_owner }}

View File

@@ -38,7 +38,8 @@ jobs:
fuzz: ${{ steps.check_fuzzer.outputs.changed }}
# Flag that is set to "true" when code related to ty changes.
ty: ${{ steps.check_ty.outputs.changed }}
# Flag that is set to "true" when code related to the py-fuzzer folder changes.
py-fuzzer: ${{ steps.check_py_fuzzer.outputs.changed }}
# Flag that is set to "true" when code related to the playground changes.
playground: ${{ steps.check_playground.outputs.changed }}
steps:
@@ -68,7 +69,6 @@ jobs:
':crates/ruff_text_size/**' \
':crates/ruff_python_ast/**' \
':crates/ruff_python_parser/**' \
':python/py-fuzzer/**' \
':.github/workflows/ci.yaml' \
; then
echo "changed=false" >> "$GITHUB_OUTPUT"
@@ -138,6 +138,18 @@ jobs:
echo "changed=true" >> "$GITHUB_OUTPUT"
fi
- name: Check if the py-fuzzer code changed
id: check_py_fuzzer
env:
MERGE_BASE: ${{ steps.merge_base.outputs.sha }}
run: |
if git diff --quiet "${MERGE_BASE}...HEAD" -- 'python/py_fuzzer/**' \
; then
echo "changed=false" >> "$GITHUB_OUTPUT"
else
echo "changed=true" >> "$GITHUB_OUTPUT"
fi
- name: Check if there was any code related change
id: check_code
env:
@@ -238,7 +250,7 @@ jobs:
- name: "Install Rust toolchain"
run: rustup show
- name: "Install mold"
uses: rui314/setup-mold@702b1908b5edf30d71a8d1666b724e0f0c6fa035 # v1
uses: rui314/setup-mold@7344740a9418dcdcb481c7df83d9fbd1d5072d7d # v1
- name: "Install cargo nextest"
uses: taiki-e/install-action@6064345e6658255e90e9500fdf9a06ab77e6909c # v2.57.6
with:
@@ -296,7 +308,7 @@ jobs:
- name: "Install Rust toolchain"
run: rustup show
- name: "Install mold"
uses: rui314/setup-mold@702b1908b5edf30d71a8d1666b724e0f0c6fa035 # v1
uses: rui314/setup-mold@7344740a9418dcdcb481c7df83d9fbd1d5072d7d # v1
- name: "Install cargo nextest"
uses: taiki-e/install-action@6064345e6658255e90e9500fdf9a06ab77e6909c # v2.57.6
with:
@@ -381,7 +393,7 @@ jobs:
- name: "Install Rust toolchain"
run: rustup show
- name: "Install mold"
uses: rui314/setup-mold@702b1908b5edf30d71a8d1666b724e0f0c6fa035 # v1
uses: rui314/setup-mold@7344740a9418dcdcb481c7df83d9fbd1d5072d7d # v1
- name: "Build"
run: cargo build --release --locked
@@ -406,7 +418,7 @@ jobs:
MSRV: ${{ steps.msrv.outputs.value }}
run: rustup default "${MSRV}"
- name: "Install mold"
uses: rui314/setup-mold@702b1908b5edf30d71a8d1666b724e0f0c6fa035 # v1
uses: rui314/setup-mold@7344740a9418dcdcb481c7df83d9fbd1d5072d7d # v1
- name: "Build tests"
shell: bash
env:
@@ -429,7 +441,7 @@ jobs:
- name: "Install Rust toolchain"
run: rustup show
- name: "Install cargo-binstall"
uses: cargo-bins/cargo-binstall@dd6a0ac24caa1243d18df0f770b941e990e8facc # v1.14.3
uses: cargo-bins/cargo-binstall@79e4beb1e02f733a26129a6bf26c37dab4ab3307 # v1.14.4
with:
tool: cargo-fuzz@0.11.2
- name: "Install cargo-fuzz"
@@ -443,7 +455,7 @@ jobs:
needs:
- cargo-test-linux
- determine_changes
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && needs.determine_changes.outputs.parser == 'true' }}
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && (needs.determine_changes.outputs.parser == 'true' || needs.determine_changes.outputs.py-fuzzer == 'true') }}
timeout-minutes: 20
env:
FORCE_COLOR: 1
@@ -633,7 +645,7 @@ jobs:
- cargo-test-linux
- determine_changes
# Only runs on pull requests, since that is the only we way we can find the base version for comparison.
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && github.event_name == 'pull_request' && needs.determine_changes.outputs.ty == 'true' }}
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && github.event_name == 'pull_request' && (needs.determine_changes.outputs.ty == 'true' || needs.determine_changes.outputs.py-fuzzer == 'true') }}
timeout-minutes: 20
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
@@ -682,7 +694,7 @@ jobs:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- uses: cargo-bins/cargo-binstall@dd6a0ac24caa1243d18df0f770b941e990e8facc # v1.14.3
- uses: cargo-bins/cargo-binstall@79e4beb1e02f733a26129a6bf26c37dab4ab3307 # v1.14.4
- run: cargo binstall --no-confirm cargo-shear
- run: cargo shear
@@ -703,7 +715,7 @@ jobs:
- name: "Prep README.md"
run: python scripts/transform_readme.py --target pypi
- name: "Build wheels"
uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380 # v1.49.4
with:
args: --out dist
- name: "Test wheel"
@@ -728,7 +740,7 @@ jobs:
with:
node-version: 22
- name: "Cache pre-commit"
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
with:
path: ~/.cache/pre-commit
key: pre-commit-${{ hashFiles('.pre-commit-config.yaml') }}

View File

@@ -38,7 +38,7 @@ jobs:
- name: "Install Rust toolchain"
run: rustup show
- name: "Install mold"
uses: rui314/setup-mold@702b1908b5edf30d71a8d1666b724e0f0c6fa035 # v1
uses: rui314/setup-mold@7344740a9418dcdcb481c7df83d9fbd1d5072d7d # v1
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
- name: Build ruff
# A debug build means the script runs slower once it gets started,

View File

@@ -11,6 +11,7 @@ on:
- "crates/ruff_python_parser"
- ".github/workflows/mypy_primer.yaml"
- ".github/workflows/mypy_primer_comment.yaml"
- "scripts/mypy_primer.sh"
- "Cargo.lock"
- "!**.md"

View File

@@ -61,7 +61,7 @@ jobs:
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@09d2acae674a48949e3602304ab46fd20ae0c42f
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
with:
persist-credentials: false
submodules: recursive
@@ -124,19 +124,19 @@ jobs:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BUILD_MANIFEST_NAME: target/distrib/global-dist-manifest.json
steps:
- uses: actions/checkout@09d2acae674a48949e3602304ab46fd20ae0c42f
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
with:
persist-credentials: false
submodules: recursive
- name: Install cached dist
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0
with:
name: cargo-dist-cache
path: ~/.cargo/bin/
- run: chmod +x ~/.cargo/bin/dist
# Get all the local artifacts for the global tasks to use (for e.g. checksums)
- name: Fetch local artifacts
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0
with:
pattern: artifacts-*
path: target/distrib/
@@ -175,19 +175,19 @@ jobs:
outputs:
val: ${{ steps.host.outputs.manifest }}
steps:
- uses: actions/checkout@09d2acae674a48949e3602304ab46fd20ae0c42f
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
with:
persist-credentials: false
submodules: recursive
- name: Install cached dist
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0
with:
name: cargo-dist-cache
path: ~/.cargo/bin/
- run: chmod +x ~/.cargo/bin/dist
# Fetch artifacts from scratch-storage
- name: Fetch artifacts
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0
with:
pattern: artifacts-*
path: target/distrib/
@@ -251,13 +251,13 @@ jobs:
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@09d2acae674a48949e3602304ab46fd20ae0c42f
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
with:
persist-credentials: false
submodules: recursive
# Create a GitHub Release while uploading all files to it
- name: "Download GitHub Artifacts"
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0
with:
pattern: artifacts-*
path: artifacts

View File

@@ -54,6 +54,9 @@ jobs:
- name: Compute diagnostic diff
shell: bash
env:
# TODO: Remove this once we fixed the remaining panics in the conformance suite.
TY_MAX_PARALLELISM: 1
run: |
RUFF_DIR="$GITHUB_WORKSPACE/ruff"
@@ -63,15 +66,15 @@ jobs:
echo "new commit"
git rev-list --format=%s --max-count=1 "$GITHUB_SHA"
cargo build --release --bin ty
mv target/release/ty ty-new
cargo build --bin ty
mv target/debug/ty ty-new
MERGE_BASE="$(git merge-base "$GITHUB_SHA" "origin/$GITHUB_BASE_REF")"
git checkout -b old_commit "$MERGE_BASE"
echo "old commit (merge base)"
git rev-list --format=%s --max-count=1 old_commit
cargo build --release --bin ty
mv target/release/ty ty-old
cargo build --bin ty
mv target/debug/ty ty-old
)
(

View File

@@ -1,5 +1,78 @@
# Changelog
## 0.12.10
### Preview features
- \[`flake8-simplify`\] Implement fix for `maxsplit` without separator (`SIM905`) ([#19851](https://github.com/astral-sh/ruff/pull/19851))
- \[`flake8-use-pathlib`\] Add fixes for `PTH102` and `PTH103` ([#19514](https://github.com/astral-sh/ruff/pull/19514))
### Bug fixes
- \[`isort`\] Handle multiple continuation lines after module docstring (`I002`) ([#19818](https://github.com/astral-sh/ruff/pull/19818))
- \[`pyupgrade`\] Avoid reporting `__future__` features as unnecessary when they are used (`UP010`) ([#19769](https://github.com/astral-sh/ruff/pull/19769))
- \[`pyupgrade`\] Handle nested `Optional`s (`UP045`) ([#19770](https://github.com/astral-sh/ruff/pull/19770))
### Rule changes
- \[`pycodestyle`\] Make `E731` fix unsafe instead of display-only for class assignments ([#19700](https://github.com/astral-sh/ruff/pull/19700))
- \[`pyflakes`\] Add secondary annotation showing previous definition (`F811`) ([#19900](https://github.com/astral-sh/ruff/pull/19900))
### Documentation
- Fix description of global config file discovery strategy ([#19188](https://github.com/astral-sh/ruff/pull/19188))
- Update outdated links to <https://typing.python.org/en/latest/source/stubs.html> ([#19992](https://github.com/astral-sh/ruff/pull/19992))
- \[`flake8-annotations`\] Remove unused import in example (`ANN401`) ([#20000](https://github.com/astral-sh/ruff/pull/20000))
## 0.12.9
### Preview features
- \[`airflow`\] Add check for `airflow.secrets.cache.SecretCache` (`AIR301`) ([#17707](https://github.com/astral-sh/ruff/pull/17707))
- \[`ruff`\] Offer a safe fix for multi-digit zeros (`RUF064`) ([#19847](https://github.com/astral-sh/ruff/pull/19847))
### Bug fixes
- \[`flake8-blind-except`\] Fix `BLE001` false-positive on `raise ... from None` ([#19755](https://github.com/astral-sh/ruff/pull/19755))
- \[`flake8-comprehensions`\] Fix false positive for `C420` with attribute, subscript, or slice assignment targets ([#19513](https://github.com/astral-sh/ruff/pull/19513))
- \[`flake8-simplify`\] Fix handling of U+001C..U+001F whitespace (`SIM905`) ([#19849](https://github.com/astral-sh/ruff/pull/19849))
### Rule changes
- \[`pylint`\] Use lowercase hex characters to match the formatter (`PLE2513`) ([#19808](https://github.com/astral-sh/ruff/pull/19808))
### Documentation
- Fix `lint.future-annotations` link ([#19876](https://github.com/astral-sh/ruff/pull/19876))
### Other changes
- Build `riscv64` binaries for release ([#19819](https://github.com/astral-sh/ruff/pull/19819))
- Add rule code to error description in GitLab output ([#19896](https://github.com/astral-sh/ruff/pull/19896))
- Improve rendering of the `full` output format ([#19415](https://github.com/astral-sh/ruff/pull/19415))
Below is an example diff for [`F401`](https://docs.astral.sh/ruff/rules/unused-import/):
```diff
-unused.py:8:19: F401 [*] `pathlib` imported but unused
+F401 [*] `pathlib` imported but unused
+ --> unused.py:8:19
|
7 | # Unused, _not_ marked as required (due to the alias).
8 | import pathlib as non_alias
- | ^^^^^^^^^ F401
+ | ^^^^^^^^^
9 |
10 | # Unused, marked as required.
|
- = help: Remove unused import: `pathlib`
+help: Remove unused import: `pathlib`
```
For now, the primary difference is the movement of the filename, line number, and column information to a second line in the header. This new representation will allow us to make further additions to Ruff's diagnostics, such as adding sub-diagnostics and multiple annotations to the same snippet.
## 0.12.8
### Preview features

138
Cargo.lock generated
View File

@@ -128,9 +128,9 @@ dependencies = [
[[package]]
name = "anyhow"
version = "1.0.98"
version = "1.0.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100"
[[package]]
name = "approx"
@@ -257,9 +257,12 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.9.1"
version = "2.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
checksum = "6a65b545ab31d687cff52899d4890855fec459eb6afe0da6417b8a18da87aa29"
dependencies = [
"serde",
]
[[package]]
name = "bitvec"
@@ -322,9 +325,9 @@ dependencies = [
[[package]]
name = "camino"
version = "1.1.10"
version = "1.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0da45bc31171d8d6960122e222a67740df867c1dd53b4d51caa297084c185cab"
checksum = "5d07aa9a93b00c76f71bc35d598bed923f6d4f3a9ca5c24b7737ae1a292841c0"
dependencies = [
"serde",
]
@@ -408,9 +411,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.5.43"
version = "4.5.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50fd97c9dc2399518aa331917ac6f274280ec5eb34e555dd291899745c48ec6f"
checksum = "1fc0e74a703892159f5ae7d3aac52c8e6c392f5ae5f359c70b5881d60aaac318"
dependencies = [
"clap_builder",
"clap_derive",
@@ -418,9 +421,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.5.43"
version = "4.5.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c35b5830294e1fa0462034af85cc95225a4cb07092c088c55bda3147cfcd8f65"
checksum = "b3e7f4214277f3c7aa526a59dd3fbe306a370daee1f8b7b8c987069cd8e888a8"
dependencies = [
"anstream",
"anstyle",
@@ -461,9 +464,9 @@ dependencies = [
[[package]]
name = "clap_derive"
version = "4.5.41"
version = "4.5.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef4f52386a59ca4c860f7393bcf8abd8dfd91ecccc0f774635ff68e92eeef491"
checksum = "14cb31bb0a7d536caef2639baa7fad459e15c3144efefa6dbd1c84562c4739f6"
dependencies = [
"heck",
"proc-macro2",
@@ -1028,6 +1031,16 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "erased-serde"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e004d887f51fcb9fef17317a2f3525c887d8aa3f4f50fed920816a688284a5b7"
dependencies = [
"serde",
"typeid",
]
[[package]]
name = "errno"
version = "0.3.13"
@@ -1178,7 +1191,7 @@ checksum = "5697765925a05c9d401dd04a93dfd662d336cc25fdcc3301220385a1ffcfdde5"
dependencies = [
"compact_str",
"get-size-derive2",
"hashbrown 0.15.4",
"hashbrown 0.15.5",
"smallvec",
]
@@ -1218,9 +1231,9 @@ dependencies = [
[[package]]
name = "glob"
version = "0.3.2"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2"
checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
[[package]]
name = "globset"
@@ -1241,7 +1254,7 @@ version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bf760ebf69878d9fd8f110c89703d90ce35095324d1f1edcb595c63945ee757"
dependencies = [
"bitflags 2.9.1",
"bitflags 2.9.2",
"ignore",
"walkdir",
]
@@ -1264,9 +1277,9 @@ checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
[[package]]
name = "hashbrown"
version = "0.15.4"
version = "0.15.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5"
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
dependencies = [
"allocator-api2",
"equivalent",
@@ -1279,7 +1292,7 @@ version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1"
dependencies = [
"hashbrown 0.15.4",
"hashbrown 0.15.5",
]
[[package]]
@@ -1471,7 +1484,7 @@ version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17d34b7d42178945f775e84bc4c36dde7c1c6cdfea656d3354d009056f2bb3d2"
dependencies = [
"hashbrown 0.15.4",
"hashbrown 0.15.5",
]
[[package]]
@@ -1491,7 +1504,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661"
dependencies = [
"equivalent",
"hashbrown 0.15.4",
"hashbrown 0.15.5",
"serde",
]
@@ -1521,7 +1534,7 @@ version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3"
dependencies = [
"bitflags 2.9.1",
"bitflags 2.9.2",
"inotify-sys",
"libc",
]
@@ -1764,9 +1777,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "libc"
version = "0.2.174"
version = "0.2.175"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543"
[[package]]
name = "libcst"
@@ -1809,7 +1822,7 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3"
dependencies = [
"bitflags 2.9.1",
"bitflags 2.9.2",
"libc",
"redox_syscall",
]
@@ -2014,7 +2027,7 @@ version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
dependencies = [
"bitflags 2.9.1",
"bitflags 2.9.2",
"cfg-if",
"cfg_aliases",
"libc",
@@ -2026,7 +2039,7 @@ version = "0.30.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6"
dependencies = [
"bitflags 2.9.1",
"bitflags 2.9.2",
"cfg-if",
"cfg_aliases",
"libc",
@@ -2054,7 +2067,7 @@ version = "8.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3"
dependencies = [
"bitflags 2.9.1",
"bitflags 2.9.2",
"fsevent-sys",
"inotify",
"kqueue",
@@ -2473,9 +2486,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.95"
version = "1.0.96"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
checksum = "beef09f85ae72cea1ef96ba6870c51e6382ebfa4f0e85b643459331f3daa5be0"
dependencies = [
"unicode-ident",
]
@@ -2666,7 +2679,7 @@ version = "0.5.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77"
dependencies = [
"bitflags 2.9.1",
"bitflags 2.9.2",
]
[[package]]
@@ -2743,13 +2756,13 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.12.8"
version = "0.12.10"
dependencies = [
"anyhow",
"argfile",
"assert_fs",
"bincode 2.0.1",
"bitflags 2.9.1",
"bitflags 2.9.2",
"cachedir",
"clap",
"clap_complete_command",
@@ -2886,6 +2899,7 @@ dependencies = [
"schemars",
"serde",
"serde_json",
"similar",
"tempfile",
"thiserror 2.0.12",
"tracing",
@@ -2996,17 +3010,17 @@ dependencies = [
[[package]]
name = "ruff_linter"
version = "0.12.8"
version = "0.12.10"
dependencies = [
"aho-corasick",
"anyhow",
"bitflags 2.9.1",
"bitflags 2.9.2",
"clap",
"colored 3.0.0",
"fern",
"glob",
"globset",
"hashbrown 0.15.4",
"hashbrown 0.15.5",
"imperative",
"insta",
"is-macro",
@@ -3022,7 +3036,6 @@ dependencies = [
"pep440_rs",
"pyproject-toml",
"regex",
"ruff_annotate_snippets",
"ruff_cache",
"ruff_db",
"ruff_diagnostics",
@@ -3074,6 +3087,7 @@ name = "ruff_memory_usage"
version = "0.0.0"
dependencies = [
"get-size2",
"ordermap",
]
[[package]]
@@ -3106,7 +3120,7 @@ name = "ruff_python_ast"
version = "0.0.0"
dependencies = [
"aho-corasick",
"bitflags 2.9.1",
"bitflags 2.9.2",
"compact_str",
"get-size2",
"is-macro",
@@ -3194,7 +3208,7 @@ dependencies = [
name = "ruff_python_literal"
version = "0.0.0"
dependencies = [
"bitflags 2.9.1",
"bitflags 2.9.2",
"itertools 0.14.0",
"ruff_python_ast",
"unic-ucd-category",
@@ -3205,7 +3219,7 @@ name = "ruff_python_parser"
version = "0.0.0"
dependencies = [
"anyhow",
"bitflags 2.9.1",
"bitflags 2.9.2",
"bstr",
"compact_str",
"get-size2",
@@ -3230,7 +3244,7 @@ dependencies = [
name = "ruff_python_semantic"
version = "0.0.0"
dependencies = [
"bitflags 2.9.1",
"bitflags 2.9.2",
"insta",
"is-macro",
"ruff_cache",
@@ -3251,7 +3265,7 @@ dependencies = [
name = "ruff_python_stdlib"
version = "0.0.0"
dependencies = [
"bitflags 2.9.1",
"bitflags 2.9.2",
"unicode-ident",
]
@@ -3335,7 +3349,7 @@ dependencies = [
[[package]]
name = "ruff_wasm"
version = "0.12.8"
version = "0.12.10"
dependencies = [
"console_error_panic_hook",
"console_log",
@@ -3428,7 +3442,7 @@ version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8"
dependencies = [
"bitflags 2.9.1",
"bitflags 2.9.2",
"errno",
"libc",
"linux-raw-sys",
@@ -3450,13 +3464,14 @@ checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
[[package]]
name = "salsa"
version = "0.23.0"
source = "git+https://github.com/salsa-rs/salsa.git?rev=b121ee46c4483ba74c19e933a3522bd548eb7343#b121ee46c4483ba74c19e933a3522bd548eb7343"
source = "git+https://github.com/salsa-rs/salsa.git?rev=a0e7a06#a0e7a0660c93136f23bf08b4f1604eee3d1f6b11"
dependencies = [
"boxcar",
"compact_str",
"crossbeam-queue",
"crossbeam-utils",
"hashbrown 0.15.4",
"erased-serde",
"hashbrown 0.15.5",
"hashlink",
"indexmap",
"intrusive-collections",
@@ -3466,6 +3481,7 @@ dependencies = [
"rustc-hash",
"salsa-macro-rules",
"salsa-macros",
"serde",
"smallvec",
"thin-vec",
"tracing",
@@ -3474,12 +3490,12 @@ dependencies = [
[[package]]
name = "salsa-macro-rules"
version = "0.23.0"
source = "git+https://github.com/salsa-rs/salsa.git?rev=b121ee46c4483ba74c19e933a3522bd548eb7343#b121ee46c4483ba74c19e933a3522bd548eb7343"
source = "git+https://github.com/salsa-rs/salsa.git?rev=a0e7a06#a0e7a0660c93136f23bf08b4f1604eee3d1f6b11"
[[package]]
name = "salsa-macros"
version = "0.23.0"
source = "git+https://github.com/salsa-rs/salsa.git?rev=b121ee46c4483ba74c19e933a3522bd548eb7343#b121ee46c4483ba74c19e933a3522bd548eb7343"
source = "git+https://github.com/salsa-rs/salsa.git?rev=a0e7a06#a0e7a0660c93136f23bf08b4f1604eee3d1f6b11"
dependencies = [
"proc-macro2",
"quote",
@@ -3699,6 +3715,9 @@ name = "smallvec"
version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
dependencies = [
"serde",
]
[[package]]
name = "snapbox"
@@ -3903,6 +3922,9 @@ name = "thin-vec"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "144f754d318415ac792f9d69fc87abbbfc043ce2ef041c60f16ad828f638717d"
dependencies = [
"serde",
]
[[package]]
name = "thiserror"
@@ -4238,7 +4260,7 @@ dependencies = [
name = "ty_ide"
version = "0.0.0"
dependencies = [
"bitflags 2.9.1",
"bitflags 2.9.2",
"insta",
"itertools 0.14.0",
"regex",
@@ -4260,6 +4282,7 @@ name = "ty_project"
version = "0.0.0"
dependencies = [
"anyhow",
"bincode 2.0.1",
"camino",
"colored 3.0.0",
"crossbeam",
@@ -4289,6 +4312,7 @@ dependencies = [
"tracing",
"ty_combine",
"ty_python_semantic",
"ty_static",
"ty_vendored",
]
@@ -4297,7 +4321,7 @@ name = "ty_python_semantic"
version = "0.0.0"
dependencies = [
"anyhow",
"bitflags 2.9.1",
"bitflags 2.9.2",
"bitvec",
"camino",
"colored 3.0.0",
@@ -4306,7 +4330,7 @@ dependencies = [
"drop_bomb",
"get-size2",
"glob",
"hashbrown 0.15.4",
"hashbrown 0.15.5",
"indexmap",
"insta",
"itertools 0.14.0",
@@ -4350,7 +4374,7 @@ name = "ty_server"
version = "0.0.0"
dependencies = [
"anyhow",
"bitflags 2.9.1",
"bitflags 2.9.2",
"crossbeam",
"dunce",
"insta",
@@ -4393,7 +4417,7 @@ name = "ty_test"
version = "0.0.0"
dependencies = [
"anyhow",
"bitflags 2.9.1",
"bitflags 2.9.2",
"camino",
"colored 3.0.0",
"insta",
@@ -4460,6 +4484,12 @@ version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a"
[[package]]
name = "typeid"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c"
[[package]]
name = "typenum"
version = "1.18.0"
@@ -5143,7 +5173,7 @@ version = "0.39.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
dependencies = [
"bitflags 2.9.1",
"bitflags 2.9.2",
]
[[package]]

View File

@@ -5,7 +5,7 @@ resolver = "2"
[workspace.package]
# Please update rustfmt.toml when bumping the Rust edition
edition = "2024"
rust-version = "1.86"
rust-version = "1.87"
homepage = "https://docs.astral.sh/ruff"
documentation = "https://docs.astral.sh/ruff"
repository = "https://github.com/astral-sh/ruff"
@@ -57,8 +57,8 @@ anyhow = { version = "1.0.80" }
arc-swap = { version = "1.7.1" }
assert_fs = { version = "1.1.0" }
argfile = { version = "0.2.0" }
bincode = { version = "2.0.0" }
bitflags = { version = "2.5.0" }
bincode = { version = "2.0.0", features = ["serde"] }
bitflags = { version = "2.5.0", features = ["serde"] }
bitvec = { version = "1.0.1", default-features = false, features = [
"alloc",
] }
@@ -126,7 +126,7 @@ memchr = { version = "2.7.1" }
mimalloc = { version = "0.1.39" }
natord = { version = "1.0.9" }
notify = { version = "8.0.0" }
ordermap = { version = "0.5.0" }
ordermap = { version = "0.5.0", features = ["serde"] }
path-absolutize = { version = "3.1.1" }
path-slash = { version = "0.2.1" }
pathdiff = { version = "0.2.1" }
@@ -143,24 +143,25 @@ regex-automata = { version = "0.4.9" }
rustc-hash = { version = "2.0.0" }
rustc-stable-hash = { version = "0.1.2" }
# When updating salsa, make sure to also update the revision in `fuzz/Cargo.toml`
salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "b121ee46c4483ba74c19e933a3522bd548eb7343", default-features = false, features = [
salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "a0e7a06", default-features = false, features = [
"compact_str",
"macros",
"salsa_unstable",
"inventory",
"persistence",
] }
schemars = { version = "0.8.16" }
seahash = { version = "4.1.0" }
serde = { version = "1.0.197", features = ["derive"] }
serde = { version = "1.0.197", features = ["derive", "rc"] }
serde-wasm-bindgen = { version = "0.6.4" }
serde_json = { version = "1.0.113" }
serde_json = { version = "1.0.142" }
serde_test = { version = "1.0.152" }
serde_with = { version = "3.6.0", default-features = false, features = [
"macros",
] }
shellexpand = { version = "3.0.0" }
similar = { version = "2.4.0", features = ["inline"] }
smallvec = { version = "1.13.2", features = ["union", "const_generics", "const_new"] }
smallvec = { version = "1.13.2", features = ["union", "const_generics", "const_new", "serde"] }
snapbox = { version = "0.6.0", features = [
"diff",
"term-svg",
@@ -215,6 +216,8 @@ unexpected_cfgs = { level = "warn", check-cfg = [
[workspace.lints.clippy]
pedantic = { level = "warn", priority = -2 }
# Enabled at the crate level
disallowed_methods = "allow"
# Allowed pedantic lints
char_lit_as_u8 = "allow"
collapsible_else_if = "allow"
@@ -253,6 +256,7 @@ unused_peekable = "warn"
# Diagnostics are not actionable: Enable once https://github.com/rust-lang/rust-clippy/issues/13774 is resolved.
large_stack_arrays = "allow"
[profile.release]
# Note that we set these explicitly, and these values
# were chosen based on a trade-off between compile times

View File

@@ -148,8 +148,8 @@ curl -LsSf https://astral.sh/ruff/install.sh | sh
powershell -c "irm https://astral.sh/ruff/install.ps1 | iex"
# For a specific version.
curl -LsSf https://astral.sh/ruff/0.12.8/install.sh | sh
powershell -c "irm https://astral.sh/ruff/0.12.8/install.ps1 | iex"
curl -LsSf https://astral.sh/ruff/0.12.10/install.sh | sh
powershell -c "irm https://astral.sh/ruff/0.12.10/install.ps1 | iex"
```
You can also install Ruff via [Homebrew](https://formulae.brew.sh/formula/ruff), [Conda](https://anaconda.org/conda-forge/ruff),
@@ -182,7 +182,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com/) hook via [`ruff
```yaml
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.12.8
rev: v0.12.10
hooks:
# Run the linter.
- id: ruff-check

View File

@@ -24,3 +24,20 @@ ignore-interior-mutability = [
# The expression is read-only.
"ruff_python_ast::hashable::HashableExpr",
]
disallowed-methods = [
{ path = "std::env::var", reason = "Use System::env_var instead in ty crates" },
{ path = "std::env::current_dir", reason = "Use System::current_directory instead in ty crates" },
{ path = "std::fs::read_to_string", reason = "Use System::read_to_string instead in ty crates" },
{ path = "std::fs::metadata", reason = "Use System::path_metadata instead in ty crates" },
{ path = "std::fs::canonicalize", reason = "Use System::canonicalize_path instead in ty crates" },
{ path = "dunce::canonicalize", reason = "Use System::canonicalize_path instead in ty crates" },
{ path = "std::fs::read_dir", reason = "Use System::read_directory instead in ty crates" },
{ path = "std::fs::write", reason = "Use WritableSystem::write_file instead in ty crates" },
{ path = "std::fs::create_dir_all", reason = "Use WritableSystem::create_directory_all instead in ty crates" },
{ path = "std::fs::File::create_new", reason = "Use WritableSystem::create_new_file instead in ty crates" },
# Path methods that have System trait equivalents
{ path = "std::path::Path::exists", reason = "Use System::path_exists instead in ty crates" },
{ path = "std::path::Path::is_dir", reason = "Use System::is_directory instead in ty crates" },
{ path = "std::path::Path::is_file", reason = "Use System::is_file instead in ty crates" },
]

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff"
version = "0.12.8"
version = "0.12.10"
publish = true
authors = { workspace = true }
edition = { workspace = true }
@@ -31,7 +31,7 @@ ruff_workspace = { workspace = true }
anyhow = { workspace = true }
argfile = { workspace = true }
bincode = { workspace = true, features = ["serde"] }
bincode = { workspace = true }
bitflags = { workspace = true }
cachedir = { workspace = true }
clap = { workspace = true, features = ["derive", "env", "wrap_help"] }
@@ -85,7 +85,7 @@ dist = true
[target.'cfg(target_os = "windows")'.dependencies]
mimalloc = { workspace = true }
[target.'cfg(all(not(target_os = "windows"), not(target_os = "openbsd"), not(target_os = "aix"), not(target_os = "android"), any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "powerpc64")))'.dependencies]
[target.'cfg(all(not(target_os = "windows"), not(target_os = "openbsd"), not(target_os = "aix"), not(target_os = "android"), any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "powerpc64", target_arch = "riscv64")))'.dependencies]
tikv-jemallocator = { workspace = true }
[lints]

View File

@@ -13,25 +13,16 @@ use itertools::Itertools;
use log::{debug, error};
use rayon::iter::ParallelIterator;
use rayon::iter::{IntoParallelIterator, ParallelBridge};
use ruff_linter::codes::Rule;
use rustc_hash::FxHashMap;
use tempfile::NamedTempFile;
use ruff_cache::{CacheKey, CacheKeyHasher};
use ruff_db::diagnostic::Diagnostic;
use ruff_diagnostics::Fix;
use ruff_linter::message::create_lint_diagnostic;
use ruff_linter::package::PackageRoot;
use ruff_linter::{VERSION, warn_user};
use ruff_macros::CacheKey;
use ruff_notebook::NotebookIndex;
use ruff_source_file::SourceFileBuilder;
use ruff_text_size::{TextRange, TextSize};
use ruff_workspace::Settings;
use ruff_workspace::resolver::Resolver;
use crate::diagnostics::Diagnostics;
/// [`Path`] that is relative to the package root in [`PackageCache`].
pub(crate) type RelativePath = Path;
/// [`PathBuf`] that is relative to the package root in [`PackageCache`].
@@ -298,13 +289,8 @@ impl Cache {
});
}
pub(crate) fn update_lint(
&self,
path: RelativePathBuf,
key: &FileCacheKey,
data: LintCacheData,
) {
self.update(path, key, ChangeData::Lint(data));
pub(crate) fn set_linted(&self, path: RelativePathBuf, key: &FileCacheKey, yes: bool) {
self.update(path, key, ChangeData::Linted(yes));
}
pub(crate) fn set_formatted(&self, path: RelativePathBuf, key: &FileCacheKey) {
@@ -339,42 +325,15 @@ pub(crate) struct FileCache {
}
impl FileCache {
/// Convert the file cache into `Diagnostics`, using `path` as file name.
pub(crate) fn to_diagnostics(&self, path: &Path) -> Option<Diagnostics> {
self.data.lint.as_ref().map(|lint| {
let diagnostics = if lint.messages.is_empty() {
Vec::new()
} else {
let file = SourceFileBuilder::new(path.to_string_lossy(), &*lint.source).finish();
lint.messages
.iter()
.map(|msg| {
create_lint_diagnostic(
&msg.body,
msg.suggestion.as_ref(),
msg.range,
msg.fix.clone(),
msg.parent,
file.clone(),
msg.noqa_offset,
msg.rule,
)
})
.collect()
};
let notebook_indexes = if let Some(notebook_index) = lint.notebook_index.as_ref() {
FxHashMap::from_iter([(path.to_string_lossy().to_string(), notebook_index.clone())])
} else {
FxHashMap::default()
};
Diagnostics::new(diagnostics, notebook_indexes)
})
/// Return whether or not the file in the cache was linted and found to have no diagnostics.
pub(crate) fn linted(&self) -> bool {
self.data.linted
}
}
#[derive(Debug, Default, bincode::Decode, bincode::Encode)]
struct FileCacheData {
lint: Option<LintCacheData>,
linted: bool,
formatted: bool,
}
@@ -410,88 +369,6 @@ pub(crate) fn init(path: &Path) -> Result<()> {
Ok(())
}
#[derive(bincode::Decode, Debug, bincode::Encode, PartialEq)]
pub(crate) struct LintCacheData {
/// Imports made.
// pub(super) imports: ImportMap,
/// Diagnostic messages.
pub(super) messages: Vec<CacheMessage>,
/// Source code of the file.
///
/// # Notes
///
/// This will be empty if `messages` is empty.
pub(super) source: String,
/// Notebook index if this file is a Jupyter Notebook.
#[bincode(with_serde)]
pub(super) notebook_index: Option<NotebookIndex>,
}
impl LintCacheData {
pub(crate) fn from_diagnostics(
diagnostics: &[Diagnostic],
notebook_index: Option<NotebookIndex>,
) -> Self {
let source = if let Some(msg) = diagnostics.first() {
msg.expect_ruff_source_file().source_text().to_owned()
} else {
String::new() // No messages, no need to keep the source!
};
let messages = diagnostics
.iter()
// Parse the kebab-case rule name into a `Rule`. This will fail for syntax errors, so
// this also serves to filter them out, but we shouldn't be caching files with syntax
// errors anyway.
.filter_map(|msg| Some((msg.name().parse().ok()?, msg)))
.map(|(rule, msg)| {
// Make sure that all message use the same source file.
assert_eq!(
msg.expect_ruff_source_file(),
diagnostics.first().unwrap().expect_ruff_source_file(),
"message uses a different source file"
);
CacheMessage {
rule,
body: msg.body().to_string(),
suggestion: msg.first_help_text().map(ToString::to_string),
range: msg.expect_range(),
parent: msg.parent(),
fix: msg.fix().cloned(),
noqa_offset: msg.noqa_offset(),
}
})
.collect();
Self {
messages,
source,
notebook_index,
}
}
}
/// On disk representation of a diagnostic message.
#[derive(bincode::Decode, Debug, bincode::Encode, PartialEq)]
pub(super) struct CacheMessage {
/// The rule for the cached diagnostic.
#[bincode(with_serde)]
rule: Rule,
/// The message body to display to the user, to explain the diagnostic.
body: String,
/// The message to display to the user, to explain the suggested fix.
suggestion: Option<String>,
/// Range into the message's [`FileCache::source`].
#[bincode(with_serde)]
range: TextRange,
#[bincode(with_serde)]
parent: Option<TextSize>,
#[bincode(with_serde)]
fix: Option<Fix>,
#[bincode(with_serde)]
noqa_offset: Option<TextSize>,
}
pub(crate) trait PackageCaches {
fn get(&self, package_root: &Path) -> Option<&Cache>;
@@ -579,15 +456,15 @@ struct Change {
#[derive(Debug)]
enum ChangeData {
Lint(LintCacheData),
Linted(bool),
Formatted,
}
impl ChangeData {
fn apply(self, data: &mut FileCacheData) {
match self {
ChangeData::Lint(new_lint) => {
data.lint = Some(new_lint);
ChangeData::Linted(yes) => {
data.linted = yes;
}
ChangeData::Formatted => {
data.formatted = true;
@@ -612,7 +489,6 @@ mod tests {
use test_case::test_case;
use ruff_cache::CACHE_DIR_NAME;
use ruff_db::diagnostic::Diagnostic;
use ruff_linter::package::PackageRoot;
use ruff_linter::settings::LinterSettings;
use ruff_linter::settings::flags;
@@ -620,7 +496,7 @@ mod tests {
use ruff_python_ast::{PySourceType, PythonVersion};
use ruff_workspace::Settings;
use crate::cache::{self, FileCache, FileCacheData, FileCacheKey};
use crate::cache::{self, ChangeData, FileCache, FileCacheData, FileCacheKey};
use crate::cache::{Cache, RelativePathBuf};
use crate::commands::format::{FormatCommandError, FormatMode, FormatResult, format_path};
use crate::diagnostics::{Diagnostics, lint_path};
@@ -647,7 +523,7 @@ mod tests {
assert_eq!(cache.changes.lock().unwrap().len(), 0);
let mut paths = Vec::new();
let mut parse_errors = Vec::new();
let mut paths_with_diagnostics = Vec::new();
let mut expected_diagnostics = Diagnostics::default();
for entry in fs::read_dir(&package_root).unwrap() {
let entry = entry.unwrap();
@@ -671,7 +547,7 @@ mod tests {
continue;
}
let diagnostics = lint_path(
let mut diagnostics = lint_path(
&path,
Some(PackageRoot::root(&package_root)),
&settings.linter,
@@ -681,8 +557,15 @@ mod tests {
UnsafeFixes::Enabled,
)
.unwrap();
if diagnostics.inner.iter().any(Diagnostic::is_invalid_syntax) {
parse_errors.push(path.clone());
if diagnostics.inner.is_empty() {
// We won't load a notebook index from the cache for files without diagnostics,
// so remove them from `expected_diagnostics` too. This allows us to keep the
// full equality assertion below.
diagnostics
.notebook_indexes
.remove(&path.to_string_lossy().to_string());
} else {
paths_with_diagnostics.push(path.clone());
}
paths.push(path);
expected_diagnostics += diagnostics;
@@ -695,11 +578,11 @@ mod tests {
let cache = Cache::open(package_root.clone(), &settings);
assert_ne!(cache.package.files.len(), 0);
parse_errors.sort();
paths_with_diagnostics.sort();
for path in &paths {
if parse_errors.binary_search(path).is_ok() {
continue; // We don't cache parsing errors.
if paths_with_diagnostics.binary_search(path).is_ok() {
continue; // We don't cache files with diagnostics.
}
let relative_path = cache.relative_path(path).unwrap();
@@ -733,7 +616,7 @@ mod tests {
#[test]
fn cache_adds_file_on_lint() {
let source: &[u8] = b"a = 1\n\n__all__ = list([\"a\", \"b\"])\n";
let source: &[u8] = b"a = 1\n\n__all__ = list([\"a\"])\n";
let test_cache = TestCache::new("cache_adds_file_on_lint");
let cache = test_cache.open();
@@ -757,7 +640,7 @@ mod tests {
#[test]
fn cache_adds_files_on_lint() {
let source: &[u8] = b"a = 1\n\n__all__ = list([\"a\", \"b\"])\n";
let source: &[u8] = b"a = 1\n\n__all__ = list([\"a\"])\n";
let test_cache = TestCache::new("cache_adds_files_on_lint");
let cache = test_cache.open();
@@ -782,6 +665,40 @@ mod tests {
cache.persist().unwrap();
}
#[test]
fn cache_does_not_add_file_on_lint_with_diagnostic() {
let source: &[u8] = b"a = 1\n\n__all__ = list([\"a\", \"b\"])\n";
let test_cache = TestCache::new("cache_does_not_add_file_on_lint_with_diagnostic");
let cache = test_cache.open();
test_cache.write_source_file("source.py", source);
assert_eq!(cache.changes.lock().unwrap().len(), 0);
cache.persist().unwrap();
let cache = test_cache.open();
let results = test_cache
.lint_file_with_cache("source.py", &cache)
.expect("Failed to lint test file");
assert_eq!(results.inner.len(), 1, "Expected one F822 diagnostic");
assert_eq!(
cache.changes.lock().unwrap().len(),
1,
"Files with diagnostics still trigger change events"
);
assert!(
cache
.changes
.lock()
.unwrap()
.last()
.is_some_and(|change| matches!(change.new_data, ChangeData::Linted(false))),
"Files with diagnostics are marked as unlinted"
);
cache.persist().unwrap();
}
#[test]
fn cache_adds_files_on_format() {
let source: &[u8] = b"a = 1\n\n__all__ = list([\"a\", \"b\"])\n";
@@ -812,7 +729,7 @@ mod tests {
#[test]
fn cache_invalidated_on_file_modified_time() {
let source: &[u8] = b"a = 1\n\n__all__ = list([\"a\", \"b\"])\n";
let source: &[u8] = b"a = 1\n\n__all__ = list([\"a\"])\n";
let test_cache = TestCache::new("cache_invalidated_on_file_modified_time");
let cache = test_cache.open();
@@ -869,7 +786,7 @@ mod tests {
file.set_permissions(perms)
}
let source: &[u8] = b"a = 1\n\n__all__ = list([\"a\", \"b\"])\n";
let source: &[u8] = b"a = 1\n\n__all__ = list([\"a\"])\n";
let test_cache = TestCache::new("cache_invalidated_on_permission_change");
let cache = test_cache.open();
@@ -922,7 +839,7 @@ mod tests {
);
// Now actually lint a file.
let source: &[u8] = b"a = 1\n\n__all__ = list([\"a\", \"b\"])\n";
let source: &[u8] = b"a = 1\n\n__all__ = list([\"a\"])\n";
test_cache.write_source_file("new.py", source);
let new_path_key = RelativePathBuf::from("new.py");
assert_eq!(cache.changes.lock().unwrap().len(), 0);
@@ -945,7 +862,7 @@ mod tests {
#[test]
fn format_updates_cache_entry() {
let source: &[u8] = b"a = 1\n\n__all__ = list([\"a\", \"b\"])\n";
let source: &[u8] = b"a = 1\n\n__all__ = list([\"a\"])\n";
let test_cache = TestCache::new("format_updates_cache_entry");
let cache = test_cache.open();
@@ -979,7 +896,7 @@ mod tests {
panic!("Cache entry for `source.py` is missing.");
};
assert!(file_cache.data.lint.is_some());
assert!(file_cache.data.linted);
assert!(file_cache.data.formatted);
}
@@ -1029,7 +946,7 @@ mod tests {
panic!("Cache entry for `source.py` is missing.");
};
assert_eq!(file_cache.data.lint, None);
assert!(!file_cache.data.linted);
assert!(file_cache.data.formatted);
}

View File

@@ -20,15 +20,21 @@ use ruff_linter::settings::types::UnsafeFixes;
use ruff_linter::settings::{LinterSettings, flags};
use ruff_linter::source_kind::{SourceError, SourceKind};
use ruff_linter::{IOError, Violation, fs};
use ruff_notebook::{Notebook, NotebookError, NotebookIndex};
use ruff_notebook::{NotebookError, NotebookIndex};
use ruff_python_ast::{PySourceType, SourceType, TomlSourceType};
use ruff_source_file::SourceFileBuilder;
use ruff_text_size::TextRange;
use ruff_workspace::Settings;
use rustc_hash::FxHashMap;
use crate::cache::{Cache, FileCacheKey, LintCacheData};
use crate::cache::{Cache, FileCache, FileCacheKey};
/// A collection of [`Diagnostic`]s and additional information needed to render them.
///
/// Note that `notebook_indexes` may be empty if there are no diagnostics because the
/// `NotebookIndex` isn't cached in this case. This isn't a problem for any current uses as of
/// 2025-08-12, which are all related to diagnostic rendering, but could be surprising if used
/// differently in the future.
#[derive(Debug, Default, PartialEq)]
pub(crate) struct Diagnostics {
pub(crate) inner: Vec<Diagnostic>,
@@ -193,19 +199,9 @@ pub(crate) fn lint_path(
let cache_key = FileCacheKey::from_path(path).context("Failed to create cache key")?;
let cached_diagnostics = cache
.get(relative_path, &cache_key)
.and_then(|entry| entry.to_diagnostics(path));
if let Some(diagnostics) = cached_diagnostics {
// `FixMode::Generate` and `FixMode::Diff` rely on side-effects (writing to disk,
// and writing the diff to stdout, respectively). If a file has diagnostics, we
// need to avoid reading from and writing to the cache in these modes.
if match fix_mode {
flags::FixMode::Generate => true,
flags::FixMode::Apply | flags::FixMode::Diff => {
diagnostics.inner.is_empty() && diagnostics.fixed.is_empty()
}
} {
return Ok(diagnostics);
}
.is_some_and(FileCache::linted);
if cached_diagnostics {
return Ok(Diagnostics::default());
}
// Stash the file metadata for later so when we update the cache it reflects the prerun
@@ -322,31 +318,21 @@ pub(crate) fn lint_path(
(result, transformed, fixed)
};
let has_error = result.has_syntax_errors();
let diagnostics = result.diagnostics;
if let Some((cache, relative_path, key)) = caching {
// We don't cache parsing errors.
if !has_error {
// `FixMode::Apply` and `FixMode::Diff` rely on side-effects (writing to disk,
// and writing the diff to stdout, respectively). If a file has diagnostics, we
// need to avoid reading from and writing to the cache in these modes.
if match fix_mode {
flags::FixMode::Generate => true,
flags::FixMode::Apply | flags::FixMode::Diff => {
diagnostics.is_empty() && fixed.is_empty()
}
} {
cache.update_lint(
relative_path.to_owned(),
&key,
LintCacheData::from_diagnostics(
&diagnostics,
transformed.as_ipy_notebook().map(Notebook::index).cloned(),
),
);
}
}
// `FixMode::Apply` and `FixMode::Diff` rely on side-effects (writing to disk,
// and writing the diff to stdout, respectively). If a file has diagnostics
// with fixes, we need to avoid reading from and writing to the cache in these
// modes.
let use_fixes = match fix_mode {
flags::FixMode::Generate => true,
flags::FixMode::Apply | flags::FixMode::Diff => fixed.is_empty(),
};
// We don't cache files with diagnostics.
let linted = diagnostics.is_empty() && use_fixes;
cache.set_linted(relative_path.to_owned(), &key, linted);
}
let notebook_indexes = if let SourceKind::IpyNotebook(notebook) = transformed {

View File

@@ -19,7 +19,8 @@ static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
any(
target_arch = "x86_64",
target_arch = "aarch64",
target_arch = "powerpc64"
target_arch = "powerpc64",
target_arch = "riscv64"
)
))]
#[global_allocator]

View File

@@ -115,12 +115,13 @@ fn stdin_error() {
success: false
exit_code: 1
----- stdout -----
-:1:8: F401 [*] `os` imported but unused
F401 [*] `os` imported but unused
--> -:1:8
|
1 | import os
| ^^ F401
| ^^
|
= help: Remove unused import: `os`
help: Remove unused import: `os`
Found 1 error.
[*] 1 fixable with the `--fix` option.
@@ -139,12 +140,13 @@ fn stdin_filename() {
success: false
exit_code: 1
----- stdout -----
F401.py:1:8: F401 [*] `os` imported but unused
F401 [*] `os` imported but unused
--> F401.py:1:8
|
1 | import os
| ^^ F401
| ^^
|
= help: Remove unused import: `os`
help: Remove unused import: `os`
Found 1 error.
[*] 1 fixable with the `--fix` option.
@@ -174,19 +176,21 @@ import bar # unused import
success: false
exit_code: 1
----- stdout -----
bar.py:2:8: F401 [*] `bar` imported but unused
F401 [*] `bar` imported but unused
--> bar.py:2:8
|
2 | import bar # unused import
| ^^^ F401
| ^^^
|
= help: Remove unused import: `bar`
help: Remove unused import: `bar`
foo.py:2:8: F401 [*] `foo` imported but unused
F401 [*] `foo` imported but unused
--> foo.py:2:8
|
2 | import foo # unused import
| ^^^ F401
| ^^^
|
= help: Remove unused import: `foo`
help: Remove unused import: `foo`
Found 2 errors.
[*] 2 fixable with the `--fix` option.
@@ -208,12 +212,13 @@ fn check_warn_stdin_filename_with_files() {
success: false
exit_code: 1
----- stdout -----
F401.py:1:8: F401 [*] `os` imported but unused
F401 [*] `os` imported but unused
--> F401.py:1:8
|
1 | import os
| ^^ F401
| ^^
|
= help: Remove unused import: `os`
help: Remove unused import: `os`
Found 1 error.
[*] 1 fixable with the `--fix` option.
@@ -234,12 +239,13 @@ fn stdin_source_type_py() {
success: false
exit_code: 1
----- stdout -----
TCH.py:1:8: F401 [*] `os` imported but unused
F401 [*] `os` imported but unused
--> TCH.py:1:8
|
1 | import os
| ^^ F401
| ^^
|
= help: Remove unused import: `os`
help: Remove unused import: `os`
Found 1 error.
[*] 1 fixable with the `--fix` option.
@@ -471,10 +477,11 @@ fn stdin_fix_jupyter() {
"nbformat_minor": 5
}
----- stderr -----
Jupyter.ipynb:cell 3:1:7: F821 Undefined name `x`
F821 Undefined name `x`
--> Jupyter.ipynb:cell 3:1:7
|
1 | print(x)
| ^ F821
| ^
|
Found 3 errors (2 fixed, 1 remaining).
@@ -569,19 +576,21 @@ fn stdin_override_parser_ipynb() {
success: false
exit_code: 1
----- stdout -----
Jupyter.py:cell 1:1:8: F401 [*] `os` imported but unused
F401 [*] `os` imported but unused
--> Jupyter.py:cell 1:1:8
|
1 | import os
| ^^ F401
| ^^
|
= help: Remove unused import: `os`
help: Remove unused import: `os`
Jupyter.py:cell 3:1:8: F401 [*] `sys` imported but unused
F401 [*] `sys` imported but unused
--> Jupyter.py:cell 3:1:8
|
1 | import sys
| ^^^ F401
| ^^^
|
= help: Remove unused import: `sys`
help: Remove unused import: `sys`
Found 2 errors.
[*] 2 fixable with the `--fix` option.
@@ -605,12 +614,13 @@ fn stdin_override_parser_py() {
success: false
exit_code: 1
----- stdout -----
F401.ipynb:1:8: F401 [*] `os` imported but unused
F401 [*] `os` imported but unused
--> F401.ipynb:1:8
|
1 | import os
| ^^ F401
| ^^
|
= help: Remove unused import: `os`
help: Remove unused import: `os`
Found 1 error.
[*] 1 fixable with the `--fix` option.
@@ -633,12 +643,13 @@ fn stdin_fix_when_not_fixable_should_still_print_contents() {
print(sys.version)
----- stderr -----
-:3:4: F634 If test is a tuple, which is always `True`
F634 If test is a tuple, which is always `True`
--> -:3:4
|
1 | import sys
2 |
3 | if (1, 2):
| ^^^^^^ F634
| ^^^^^^
4 | print(sys.version)
|
@@ -798,7 +809,8 @@ fn stdin_parse_error() {
success: false
exit_code: 1
----- stdout -----
-:1:16: invalid-syntax: Expected one or more symbol names after import
invalid-syntax: Expected one or more symbol names after import
--> -:1:16
|
1 | from foo import
| ^
@@ -818,14 +830,16 @@ fn stdin_multiple_parse_error() {
success: false
exit_code: 1
----- stdout -----
-:1:16: invalid-syntax: Expected one or more symbol names after import
invalid-syntax: Expected one or more symbol names after import
--> -:1:16
|
1 | from foo import
| ^
2 | bar =
|
-:2:6: invalid-syntax: Expected an expression
invalid-syntax: Expected an expression
--> -:2:6
|
1 | from foo import
2 | bar =
@@ -847,7 +861,8 @@ fn parse_error_not_included() {
success: false
exit_code: 1
----- stdout -----
-:1:6: invalid-syntax: Expected an expression
invalid-syntax: Expected an expression
--> -:1:6
|
1 | foo =
| ^
@@ -867,10 +882,11 @@ fn full_output_preview() {
success: false
exit_code: 1
----- stdout -----
-:1:1: E741 Ambiguous variable name: `l`
E741 Ambiguous variable name: `l`
--> -:1:1
|
1 | l = 1
| ^ E741
| ^
|
Found 1 error.
@@ -895,10 +911,11 @@ preview = true
success: false
exit_code: 1
----- stdout -----
-:1:1: E741 Ambiguous variable name: `l`
E741 Ambiguous variable name: `l`
--> -:1:1
|
1 | l = 1
| ^ E741
| ^
|
Found 1 error.
@@ -916,10 +933,11 @@ fn full_output_format() {
success: false
exit_code: 1
----- stdout -----
-:1:1: E741 Ambiguous variable name: `l`
E741 Ambiguous variable name: `l`
--> -:1:1
|
1 | l = 1
| ^ E741
| ^
|
Found 1 error.
@@ -1406,7 +1424,9 @@ fn redirect_direct() {
success: false
exit_code: 1
----- stdout -----
-:1:1: RUF950 Hey this is a test rule that was redirected from another.
RUF950 Hey this is a test rule that was redirected from another.
--> -:1:1
Found 1 error.
----- stderr -----
@@ -1438,7 +1458,9 @@ fn redirect_prefix() {
success: false
exit_code: 1
----- stdout -----
-:1:1: RUF950 Hey this is a test rule that was redirected from another.
RUF950 Hey this is a test rule that was redirected from another.
--> -:1:1
Found 1 error.
----- stderr -----
@@ -1455,7 +1477,9 @@ fn deprecated_direct() {
success: false
exit_code: 1
----- stdout -----
-:1:1: RUF920 Hey this is a deprecated test rule.
RUF920 Hey this is a deprecated test rule.
--> -:1:1
Found 1 error.
----- stderr -----
@@ -1472,8 +1496,12 @@ fn deprecated_multiple_direct() {
success: false
exit_code: 1
----- stdout -----
-:1:1: RUF920 Hey this is a deprecated test rule.
-:1:1: RUF921 Hey this is another deprecated test rule.
RUF920 Hey this is a deprecated test rule.
--> -:1:1
RUF921 Hey this is another deprecated test rule.
--> -:1:1
Found 2 errors.
----- stderr -----
@@ -1491,8 +1519,12 @@ fn deprecated_indirect() {
success: false
exit_code: 1
----- stdout -----
-:1:1: RUF920 Hey this is a deprecated test rule.
-:1:1: RUF921 Hey this is another deprecated test rule.
RUF920 Hey this is a deprecated test rule.
--> -:1:1
RUF921 Hey this is another deprecated test rule.
--> -:1:1
Found 2 errors.
----- stderr -----
@@ -1638,22 +1670,23 @@ fn check_input_from_argfile() -> Result<()> {
(file_a_path.display().to_string().as_str(), "/path/to/a.py"),
]}, {
assert_cmd_snapshot!(cmd
.pass_stdin(""), @r###"
.pass_stdin(""), @r"
success: false
exit_code: 1
----- stdout -----
/path/to/a.py:1:8: F401 [*] `os` imported but unused
F401 [*] `os` imported but unused
--> /path/to/a.py:1:8
|
1 | import os
| ^^ F401
| ^^
|
= help: Remove unused import: `os`
help: Remove unused import: `os`
Found 1 error.
[*] 1 fixable with the `--fix` option.
----- stderr -----
"###);
");
});
Ok(())
@@ -1669,8 +1702,12 @@ fn check_hints_hidden_unsafe_fixes() {
success: false
exit_code: 1
----- stdout -----
-:1:1: RUF901 [*] Hey this is a stable test rule with a safe fix.
-:1:1: RUF902 Hey this is a stable test rule with an unsafe fix.
RUF901 [*] Hey this is a stable test rule with a safe fix.
--> -:1:1
RUF902 Hey this is a stable test rule with an unsafe fix.
--> -:1:1
Found 2 errors.
[*] 1 fixable with the `--fix` option (1 hidden fix can be enabled with the `--unsafe-fixes` option).
@@ -1687,7 +1724,9 @@ fn check_hints_hidden_unsafe_fixes_with_no_safe_fixes() {
success: false
exit_code: 1
----- stdout -----
-:1:1: RUF902 Hey this is a stable test rule with an unsafe fix.
RUF902 Hey this is a stable test rule with an unsafe fix.
--> -:1:1
Found 1 error.
No fixes available (1 hidden fix can be enabled with the `--unsafe-fixes` option).
@@ -1705,8 +1744,12 @@ fn check_no_hint_for_hidden_unsafe_fixes_when_disabled() {
success: false
exit_code: 1
----- stdout -----
-:1:1: RUF901 [*] Hey this is a stable test rule with a safe fix.
-:1:1: RUF902 Hey this is a stable test rule with an unsafe fix.
RUF901 [*] Hey this is a stable test rule with a safe fix.
--> -:1:1
RUF902 Hey this is a stable test rule with an unsafe fix.
--> -:1:1
Found 2 errors.
[*] 1 fixable with the --fix option.
@@ -1725,7 +1768,9 @@ fn check_no_hint_for_hidden_unsafe_fixes_with_no_safe_fixes_when_disabled() {
success: false
exit_code: 1
----- stdout -----
-:1:1: RUF902 Hey this is a stable test rule with an unsafe fix.
RUF902 Hey this is a stable test rule with an unsafe fix.
--> -:1:1
Found 1 error.
----- stderr -----
@@ -1742,8 +1787,12 @@ fn check_shows_unsafe_fixes_with_opt_in() {
success: false
exit_code: 1
----- stdout -----
-:1:1: RUF901 [*] Hey this is a stable test rule with a safe fix.
-:1:1: RUF902 [*] Hey this is a stable test rule with an unsafe fix.
RUF901 [*] Hey this is a stable test rule with a safe fix.
--> -:1:1
RUF902 [*] Hey this is a stable test rule with an unsafe fix.
--> -:1:1
Found 2 errors.
[*] 2 fixable with the --fix option.
@@ -1764,7 +1813,9 @@ fn fix_applies_safe_fixes_by_default() {
# fix from stable-test-rule-safe-fix
----- stderr -----
-:1:1: RUF902 Hey this is a stable test rule with an unsafe fix.
RUF902 Hey this is a stable test rule with an unsafe fix.
--> -:1:1
Found 2 errors (1 fixed, 1 remaining).
No fixes available (1 hidden fix can be enabled with the `--unsafe-fixes` option).
");
@@ -1801,7 +1852,9 @@ fn fix_does_not_apply_display_only_fixes() {
----- stdout -----
def add_to_list(item, some_list=[]): ...
----- stderr -----
-:1:1: RUF903 Hey this is a stable test rule with a display only fix.
RUF903 Hey this is a stable test rule with a display only fix.
--> -:1:1
Found 1 error.
");
}
@@ -1819,7 +1872,9 @@ fn fix_does_not_apply_display_only_fixes_with_unsafe_fixes_enabled() {
----- stdout -----
def add_to_list(item, some_list=[]): ...
----- stderr -----
-:1:1: RUF903 Hey this is a stable test rule with a display only fix.
RUF903 Hey this is a stable test rule with a display only fix.
--> -:1:1
Found 1 error.
");
}
@@ -1836,7 +1891,9 @@ fn fix_only_unsafe_fixes_available() {
----- stdout -----
----- stderr -----
-:1:1: RUF902 Hey this is a stable test rule with an unsafe fix.
RUF902 Hey this is a stable test rule with an unsafe fix.
--> -:1:1
Found 1 error.
No fixes available (1 hidden fix can be enabled with the `--unsafe-fixes` option).
");
@@ -1972,8 +2029,12 @@ extend-unsafe-fixes = ["RUF901"]
success: false
exit_code: 1
----- stdout -----
-:1:1: RUF901 Hey this is a stable test rule with a safe fix.
-:1:1: RUF902 Hey this is a stable test rule with an unsafe fix.
RUF901 Hey this is a stable test rule with a safe fix.
--> -:1:1
RUF902 Hey this is a stable test rule with an unsafe fix.
--> -:1:1
Found 2 errors.
No fixes available (2 hidden fixes can be enabled with the `--unsafe-fixes` option).
@@ -2004,8 +2065,12 @@ extend-safe-fixes = ["RUF902"]
success: false
exit_code: 1
----- stdout -----
-:1:1: RUF901 [*] Hey this is a stable test rule with a safe fix.
-:1:1: RUF902 [*] Hey this is a stable test rule with an unsafe fix.
RUF901 [*] Hey this is a stable test rule with a safe fix.
--> -:1:1
RUF902 [*] Hey this is a stable test rule with an unsafe fix.
--> -:1:1
Found 2 errors.
[*] 2 fixable with the `--fix` option.
@@ -2038,8 +2103,12 @@ extend-safe-fixes = ["RUF902"]
success: false
exit_code: 1
----- stdout -----
-:1:1: RUF901 [*] Hey this is a stable test rule with a safe fix.
-:1:1: RUF902 Hey this is a stable test rule with an unsafe fix.
RUF901 [*] Hey this is a stable test rule with a safe fix.
--> -:1:1
RUF902 Hey this is a stable test rule with an unsafe fix.
--> -:1:1
Found 2 errors.
[*] 1 fixable with the `--fix` option (1 hidden fix can be enabled with the `--unsafe-fixes` option).
@@ -2074,13 +2143,27 @@ extend-safe-fixes = ["RUF9"]
success: false
exit_code: 1
----- stdout -----
-:1:1: RUF900 Hey this is a stable test rule.
-:1:1: RUF901 Hey this is a stable test rule with a safe fix.
-:1:1: RUF902 [*] Hey this is a stable test rule with an unsafe fix.
-:1:1: RUF903 Hey this is a stable test rule with a display only fix.
-:1:1: RUF920 Hey this is a deprecated test rule.
-:1:1: RUF921 Hey this is another deprecated test rule.
-:1:1: RUF950 Hey this is a test rule that was redirected from another.
RUF900 Hey this is a stable test rule.
--> -:1:1
RUF901 Hey this is a stable test rule with a safe fix.
--> -:1:1
RUF902 [*] Hey this is a stable test rule with an unsafe fix.
--> -:1:1
RUF903 Hey this is a stable test rule with a display only fix.
--> -:1:1
RUF920 Hey this is a deprecated test rule.
--> -:1:1
RUF921 Hey this is another deprecated test rule.
--> -:1:1
RUF950 Hey this is a test rule that was redirected from another.
--> -:1:1
Found 7 errors.
[*] 1 fixable with the `--fix` option (1 hidden fix can be enabled with the `--unsafe-fixes` option).
@@ -2141,10 +2224,11 @@ def log(x, base) -> float:
success: false
exit_code: 1
----- stdout -----
-:2:5: D417 Missing argument description in the docstring for `log`: `base`
D417 Missing argument description in the docstring for `log`: `base`
--> -:2:5
|
2 | def log(x, base) -> float:
| ^^^ D417
| ^^^
3 | """Calculate natural log of a value
|
@@ -2177,14 +2261,15 @@ select = ["RUF017"]
success: false
exit_code: 1
----- stdout -----
-:3:1: RUF017 Avoid quadratic list summation
RUF017 Avoid quadratic list summation
--> -:3:1
|
1 | x = [1, 2, 3]
2 | y = [4, 5, 6]
3 | sum([x, y], [])
| ^^^^^^^^^^^^^^^ RUF017
| ^^^^^^^^^^^^^^^
|
= help: Replace with `functools.reduce`
help: Replace with `functools.reduce`
Found 1 error.
No fixes available (1 hidden fix can be enabled with the `--unsafe-fixes` option).
@@ -2217,14 +2302,15 @@ unfixable = ["RUF"]
success: false
exit_code: 1
----- stdout -----
-:3:1: RUF017 Avoid quadratic list summation
RUF017 Avoid quadratic list summation
--> -:3:1
|
1 | x = [1, 2, 3]
2 | y = [4, 5, 6]
3 | sum([x, y], [])
| ^^^^^^^^^^^^^^^ RUF017
| ^^^^^^^^^^^^^^^
|
= help: Replace with `functools.reduce`
help: Replace with `functools.reduce`
Found 1 error.
@@ -2246,10 +2332,11 @@ fn pyproject_toml_stdin_syntax_error() {
success: false
exit_code: 1
----- stdout -----
pyproject.toml:1:9: RUF200 Failed to parse pyproject.toml: unclosed table, expected `]`
RUF200 Failed to parse pyproject.toml: unclosed table, expected `]`
--> pyproject.toml:1:9
|
1 | [project
| ^ RUF200
| ^
|
Found 1 error.
@@ -2271,11 +2358,12 @@ fn pyproject_toml_stdin_schema_error() {
success: false
exit_code: 1
----- stdout -----
pyproject.toml:2:8: RUF200 Failed to parse pyproject.toml: invalid type: integer `1`, expected a string
RUF200 Failed to parse pyproject.toml: invalid type: integer `1`, expected a string
--> pyproject.toml:2:8
|
1 | [project]
2 | name = 1
| ^ RUF200
| ^
|
Found 1 error.
@@ -2363,11 +2451,12 @@ fn pyproject_toml_stdin_schema_error_fix() {
[project]
name = 1
----- stderr -----
pyproject.toml:2:8: RUF200 Failed to parse pyproject.toml: invalid type: integer `1`, expected a string
RUF200 Failed to parse pyproject.toml: invalid type: integer `1`, expected a string
--> pyproject.toml:2:8
|
1 | [project]
2 | name = 1
| ^ RUF200
| ^
|
Found 1 error.

View File

@@ -5588,15 +5588,15 @@ fn cookiecutter_globbing() -> Result<()> {
.args(STDIN_BASE_OPTIONS)
.arg("--select=F811")
.current_dir(tempdir.path()), @r"
success: false
exit_code: 1
----- stdout -----
{{cookiecutter.repo_name}}/tests/maintest.py:3:8: F811 [*] Redefinition of unused `foo` from line 1
Found 1 error.
[*] 1 fixable with the `--fix` option.
success: false
exit_code: 1
----- stdout -----
{{cookiecutter.repo_name}}/tests/maintest.py:3:8: F811 [*] Redefinition of unused `foo` from line 1: `foo` redefined here
Found 1 error.
[*] 1 fixable with the `--fix` option.
----- stderr -----
");
----- stderr -----
");
});
Ok(())
@@ -5801,3 +5801,32 @@ fn future_annotations_preview_warning() {
",
);
}
#[test]
fn up045_nested_optional_flatten_all() {
let contents = "\
from typing import Optional
nested_optional: Optional[Optional[Optional[str]]] = None
";
assert_cmd_snapshot!(
Command::new(get_cargo_bin(BIN_NAME))
.args(STDIN_BASE_OPTIONS)
.args(["--select", "UP045", "--diff", "--target-version", "py312"])
.arg("-")
.pass_stdin(contents),
@r"
success: false
exit_code: 1
----- stdout -----
@@ -1,2 +1,2 @@
from typing import Optional
-nested_optional: Optional[Optional[Optional[str]]] = None
+nested_optional: str | None = None
----- stderr -----
Would fix 1 error.
",
);
}

View File

@@ -16,25 +16,28 @@ info:
success: false
exit_code: 1
----- stdout -----
input.py:1:8: F401 [*] `os` imported but unused
F401 [*] `os` imported but unused
--> input.py:1:8
|
1 | import os # F401
| ^^ F401
| ^^
2 | x = y # F821
3 | match 42: # invalid-syntax
|
= help: Remove unused import: `os`
help: Remove unused import: `os`
input.py:2:5: F821 Undefined name `y`
F821 Undefined name `y`
--> input.py:2:5
|
1 | import os # F401
2 | x = y # F821
| ^ F821
| ^
3 | match 42: # invalid-syntax
4 | case _: ...
|
input.py:3:1: invalid-syntax: Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)
invalid-syntax: Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)
--> input.py:3:1
|
1 | import os # F401
2 | x = y # F821

View File

@@ -19,7 +19,7 @@ exit_code: 1
[
{
"check_name": "F401",
"description": "`os` imported but unused",
"description": "F401: `os` imported but unused",
"fingerprint": "4dbad37161e65c72",
"location": {
"path": "input.py",
@@ -38,7 +38,7 @@ exit_code: 1
},
{
"check_name": "F821",
"description": "Undefined name `y`",
"description": "F821: Undefined name `y`",
"fingerprint": "7af59862a085230",
"location": {
"path": "input.py",
@@ -56,8 +56,8 @@ exit_code: 1
"severity": "major"
},
{
"check_name": "syntax-error",
"description": "Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)",
"check_name": "invalid-syntax",
"description": "invalid-syntax: Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)",
"fingerprint": "e558cec859bb66e8",
"location": {
"path": "input.py",

View File

@@ -1201,11 +1201,16 @@ fn format_snippet<'m>(
let is_file_level = snippet.annotations.iter().any(|ann| ann.is_file_level);
if is_file_level {
assert!(
snippet.source.is_empty(),
"Non-empty file-level snippet that won't be rendered: {:?}",
snippet.source
);
// TODO(brent) enable this assertion again once we set `is_file_level` for individual rules.
// It's causing too many false positives currently when the default is to make any
// annotation with a default range file-level. See
// https://github.com/astral-sh/ruff/issues/19688.
//
// assert!(
// snippet.source.is_empty(),
// "Non-empty file-level snippet that won't be rendered: {:?}",
// snippet.source
// );
let header = format_header(origin, main_range, &[], is_first, snippet.cell_index);
return DisplaySet {
display_lines: header.map_or_else(Vec::new, |header| vec![header]),
@@ -1273,13 +1278,20 @@ fn format_header<'a>(
..
} = item
{
if main_range >= range.0 && main_range < range.1 + max(*end_line as usize, 1) {
// At the very end of the `main_range`, report the location as the first character
// in the next line instead of falling back to the default location of `1:1`. This
// is another divergence from upstream.
let end_of_range = range.1 + max(*end_line as usize, 1);
if main_range >= range.0 && main_range < end_of_range {
let char_column = text[0..(main_range - range.0).min(text.len())]
.chars()
.count();
col = char_column + 1;
line_offset = lineno.unwrap_or(1);
break;
} else if main_range == end_of_range {
line_offset = lineno.map_or(1, |line| line + 1);
break;
}
}
}

View File

@@ -86,5 +86,5 @@ walltime = ["ruff_db/os", "ty_project", "divan"]
[target.'cfg(target_os = "windows")'.dev-dependencies]
mimalloc = { workspace = true }
[target.'cfg(all(not(target_os = "windows"), not(target_os = "openbsd"), any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "powerpc64")))'.dev-dependencies]
[target.'cfg(all(not(target_os = "windows"), not(target_os = "openbsd"), any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "powerpc64", target_arch = "riscv64")))'.dev-dependencies]
tikv-jemallocator = { workspace = true }

View File

@@ -21,7 +21,8 @@ static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
any(
target_arch = "x86_64",
target_arch = "aarch64",
target_arch = "powerpc64"
target_arch = "powerpc64",
target_arch = "riscv64"
)
))]
#[global_allocator]

View File

@@ -18,7 +18,8 @@ static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
any(
target_arch = "x86_64",
target_arch = "aarch64",
target_arch = "powerpc64"
target_arch = "powerpc64",
target_arch = "riscv64"
)
))]
#[global_allocator]

View File

@@ -26,7 +26,8 @@ static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
any(
target_arch = "x86_64",
target_arch = "aarch64",
target_arch = "powerpc64"
target_arch = "powerpc64",
target_arch = "riscv64"
)
))]
#[global_allocator]
@@ -42,7 +43,8 @@ static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc;
any(
target_arch = "x86_64",
target_arch = "aarch64",
target_arch = "powerpc64"
target_arch = "powerpc64",
target_arch = "riscv64"
)
))]
#[unsafe(export_name = "_rjem_malloc_conf")]
@@ -77,8 +79,11 @@ fn benchmark_linter(mut group: BenchmarkGroup, settings: &LinterSettings) {
b.iter_batched(
|| parsed.clone(),
|parsed| {
// Assert that file contains no parse errors
assert!(parsed.has_valid_syntax());
let path = case.path();
let result = lint_only(
lint_only(
&path,
None,
settings,
@@ -86,10 +91,7 @@ fn benchmark_linter(mut group: BenchmarkGroup, settings: &LinterSettings) {
&SourceKind::Python(case.code().to_string()),
PySourceType::from(path.as_path()),
ParseSource::Precomputed(parsed),
);
// Assert that file contains no parse errors
assert!(!result.has_syntax_errors());
)
},
criterion::BatchSize::SmallInput,
);

View File

@@ -20,7 +20,8 @@ static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
any(
target_arch = "x86_64",
target_arch = "aarch64",
target_arch = "powerpc64"
target_arch = "powerpc64",
target_arch = "riscv64"
)
))]
#[global_allocator]

View File

@@ -218,6 +218,24 @@ static TANJUN: std::sync::LazyLock<Benchmark<'static>> = std::sync::LazyLock::ne
)
});
static STATIC_FRAME: std::sync::LazyLock<Benchmark<'static>> = std::sync::LazyLock::new(|| {
Benchmark::new(
RealWorldProject {
name: "static-frame",
repository: "https://github.com/static-frame/static-frame",
commit: "34962b41baca5e7f98f5a758d530bff02748a421",
paths: vec![SystemPath::new("static_frame")],
// N.B. `arraykit` is installed as a dependency during mypy_primer runs,
// but it takes much longer to be installed in a Codspeed run than it does in a mypy_primer run
// (seems to be built from source on the Codspeed CI runners for some reason).
dependencies: vec!["numpy"],
max_dep_date: "2025-08-09",
python_version: PythonVersion::PY311,
},
500,
)
});
#[track_caller]
fn run_single_threaded(bencher: Bencher, benchmark: &Benchmark) {
bencher
@@ -232,7 +250,7 @@ fn small(bencher: Bencher, benchmark: &Benchmark) {
run_single_threaded(bencher, benchmark);
}
#[bench(args=[&*COLOUR_SCIENCE, &*PANDAS], sample_size=1, sample_count=3)]
#[bench(args=[&*COLOUR_SCIENCE, &*PANDAS, &*STATIC_FRAME], sample_size=1, sample_count=3)]
fn medium(bencher: Bencher, benchmark: &Benchmark) {
run_single_threaded(bencher, benchmark);
}

View File

@@ -40,6 +40,7 @@ salsa = { workspace = true }
schemars = { workspace = true, optional = true }
serde = { workspace = true, optional = true }
serde_json = { workspace = true, optional = true }
similar = { workspace = true }
thiserror = { workspace = true }
tracing = { workspace = true }
tracing-subscriber = { workspace = true, optional = true }

View File

@@ -22,6 +22,7 @@ mod stylesheet;
/// a characteristic is a deficiency. An example of a characteristic that is
/// _not_ a deficiency is the `reveal_type` diagnostic for our type checker.
#[derive(Debug, Clone, Eq, PartialEq, Hash, get_size2::GetSize)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct Diagnostic {
/// The actual diagnostic.
///
@@ -254,6 +255,11 @@ impl Diagnostic {
.find(|ann| ann.is_primary)
}
/// Returns a mutable borrow of all annotations of this diagnostic.
pub fn annotations_mut(&mut self) -> impl Iterator<Item = &mut Annotation> {
Arc::make_mut(&mut self.inner).annotations.iter_mut()
}
/// Returns the "primary" span of this diagnostic if one exists.
///
/// When there are multiple primary spans, then the first one that was
@@ -310,6 +316,11 @@ impl Diagnostic {
&self.inner.subs
}
/// Returns a mutable borrow of the sub-diagnostics of this diagnostic.
pub fn sub_diagnostics_mut(&mut self) -> impl Iterator<Item = &mut SubDiagnostic> {
Arc::make_mut(&mut self.inner).subs.iter_mut()
}
/// Returns the fix for this diagnostic if it exists.
pub fn fix(&self) -> Option<&Fix> {
self.inner.fix.as_ref()
@@ -490,6 +501,7 @@ impl Diagnostic {
}
#[derive(Debug, Clone, Eq, PartialEq, Hash, get_size2::GetSize)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
struct DiagnosticInner {
id: DiagnosticId,
severity: Severity,
@@ -566,6 +578,7 @@ impl Eq for RenderingSortKey<'_> {}
/// another (for a single parent diagnostic) is the order in which they were
/// attached to the diagnostic.
#[derive(Debug, Clone, Eq, PartialEq, Hash, get_size2::GetSize)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct SubDiagnostic {
/// Like with `Diagnostic`, we box the `SubDiagnostic` to make it
/// pointer-sized.
@@ -621,6 +634,11 @@ impl SubDiagnostic {
&self.inner.annotations
}
/// Returns a mutable borrow of the annotations of this sub-diagnostic.
pub fn annotations_mut(&mut self) -> impl Iterator<Item = &mut Annotation> {
self.inner.annotations.iter_mut()
}
/// Returns a shared borrow of the "primary" annotation of this diagnostic
/// if one exists.
///
@@ -670,6 +688,7 @@ impl SubDiagnostic {
}
#[derive(Debug, Clone, Eq, PartialEq, Hash, get_size2::GetSize)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
struct SubDiagnosticInner {
severity: SubDiagnosticSeverity,
message: DiagnosticMessage,
@@ -698,6 +717,7 @@ struct SubDiagnosticInner {
/// Messages attached to annotations should also be as brief and specific as
/// possible. Long messages could negative impact the quality of rendering.
#[derive(Debug, Clone, Eq, PartialEq, Hash, get_size2::GetSize)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct Annotation {
/// The span of this annotation, corresponding to some subsequence of the
/// user's input that we want to highlight.
@@ -840,6 +860,7 @@ impl Annotation {
/// These tags are used to provide additional information about the annotation.
/// and are passed through to the language server protocol.
#[derive(Debug, Clone, Eq, PartialEq, Hash, get_size2::GetSize)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub enum DiagnosticTag {
/// Unused or unnecessary code. Used for unused parameters, unreachable code, etc.
Unnecessary,
@@ -854,6 +875,7 @@ pub enum DiagnosticTag {
///
/// Rules use kebab case, e.g. `no-foo`.
#[derive(Debug, Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Hash, get_size2::GetSize)]
#[cfg_attr(feature = "serde", derive(serde::Serialize), serde(transparent))]
pub struct LintName(&'static str);
impl LintName {
@@ -866,6 +888,66 @@ impl LintName {
}
}
#[cfg(feature = "serde")]
pub use lint_name_serde::LintRegistryGuard;
#[cfg(feature = "serde")]
mod lint_name_serde {
use super::LintName;
use std::cell::RefCell;
thread_local! {
/// Serde doesn't provide any easy means to pass a value to a [`Deserialize`] implementation,
/// but we need a way to retrieve static [`LintName`]s from the lint registry when deserializing.
///
/// Use the [`LintRegistryGuard`] to initialize the thread local before calling into any
/// deserialization code. It ensures that the thread local variable gets cleaned up
/// once deserialization is done (once the guard gets dropped).
static LINT_REGISTRY: RefCell<Option<LintRegistry>> = const { RefCell::new(None) };
}
type LintRegistry = fn(&str) -> Option<LintName>;
/// Guard to safely change the lint registry for the current thread.
#[must_use]
pub struct LintRegistryGuard {
prev_value: Option<LintRegistry>,
}
impl LintRegistryGuard {
pub fn new(registry: LintRegistry) -> Self {
let prev = LINT_REGISTRY.replace(Some(registry));
Self { prev_value: prev }
}
}
impl Drop for LintRegistryGuard {
fn drop(&mut self) {
LINT_REGISTRY.set(self.prev_value.take());
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for LintName {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let name: &str = serde::Deserialize::deserialize(deserializer)?;
LINT_REGISTRY.with_borrow(|registry| {
let registry = registry
.expect("must set the `LintRegistryGuard` when deserializing a `LintName`");
registry(name).ok_or(serde::de::Error::custom(format!(
"invalid `LintName` {name}"
)))
})
}
}
}
impl std::ops::Deref for LintName {
type Target = str;
@@ -894,6 +976,7 @@ impl PartialEq<&str> for LintName {
/// Uniquely identifies the kind of a diagnostic.
#[derive(Debug, Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Hash, get_size2::GetSize)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub enum DiagnosticId {
Panic,
@@ -1082,6 +1165,30 @@ impl UnifiedFile {
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for UnifiedFile {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match self {
UnifiedFile::Ty(file) => serde::Serialize::serialize(file, serializer),
// Persistent caching is only used in ty.
UnifiedFile::Ruff(..) => panic!("Ruff files are not persistable"),
}
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for UnifiedFile {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
serde::Deserialize::deserialize(deserializer).map(UnifiedFile::Ty)
}
}
/// A unified wrapper for types that can be converted to a [`SourceCode`].
///
/// As with [`UnifiedFile`], ruff and ty use slightly different representations for source code.
@@ -1113,6 +1220,7 @@ impl DiagnosticSource {
/// range isn't present, it semantically implies that the diagnostic refers to
/// the entire file. For example, when the file should be executable but isn't.
#[derive(Debug, Clone, PartialEq, Eq, Hash, get_size2::GetSize)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct Span {
file: UnifiedFile,
range: Option<TextRange>,
@@ -1191,6 +1299,7 @@ impl From<crate::files::FileRange> for Span {
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash, get_size2::GetSize)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub enum Severity {
Info,
Warning,
@@ -1226,6 +1335,7 @@ impl Severity {
/// used for main diagnostics. If we want to add `Severity::Help` in the future, this type could be
/// deleted and the two combined again.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash, get_size2::GetSize)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub enum SubDiagnosticSeverity {
Help,
Info,
@@ -1279,6 +1389,10 @@ pub struct DisplayDiagnosticConfig {
hide_severity: bool,
/// Whether to show the availability of a fix in a diagnostic.
show_fix_status: bool,
/// Whether to show the diff for an available fix after the main diagnostic.
///
/// This currently only applies to `DiagnosticFormat::Full`.
show_fix_diff: bool,
/// The lowest applicability that should be shown when reporting diagnostics.
fix_applicability: Applicability,
}
@@ -1326,6 +1440,14 @@ impl DisplayDiagnosticConfig {
}
}
/// Whether to show a diff for an available fix after the main diagnostic.
pub fn show_fix_diff(self, yes: bool) -> DisplayDiagnosticConfig {
DisplayDiagnosticConfig {
show_fix_diff: yes,
..self
}
}
/// Set the lowest fix applicability that should be shown.
///
/// In other words, an applicability of `Safe` (the default) would suppress showing fixes or fix
@@ -1349,6 +1471,7 @@ impl Default for DisplayDiagnosticConfig {
preview: false,
hide_severity: false,
show_fix_status: false,
show_fix_diff: false,
fix_applicability: Applicability::Safe,
}
}
@@ -1461,6 +1584,7 @@ impl std::fmt::Display for ConciseMessage<'_> {
/// a blanket trait implementation for `IntoDiagnosticMessage` for
/// anything that implements `std::fmt::Display`.
#[derive(Clone, Debug, Eq, PartialEq, Hash, get_size2::GetSize)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct DiagnosticMessage(Box<str>);
impl DiagnosticMessage {
@@ -1524,7 +1648,11 @@ impl<T: std::fmt::Display> IntoDiagnosticMessage for T {
///
/// For Ruff rules this means the noqa code.
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Default, Hash, get_size2::GetSize)]
#[cfg_attr(feature = "serde", derive(serde::Serialize), serde(transparent))]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(transparent)
)]
pub struct SecondaryCode(String);
impl SecondaryCode {

View File

@@ -2,15 +2,15 @@ use std::borrow::Cow;
use std::collections::BTreeMap;
use std::path::Path;
use full::FullRenderer;
use ruff_annotate_snippets::{
Annotation as AnnotateAnnotation, Level as AnnotateLevel, Message as AnnotateMessage,
Renderer as AnnotateRenderer, Snippet as AnnotateSnippet,
Snippet as AnnotateSnippet,
};
use ruff_notebook::{Notebook, NotebookIndex};
use ruff_source_file::{LineIndex, OneIndexed, SourceCode};
use ruff_text_size::{TextLen, TextRange, TextSize};
use crate::diagnostic::stylesheet::DiagnosticStylesheet;
use crate::{
Db,
files::File,
@@ -111,37 +111,7 @@ impl std::fmt::Display for DisplayDiagnostics<'_> {
ConciseRenderer::new(self.resolver, self.config).render(f, self.diagnostics)?;
}
DiagnosticFormat::Full => {
let stylesheet = if self.config.color {
DiagnosticStylesheet::styled()
} else {
DiagnosticStylesheet::plain()
};
let mut renderer = if self.config.color {
AnnotateRenderer::styled()
} else {
AnnotateRenderer::plain()
}
.cut_indicator("");
renderer = renderer
.error(stylesheet.error)
.warning(stylesheet.warning)
.info(stylesheet.info)
.note(stylesheet.note)
.help(stylesheet.help)
.line_no(stylesheet.line_no)
.emphasis(stylesheet.emphasis)
.none(stylesheet.none);
for diag in self.diagnostics {
let resolved = Resolved::new(self.resolver, diag, self.config);
let renderable = resolved.to_renderable(self.config.context);
for diag in renderable.diagnostics.iter() {
writeln!(f, "{}", renderer.render(diag.to_annotate()))?;
}
writeln!(f)?;
}
FullRenderer::new(self.resolver, self.config).render(f, self.diagnostics)?;
}
DiagnosticFormat::Azure => {
AzureRenderer::new(self.resolver).render(f, self.diagnostics)?;
@@ -242,7 +212,12 @@ impl<'a> ResolvedDiagnostic<'a> {
.annotations
.iter()
.filter_map(|ann| {
let path = ann.span.file.path(resolver);
let path = ann
.span
.file
.relative_path(resolver)
.to_str()
.unwrap_or_else(|| ann.span.file.path(resolver));
let diagnostic_source = ann.span.file.diagnostic_source(resolver);
ResolvedAnnotation::new(path, &diagnostic_source, ann, resolver)
})
@@ -289,7 +264,12 @@ impl<'a> ResolvedDiagnostic<'a> {
.annotations
.iter()
.filter_map(|ann| {
let path = ann.span.file.path(resolver);
let path = ann
.span
.file
.relative_path(resolver)
.to_str()
.unwrap_or_else(|| ann.span.file.path(resolver));
let diagnostic_source = ann.span.file.diagnostic_source(resolver);
ResolvedAnnotation::new(path, &diagnostic_source, ann, resolver)
})
@@ -655,6 +635,22 @@ impl<'r> RenderableSnippet<'r> {
.as_source_code()
.slice(TextRange::new(snippet_start, snippet_end));
// Strip the BOM from the beginning of the snippet, if present. Doing this here saves us the
// trouble of updating the annotation ranges in `replace_unprintable`, and also allows us to
// check that the BOM is at the very beginning of the file, not just the beginning of the
// snippet.
const BOM: char = '\u{feff}';
let bom_len = BOM.text_len();
let (snippet, snippet_start) =
if snippet_start == TextSize::ZERO && snippet.starts_with(BOM) {
(
&snippet[bom_len.to_usize()..],
snippet_start + TextSize::new(bom_len.to_u32()),
)
} else {
(snippet, snippet_start)
};
let annotations = anns
.iter()
.map(|ann| RenderableAnnotation::new(snippet_start, ann))
@@ -719,7 +715,11 @@ impl<'r> RenderableAnnotation<'r> {
/// lifetime parameter here refers to the lifetime of the resolver that
/// created the given `ResolvedAnnotation`.
fn new(snippet_start: TextSize, ann: &'_ ResolvedAnnotation<'r>) -> RenderableAnnotation<'r> {
let range = ann.range - snippet_start;
// This should only ever saturate if a BOM is present _and_ the annotation range points
// before the BOM (i.e. at offset 0). In Ruff this typically results from the use of
// `TextRange::default()` for a diagnostic range instead of a range relative to file
// contents.
let range = ann.range.checked_sub(snippet_start).unwrap_or(ann.range);
RenderableAnnotation {
range,
message: ann.message,
@@ -1000,7 +1000,12 @@ fn replace_unprintable<'r>(
let mut last_end = 0;
let mut result = String::new();
for (index, c) in source.char_indices() {
if let Some(printable) = unprintable_replacement(c) {
// normalize `\r` line endings but don't double `\r\n`
if c == '\r' && !source[index + 1..].starts_with("\n") {
result.push_str(&source[last_end..index]);
result.push('\n');
last_end = index + 1;
} else if let Some(printable) = unprintable_replacement(c) {
result.push_str(&source[last_end..index]);
let len = printable.text_len().to_u32();

View File

@@ -1,7 +1,260 @@
use std::borrow::Cow;
use std::num::NonZeroUsize;
use anstyle::Style;
use similar::{ChangeTag, TextDiff};
use ruff_annotate_snippets::Renderer as AnnotateRenderer;
use ruff_diagnostics::{Applicability, Fix};
use ruff_source_file::OneIndexed;
use ruff_text_size::{Ranged, TextRange, TextSize};
use crate::diagnostic::render::{FileResolver, Resolved};
use crate::diagnostic::stylesheet::{DiagnosticStylesheet, fmt_styled};
use crate::diagnostic::{Diagnostic, DiagnosticSource, DisplayDiagnosticConfig};
pub(super) struct FullRenderer<'a> {
resolver: &'a dyn FileResolver,
config: &'a DisplayDiagnosticConfig,
}
impl<'a> FullRenderer<'a> {
pub(super) fn new(resolver: &'a dyn FileResolver, config: &'a DisplayDiagnosticConfig) -> Self {
Self { resolver, config }
}
pub(super) fn render(
&self,
f: &mut std::fmt::Formatter,
diagnostics: &[Diagnostic],
) -> std::fmt::Result {
let stylesheet = if self.config.color {
DiagnosticStylesheet::styled()
} else {
DiagnosticStylesheet::plain()
};
let mut renderer = if self.config.color {
AnnotateRenderer::styled()
} else {
AnnotateRenderer::plain()
}
.cut_indicator("");
renderer = renderer
.error(stylesheet.error)
.warning(stylesheet.warning)
.info(stylesheet.info)
.note(stylesheet.note)
.help(stylesheet.help)
.line_no(stylesheet.line_no)
.emphasis(stylesheet.emphasis)
.none(stylesheet.none);
for diag in diagnostics {
let resolved = Resolved::new(self.resolver, diag, self.config);
let renderable = resolved.to_renderable(self.config.context);
for diag in renderable.diagnostics.iter() {
writeln!(f, "{}", renderer.render(diag.to_annotate()))?;
}
writeln!(f)?;
if self.config.show_fix_diff {
if let Some(diff) = Diff::from_diagnostic(diag, &stylesheet, self.resolver) {
writeln!(f, "{diff}")?;
}
}
}
Ok(())
}
}
/// Renders a diff that shows the code fixes.
///
/// The implementation isn't fully fledged out and only used by tests. Before using in production, try
/// * Improve layout
/// * Replace tabs with spaces for a consistent experience across terminals
/// * Replace zero-width whitespaces
/// * Print a simpler diff if only a single line has changed
/// * Compute the diff from the `Edit` because diff calculation is expensive.
struct Diff<'a> {
fix: &'a Fix,
diagnostic_source: DiagnosticSource,
stylesheet: &'a DiagnosticStylesheet,
}
impl<'a> Diff<'a> {
fn from_diagnostic(
diagnostic: &'a Diagnostic,
stylesheet: &'a DiagnosticStylesheet,
resolver: &'a dyn FileResolver,
) -> Option<Diff<'a>> {
Some(Diff {
fix: diagnostic.fix()?,
diagnostic_source: diagnostic
.primary_span_ref()?
.file
.diagnostic_source(resolver),
stylesheet,
})
}
}
impl std::fmt::Display for Diff<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let source_code = self.diagnostic_source.as_source_code();
let source_text = source_code.text();
// TODO(dhruvmanila): Add support for Notebook cells once it's user-facing
let mut output = String::with_capacity(source_text.len());
let mut last_end = TextSize::default();
for edit in self.fix.edits() {
output.push_str(source_code.slice(TextRange::new(last_end, edit.start())));
output.push_str(edit.content().unwrap_or_default());
last_end = edit.end();
}
output.push_str(&source_text[usize::from(last_end)..]);
let diff = TextDiff::from_lines(source_text, &output);
let message = match self.fix.applicability() {
// TODO(zanieb): Adjust this messaging once it's user-facing
Applicability::Safe => "Safe fix",
Applicability::Unsafe => "Unsafe fix",
Applicability::DisplayOnly => "Display-only fix",
};
// TODO(brent) `stylesheet.separator` is cyan rather than blue, as we had before. I think
// we're getting rid of this soon anyway, so I didn't think it was worth adding another
// style to the stylesheet temporarily. The color doesn't appear at all in the snapshot
// tests, which is the only place these are currently used.
writeln!(f, " {}", fmt_styled(message, self.stylesheet.separator))?;
let (largest_old, largest_new) = diff
.ops()
.last()
.map(|op| (op.old_range().start, op.new_range().start))
.unwrap_or_default();
let digit_with = OneIndexed::from_zero_indexed(largest_new.max(largest_old)).digits();
for (idx, group) in diff.grouped_ops(3).iter().enumerate() {
if idx > 0 {
writeln!(f, "{:-^1$}", "-", 80)?;
}
for op in group {
for change in diff.iter_inline_changes(op) {
let sign = match change.tag() {
ChangeTag::Delete => "-",
ChangeTag::Insert => "+",
ChangeTag::Equal => " ",
};
let line_style = LineStyle::from(change.tag(), self.stylesheet);
let old_index = change.old_index().map(OneIndexed::from_zero_indexed);
let new_index = change.new_index().map(OneIndexed::from_zero_indexed);
write!(
f,
"{} {} |{}",
Line {
index: old_index,
width: digit_with
},
Line {
index: new_index,
width: digit_with
},
fmt_styled(line_style.apply_to(sign), self.stylesheet.emphasis),
)?;
for (emphasized, value) in change.iter_strings_lossy() {
let value = show_nonprinting(&value);
if emphasized {
write!(
f,
"{}",
fmt_styled(line_style.apply_to(&value), self.stylesheet.underline)
)?;
} else {
write!(f, "{}", line_style.apply_to(&value))?;
}
}
if change.missing_newline() {
writeln!(f)?;
}
}
}
}
Ok(())
}
}
struct LineStyle {
style: Style,
}
impl LineStyle {
fn apply_to(&self, input: &str) -> impl std::fmt::Display {
fmt_styled(input, self.style)
}
fn from(value: ChangeTag, stylesheet: &DiagnosticStylesheet) -> LineStyle {
match value {
ChangeTag::Equal => LineStyle {
style: stylesheet.none,
},
ChangeTag::Delete => LineStyle {
style: stylesheet.deletion,
},
ChangeTag::Insert => LineStyle {
style: stylesheet.insertion,
},
}
}
}
struct Line {
index: Option<OneIndexed>,
width: NonZeroUsize,
}
impl std::fmt::Display for Line {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self.index {
None => {
for _ in 0..self.width.get() {
f.write_str(" ")?;
}
Ok(())
}
Some(idx) => write!(f, "{:<width$}", idx, width = self.width.get()),
}
}
}
fn show_nonprinting(s: &str) -> Cow<'_, str> {
if s.find(['\x07', '\x08', '\x1b', '\x7f']).is_some() {
Cow::Owned(
s.replace('\x07', "")
.replace('\x08', "")
.replace('\x1b', "")
.replace('\x7f', ""),
)
} else {
Cow::Borrowed(s)
}
}
#[cfg(test)]
mod tests {
use ruff_diagnostics::Applicability;
use ruff_text_size::TextRange;
use ruff_text_size::{TextLen, TextRange, TextSize};
use crate::diagnostic::{
Annotation, DiagnosticFormat, Severity,
@@ -186,7 +439,7 @@ print()
/// For example, without the fix, we get diagnostics like this:
///
/// ```
/// error[invalid-character-sub]: Invalid unescaped character SUB, use "\x1A" instead
/// error[invalid-character-sub]: Invalid unescaped character SUB, use "\x1a" instead
/// --> example.py:1:25
/// |
/// 1 | nested_fstrings = f'␈{f'{f'␛'}'}'
@@ -206,13 +459,13 @@ print()
.builder(
"invalid-character-sub",
Severity::Error,
r#"Invalid unescaped character SUB, use "\x1A" instead"#,
r#"Invalid unescaped character SUB, use "\x1a" instead"#,
)
.primary("example.py", "1:24", "1:24", "")
.build();
insta::assert_snapshot!(env.render(&diagnostic), @r#"
error[invalid-character-sub]: Invalid unescaped character SUB, use "\x1A" instead
error[invalid-character-sub]: Invalid unescaped character SUB, use "\x1a" instead
--> example.py:1:25
|
1 | nested_fstrings = f'␈{f'{f'␛'}'}'
@@ -231,13 +484,13 @@ print()
.builder(
"invalid-character-sub",
Severity::Error,
r#"Invalid unescaped character SUB, use "\x1A" instead"#,
r#"Invalid unescaped character SUB, use "\x1a" instead"#,
)
.primary("example.py", "1:1", "1:1", "")
.build();
insta::assert_snapshot!(env.render(&diagnostic), @r#"
error[invalid-character-sub]: Invalid unescaped character SUB, use "\x1A" instead
error[invalid-character-sub]: Invalid unescaped character SUB, use "\x1a" instead
--> example.py:1:2
|
1 | ␈
@@ -400,4 +653,107 @@ print()
help: Remove `print` statement
");
}
/// Carriage return (`\r`) is a valid line-ending in Python, so we should normalize this to a
/// line feed (`\n`) for rendering. Otherwise we report a single long line for this case.
#[test]
fn normalize_carriage_return() {
let mut env = TestEnvironment::new();
env.add(
"example.py",
"# Keep parenthesis around preserved CR\rint(-\r 1)\rint(+\r 1)",
);
env.format(DiagnosticFormat::Full);
let mut diagnostic = env.err().build();
let span = env
.path("example.py")
.with_range(TextRange::at(TextSize::new(39), TextSize::new(0)));
let annotation = Annotation::primary(span);
diagnostic.annotate(annotation);
insta::assert_snapshot!(env.render(&diagnostic), @r"
error[test-diagnostic]: main diagnostic message
--> example.py:2:1
|
1 | # Keep parenthesis around preserved CR
2 | int(-
| ^
3 | 1)
4 | int(+
|
");
}
/// Without stripping the BOM, we report an error in column 2, unlike Ruff.
#[test]
fn strip_bom() {
let mut env = TestEnvironment::new();
env.add("example.py", "\u{feff}import foo");
env.format(DiagnosticFormat::Full);
let mut diagnostic = env.err().build();
let span = env
.path("example.py")
.with_range(TextRange::at(TextSize::new(3), TextSize::new(0)));
let annotation = Annotation::primary(span);
diagnostic.annotate(annotation);
insta::assert_snapshot!(env.render(&diagnostic), @r"
error[test-diagnostic]: main diagnostic message
--> example.py:1:1
|
1 | import foo
| ^
|
");
}
#[test]
fn bom_with_default_range() {
let mut env = TestEnvironment::new();
env.add("example.py", "\u{feff}import foo");
env.format(DiagnosticFormat::Full);
let mut diagnostic = env.err().build();
let span = env.path("example.py").with_range(TextRange::default());
let annotation = Annotation::primary(span);
diagnostic.annotate(annotation);
insta::assert_snapshot!(env.render(&diagnostic), @r"
error[test-diagnostic]: main diagnostic message
--> example.py:1:1
|
1 | import foo
| ^
|
");
}
/// We previously rendered this correctly, but the header was falling back to 1:1 for ranges
/// pointing to the final newline in a file. Like Ruff, we now use the offset of the first
/// character in the nonexistent final line in the header.
#[test]
fn end_of_file() {
let mut env = TestEnvironment::new();
let contents = "unexpected eof\n";
env.add("example.py", contents);
env.format(DiagnosticFormat::Full);
let mut diagnostic = env.err().build();
let span = env
.path("example.py")
.with_range(TextRange::at(contents.text_len(), TextSize::new(0)));
let annotation = Annotation::primary(span);
diagnostic.annotate(annotation);
insta::assert_snapshot!(env.render(&diagnostic), @r"
error[test-diagnostic]: main diagnostic message
--> example.py:2:1
|
1 | unexpected eof
| ^
|
");
}
}

View File

@@ -40,9 +40,12 @@ pub struct DiagnosticStylesheet {
pub(crate) help: Style,
pub(crate) line_no: Style,
pub(crate) emphasis: Style,
pub(crate) underline: Style,
pub(crate) none: Style,
pub(crate) separator: Style,
pub(crate) secondary_code: Style,
pub(crate) insertion: Style,
pub(crate) deletion: Style,
}
impl Default for DiagnosticStylesheet {
@@ -63,9 +66,12 @@ impl DiagnosticStylesheet {
help: AnsiColor::BrightCyan.on_default().effects(Effects::BOLD),
line_no: bright_blue.effects(Effects::BOLD),
emphasis: Style::new().effects(Effects::BOLD),
underline: Style::new().effects(Effects::UNDERLINE),
none: Style::new(),
separator: AnsiColor::Cyan.on_default(),
secondary_code: AnsiColor::Red.on_default().effects(Effects::BOLD),
insertion: AnsiColor::Green.on_default(),
deletion: AnsiColor::Red.on_default(),
}
}
@@ -78,9 +84,12 @@ impl DiagnosticStylesheet {
help: Style::new(),
line_no: Style::new(),
emphasis: Style::new(),
underline: Style::new(),
none: Style::new(),
separator: Style::new(),
secondary_code: Style::new(),
insertion: Style::new(),
deletion: Style::new(),
}
}
}

View File

@@ -9,7 +9,17 @@ use crate::system::file_time_now;
/// * The last modification time of the file.
/// * The hash of the file's content.
/// * The revision as it comes from an external system, for example the LSP.
#[derive(Copy, Clone, Debug, Eq, PartialEq, Default)]
#[derive(
Copy,
Clone,
Debug,
Eq,
PartialEq,
Default,
get_size2::GetSize,
serde::Serialize,
serde::Deserialize,
)]
pub struct FileRevision(u128);
impl FileRevision {

View File

@@ -14,7 +14,7 @@ use crate::diagnostic::{Span, UnifiedFile};
use crate::file_revision::FileRevision;
use crate::files::file_root::FileRoots;
use crate::files::private::FileStatus;
use crate::system::{SystemPath, SystemPathBuf, SystemVirtualPath, SystemVirtualPathBuf};
use crate::system::{FileType, SystemPath, SystemPathBuf, SystemVirtualPath, SystemVirtualPathBuf};
use crate::vendored::{VendoredPath, VendoredPathBuf};
use crate::{Db, FxDashMap, vendored};
@@ -87,11 +87,12 @@ impl Files {
.system_by_path
.entry(absolute.clone())
.or_insert_with(|| {
tracing::trace!("Adding file '{path}'");
let metadata = db.system().path_metadata(path);
tracing::trace!("Adding file '{absolute}'");
let durability = self
.root(db, path)
.root(db, &absolute)
.map_or(Durability::default(), |root| root.durability(db));
let builder = File::builder(FilePath::System(absolute))
@@ -138,6 +139,7 @@ impl Files {
};
tracing::trace!("Adding vendored file `{}`", path);
let file = File::builder(FilePath::Vendored(path.to_path_buf()))
.permissions(Some(0o444))
.revision(metadata.revision())
@@ -199,7 +201,15 @@ impl Files {
let mut roots = self.inner.roots.write().unwrap();
let absolute = SystemPath::absolute(path, db.system().current_directory());
roots.try_add(db, absolute, kind)
let (Ok(root) | Err(root)) = roots.try_add(db, absolute, |absolute| {
FileRoot::builder(absolute, kind, FileRevision::now())
.durability(Durability::HIGH)
.revision_durability(kind.durability())
.new(db)
});
root
}
/// Updates the revision of the root for `path`.
@@ -258,6 +268,51 @@ impl Files {
root.set_revision(db).to(FileRevision::now());
}
}
/// Seed the files with an existing [`File`] instance.
pub fn seed(&self, file: File, db: &dyn Db) {
let seeded = match file.path(db) {
FilePath::System(path) => self
.inner
.system_by_path
.insert(path.clone(), file)
.is_none(),
FilePath::SystemVirtual(path) => self
.inner
.system_virtual_by_path
.insert(path.clone(), VirtualFile(file))
.is_none(),
FilePath::Vendored(path) => self
.inner
.vendored_by_path
.insert(path.clone(), file)
.is_none(),
};
// Recreating a `File` input means the persisted queries depending on that file
// will be invalidated.
assert!(
seeded,
"unexpected `File` input recreated for path `{}`",
file.path(db)
);
}
/// Seed the files with an existing [`FileRoot`] instance.
pub fn seed_root(&self, root: FileRoot, db: &dyn Db) {
let mut roots = self.inner.roots.write().unwrap();
let seeded = roots
.try_add(db, root.path(db).to_path_buf(), |_| root)
.is_ok();
// Recreating a `FileRoot` input means the persisted queries depending on that file
// root will be invalidated.
assert!(
seeded,
"unexpected `FileRoot` input recreated for path `{}`",
root.path(db)
);
}
}
impl fmt::Debug for Files {
@@ -289,7 +344,7 @@ impl std::panic::RefUnwindSafe for Files {}
/// # Ordering
/// Ordering is based on the file's salsa-assigned id and not on its values.
/// The id may change between runs.
#[salsa::input]
#[salsa::input(persist, heap_size=ruff_memory_usage::heap_size)]
#[derive(PartialOrd, Ord)]
pub struct File {
/// The path of the file (immutable).
@@ -413,6 +468,15 @@ impl File {
}
}
/// Loads all existing [`File`]s in the database.
pub fn load_all(db: &dyn Db) -> Vec<File> {
// TODO: Prune deleted paths.
File::ingredient(db)
.entries(db.zalsa())
.map(|entry| entry.as_struct())
.collect()
}
/// Private method providing the implementation for [`Self::sync_path`] and [`Self::sync`] for
/// system paths.
fn sync_system_path(db: &mut dyn Db, path: &SystemPath, file: Option<File>) {
@@ -521,7 +585,17 @@ impl VirtualFile {
// The types in here need to be public because they're salsa ingredients but we
// don't want them to be publicly accessible. That's why we put them into a private module.
mod private {
#[derive(Copy, Clone, Debug, Eq, PartialEq, Default)]
#[derive(
Copy,
Clone,
Debug,
Eq,
PartialEq,
Default,
get_size2::GetSize,
serde::Serialize,
serde::Deserialize,
)]
pub enum FileStatus {
/// The file exists.
#[default]
@@ -535,6 +609,16 @@ mod private {
}
}
impl From<FileType> for FileStatus {
fn from(value: FileType) -> Self {
match value {
FileType::File => FileStatus::Exists,
FileType::Symlink => FileStatus::Exists,
FileType::Directory => FileStatus::IsADirectory,
}
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum FileError {
IsADirectory,

View File

@@ -16,7 +16,7 @@ use crate::system::{SystemPath, SystemPathBuf};
/// The main usage of file roots is to determine a file's durability. But it can also be used
/// to make a salsa query dependent on whether a file in a root has changed without writing any
/// manual invalidation logic.
#[salsa::input(debug)]
#[salsa::input(persist, debug, heap_size=ruff_memory_usage::heap_size)]
pub struct FileRoot {
/// The path of a root is guaranteed to never change.
#[returns(deref)]
@@ -35,9 +35,20 @@ impl FileRoot {
pub fn durability(self, db: &dyn Db) -> salsa::Durability {
self.kind_at_time_of_creation(db).durability()
}
/// Loads all existing [`FileRoot`]s in the database.
pub fn load_all(db: &dyn Db) -> Vec<FileRoot> {
// TODO: Prune deleted paths.
FileRoot::ingredient(db)
.entries(db.zalsa())
.map(|entry| entry.as_struct())
.collect()
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[derive(
Copy, Clone, Debug, Eq, PartialEq, get_size2::GetSize, serde::Serialize, serde::Deserialize,
)]
pub enum FileRootKind {
/// The root of a project.
Project,
@@ -47,7 +58,7 @@ pub enum FileRootKind {
}
impl FileRootKind {
const fn durability(self) -> Durability {
pub const fn durability(self) -> Durability {
match self {
FileRootKind::Project => Durability::LOW,
FileRootKind::LibrarySearchPath => Durability::HIGH,
@@ -62,34 +73,34 @@ pub(super) struct FileRoots {
}
impl FileRoots {
/// Tries to add a new root for `path` and returns the root.
/// Tries to add a new root for `path`.
///
/// The root isn't added nor is the file root's kind updated if a root for `path` already exists.
///
/// Returns `Ok(root)` if the `FileRoot` was successfully added, and returns `Err(root)` with
/// the previous root if one already existed at that path.
pub(super) fn try_add(
&mut self,
db: &dyn Db,
path: SystemPathBuf,
kind: FileRootKind,
) -> FileRoot {
create_root: impl FnOnce(SystemPathBuf) -> FileRoot,
) -> Result<FileRoot, FileRoot> {
// SAFETY: Guaranteed to succeed because `path` is a UTF-8 that only contains Unicode characters.
let normalized_path = path.as_std_path().to_slash().unwrap();
if let Ok(existing) = self.by_path.at(&normalized_path) {
// Only if it is an exact match
if existing.value.path(db) == &*path {
return *existing.value;
return Err(*existing.value);
}
}
// normalize the path to use `/` separators and escape the '{' and '}' characters,
// which matchit uses for routing parameters
// Normalize the path to use `/` separators and escape the '{' and '}' characters,
// which `matchit` uses for routing parameters.
let mut route = normalized_path.replace('{', "{{").replace('}', "}}");
// Insert a new source root
let root = FileRoot::builder(path, kind, FileRevision::now())
.durability(Durability::HIGH)
.revision_durability(kind.durability())
.new(db);
let root = create_root(path);
// Insert a path that matches the root itself
self.by_path.insert(route.clone(), root).unwrap();
@@ -100,7 +111,7 @@ impl FileRoots {
self.by_path.insert(route, root).unwrap();
self.roots.push(root);
root
Ok(root)
}
/// Returns the closest root for `path` or `None` if no root contains `path`.

View File

@@ -11,7 +11,9 @@ use std::fmt::{Display, Formatter};
/// * a file stored on the [host system](crate::system::System).
/// * a virtual file stored on the [host system](crate::system::System).
/// * a vendored file stored in the [vendored file system](crate::vendored::VendoredFileSystem).
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
#[derive(
Clone, Debug, Eq, PartialEq, Hash, get_size2::GetSize, serde::Serialize, serde::Deserialize,
)]
pub enum FilePath {
/// Path to a file on the [host system](crate::system::System).
System(SystemPathBuf),

View File

@@ -1,3 +1,8 @@
#![warn(
clippy::disallowed_methods,
reason = "Prefer System trait methods over std methods"
)]
use crate::files::Files;
use crate::system::System;
use crate::vendored::VendoredFileSystem;
@@ -65,6 +70,10 @@ pub trait Db: salsa::Database {
/// to process work in parallel. For example, to index a directory or checking the files of a project.
/// ty can still spawn more threads for other tasks, e.g. to wait for a Ctrl+C signal or
/// watching the files for changes.
#[expect(
clippy::disallowed_methods,
reason = "We don't have access to System here, but this is also only used by the CLI and the server which always run on a real system."
)]
pub fn max_parallelism() -> NonZeroUsize {
std::env::var(EnvVars::TY_MAX_PARALLELISM)
.or_else(|_| std::env::var(EnvVars::RAYON_NUM_THREADS))

View File

@@ -92,14 +92,14 @@ impl ParsedModule {
self.inner.store(None);
}
/// Returns a pointer for this [`ParsedModule`].
/// Returns the pointer address of this [`ParsedModule`].
///
/// The pointer uniquely identifies the module within the current Salsa revision,
/// regardless of whether particular [`ParsedModuleRef`] instances are garbage collected.
pub fn as_ptr(&self) -> *const () {
pub fn addr(&self) -> usize {
// Note that the outer `Arc` in `inner` is stable across garbage collection, while the inner
// `Arc` within the `ArcSwap` may change.
Arc::as_ptr(&self.inner).cast()
Arc::as_ptr(&self.inner).addr()
}
}
@@ -202,9 +202,13 @@ mod indexed {
/// Returns the node at the given index.
pub fn get_by_index<'ast>(&'ast self, index: NodeIndex) -> AnyRootNodeRef<'ast> {
let index = index
.as_u32()
.expect("attempted to access uninitialized `NodeIndex`");
// Note that this method restores the correct lifetime: the nodes are valid for as
// long as the reference to `IndexedModule` is alive.
self.index[index.as_usize()]
self.index[index as usize]
}
}
@@ -220,7 +224,7 @@ mod indexed {
T: HasNodeIndex + std::fmt::Debug,
AnyRootNodeRef<'a>: From<&'a T>,
{
node.node_index().set(self.index);
node.node_index().set(NodeIndex::from(self.index));
self.nodes.push(AnyRootNodeRef::from(node));
self.index += 1;
}

View File

@@ -148,7 +148,16 @@ impl From<Notebook> for SourceTextKind {
}
}
#[derive(Debug, thiserror::Error, PartialEq, Eq, Clone, get_size2::GetSize)]
#[derive(
Debug,
thiserror::Error,
PartialEq,
Eq,
Clone,
get_size2::GetSize,
serde::Serialize,
serde::Deserialize,
)]
pub enum SourceTextError {
#[error("Failed to read notebook: {0}`")]
FailedToReadNotebook(String),

View File

@@ -46,7 +46,7 @@ pub type Result<T> = std::io::Result<T>;
/// * File watching isn't supported.
///
/// Abstracting the system also enables tests to use a more efficient in-memory file system.
pub trait System: Debug {
pub trait System: Debug + Sync + Send {
/// Reads the metadata of the file or directory at `path`.
///
/// This function will traverse symbolic links to query information about the destination file.
@@ -66,6 +66,9 @@ pub trait System: Debug {
/// See [dunce::canonicalize] for more information.
fn canonicalize_path(&self, path: &SystemPath) -> Result<SystemPathBuf>;
/// Reads the content of the file at `path` into a bytes buffer.
fn read_to_end(&self, path: &SystemPath) -> Result<Vec<u8>>;
/// Reads the content of the file at `path` into a [`String`].
fn read_to_string(&self, path: &SystemPath) -> Result<String>;
@@ -197,6 +200,8 @@ pub trait System: Debug {
fn as_any(&self) -> &dyn std::any::Any;
fn as_any_mut(&mut self) -> &mut dyn std::any::Any;
fn dyn_clone(&self) -> Box<dyn System>;
}
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
@@ -240,7 +245,7 @@ pub trait WritableSystem: System {
fn create_new_file(&self, path: &SystemPath) -> Result<()>;
/// Writes the given content to the file at the given path.
fn write_file(&self, path: &SystemPath, content: &str) -> Result<()>;
fn write_file(&self, path: &SystemPath, content: &[u8]) -> Result<()>;
/// Creates a directory at `path` as well as any intermediate directories.
fn create_directory_all(&self, path: &SystemPath) -> Result<()>;
@@ -276,7 +281,7 @@ pub trait WritableSystem: System {
// ensures that only one thread/process ever attempts to write to it to avoid corrupting
// the cache.
self.create_new_file(&cache_path)?;
self.write_file(&cache_path, &contents)?;
self.write_file(&cache_path, contents.as_bytes())?;
Ok(Some(cache_path))
}

View File

@@ -114,8 +114,8 @@ impl MemoryFileSystem {
matches!(by_path.get(&normalized), Some(Entry::Directory(_)))
}
pub fn read_to_string(&self, path: impl AsRef<SystemPath>) -> Result<String> {
fn read_to_string(fs: &MemoryFileSystem, path: &SystemPath) -> Result<String> {
pub fn read_to_end(&self, path: impl AsRef<SystemPath>) -> Result<Vec<u8>> {
fn read_to_end(fs: &MemoryFileSystem, path: &SystemPath) -> Result<Vec<u8>> {
let by_path = fs.inner.by_path.read().unwrap();
let normalized = fs.normalize_path(path);
@@ -127,13 +127,18 @@ impl MemoryFileSystem {
}
}
read_to_string(self, path.as_ref())
read_to_end(self, path.as_ref())
}
pub(crate) fn read_virtual_path_to_string(
pub fn read_to_string(&self, path: impl AsRef<SystemPath>) -> Result<String> {
self.read_to_end(path)
.and_then(|bytes| String::from_utf8(bytes).map_err(io::Error::other))
}
pub(crate) fn read_virtual_path_to_end(
&self,
path: impl AsRef<SystemVirtualPath>,
) -> Result<String> {
) -> Result<Vec<u8>> {
let virtual_files = self.inner.virtual_files.read().unwrap();
let file = virtual_files
.get(&path.as_ref().to_path_buf())
@@ -142,6 +147,14 @@ impl MemoryFileSystem {
Ok(file.content.clone())
}
pub(crate) fn read_virtual_path_to_string(
&self,
path: impl AsRef<SystemVirtualPath>,
) -> Result<String> {
self.read_virtual_path_to_end(path)
.and_then(|bytes| String::from_utf8(bytes).map_err(io::Error::other))
}
pub fn exists(&self, path: &SystemPath) -> bool {
let by_path = self.inner.by_path.read().unwrap();
let normalized = self.normalize_path(path);
@@ -161,7 +174,7 @@ impl MemoryFileSystem {
match by_path.entry(normalized) {
btree_map::Entry::Vacant(entry) => {
entry.insert(Entry::File(File {
content: String::new(),
content: Vec::new(),
last_modified: file_time_now(),
}));
@@ -177,13 +190,17 @@ impl MemoryFileSystem {
/// Stores a new file in the file system.
///
/// The operation overrides the content for an existing file with the same normalized `path`.
pub fn write_file(&self, path: impl AsRef<SystemPath>, content: impl ToString) -> Result<()> {
pub fn write_file(
&self,
path: impl AsRef<SystemPath>,
content: impl Into<Vec<u8>>,
) -> Result<()> {
let mut by_path = self.inner.by_path.write().unwrap();
let normalized = self.normalize_path(path.as_ref());
let file = get_or_create_file(&mut by_path, &normalized)?;
file.content = content.to_string();
file.content = content.into();
file.last_modified = file_time_now();
Ok(())
@@ -214,7 +231,7 @@ impl MemoryFileSystem {
pub fn write_file_all(
&self,
path: impl AsRef<SystemPath>,
content: impl ToString,
content: impl Into<Vec<u8>>,
) -> Result<()> {
let path = path.as_ref();
@@ -228,19 +245,23 @@ impl MemoryFileSystem {
/// Stores a new virtual file in the file system.
///
/// The operation overrides the content for an existing virtual file with the same `path`.
pub fn write_virtual_file(&self, path: impl AsRef<SystemVirtualPath>, content: impl ToString) {
pub fn write_virtual_file(
&self,
path: impl AsRef<SystemVirtualPath>,
content: impl Into<Vec<u8>>,
) {
let path = path.as_ref();
let mut virtual_files = self.inner.virtual_files.write().unwrap();
match virtual_files.entry(path.to_path_buf()) {
std::collections::hash_map::Entry::Vacant(entry) => {
entry.insert(File {
content: content.to_string(),
content: content.into(),
last_modified: file_time_now(),
});
}
std::collections::hash_map::Entry::Occupied(mut entry) => {
entry.get_mut().content = content.to_string();
entry.get_mut().content = content.into();
}
}
}
@@ -468,7 +489,7 @@ impl Entry {
#[derive(Debug)]
struct File {
content: String,
content: Vec<u8>,
last_modified: FileTime,
}
@@ -533,7 +554,7 @@ fn get_or_create_file<'a>(
let entry = paths.entry(normalized.to_path_buf()).or_insert_with(|| {
Entry::File(File {
content: String::new(),
content: Vec::new(),
last_modified: file_time_now(),
})
});

View File

@@ -1,3 +1,5 @@
#![allow(clippy::disallowed_methods)]
use super::walk_directory::{
self, DirectoryWalker, WalkDirectoryBuilder, WalkDirectoryConfiguration,
WalkDirectoryVisitorBuilder, WalkState,
@@ -91,6 +93,10 @@ impl System for OsSystem {
})
}
fn read_to_end(&self, path: &SystemPath) -> Result<Vec<u8>> {
std::fs::read(path.as_std_path())
}
fn read_to_string(&self, path: &SystemPath) -> Result<String> {
std::fs::read_to_string(path.as_std_path())
}
@@ -255,6 +261,10 @@ impl System for OsSystem {
fn env_var(&self, name: &str) -> std::result::Result<String, std::env::VarError> {
std::env::var(name)
}
fn dyn_clone(&self) -> Box<dyn System> {
Box::new(self.clone())
}
}
impl OsSystem {
@@ -351,7 +361,7 @@ impl WritableSystem for OsSystem {
std::fs::File::create_new(path).map(drop)
}
fn write_file(&self, path: &SystemPath, content: &str) -> Result<()> {
fn write_file(&self, path: &SystemPath, content: &[u8]) -> Result<()> {
std::fs::write(path.as_std_path(), content)
}

View File

@@ -762,7 +762,17 @@ impl SystemVirtualPath {
}
/// An owned, virtual path on [`System`](`super::System`) (akin to [`String`]).
#[derive(Eq, PartialEq, Clone, Hash, PartialOrd, Ord)]
#[derive(
Eq,
PartialEq,
Clone,
Hash,
PartialOrd,
Ord,
get_size2::GetSize,
serde::Serialize,
serde::Deserialize,
)]
pub struct SystemVirtualPathBuf(String);
impl SystemVirtualPathBuf {

View File

@@ -75,6 +75,10 @@ impl System for TestSystem {
self.system().canonicalize_path(path)
}
fn read_to_end(&self, path: &SystemPath) -> Result<Vec<u8>> {
self.system().read_to_end(path)
}
fn read_to_string(&self, path: &SystemPath) -> Result<String> {
self.system().read_to_string(path)
}
@@ -146,6 +150,10 @@ impl System for TestSystem {
fn case_sensitivity(&self) -> CaseSensitivity {
self.system().case_sensitivity()
}
fn dyn_clone(&self) -> Box<dyn System> {
Box::new(self.clone())
}
}
impl Default for TestSystem {
@@ -161,7 +169,7 @@ impl WritableSystem for TestSystem {
self.system().create_new_file(path)
}
fn write_file(&self, path: &SystemPath, content: &str) -> Result<()> {
fn write_file(&self, path: &SystemPath, content: &[u8]) -> Result<()> {
self.system().write_file(path, content)
}
@@ -181,7 +189,9 @@ pub trait DbWithWritableSystem: Db + Sized {
/// Writes the content of the given file and notifies the Db about the change.
fn write_file(&mut self, path: impl AsRef<SystemPath>, content: impl AsRef<str>) -> Result<()> {
let path = path.as_ref();
match self.writable_system().write_file(path, content.as_ref()) {
let content = content.as_ref();
match self.writable_system().write_file(path, content.as_bytes()) {
Ok(()) => {
File::sync_path(self, path);
Ok(())
@@ -194,7 +204,8 @@ pub trait DbWithWritableSystem: Db + Sized {
File::sync_path(self, ancestor);
}
self.writable_system().write_file(path, content.as_ref())?;
self.writable_system()
.write_file(path, content.as_bytes())?;
File::sync_path(self, path);
Ok(())
@@ -239,8 +250,14 @@ pub trait DbWithTestSystem: Db + Sized {
///
/// ## Panics
/// If the db isn't using the [`InMemorySystem`].
fn write_virtual_file(&mut self, path: impl AsRef<SystemVirtualPath>, content: impl ToString) {
fn write_virtual_file(
&mut self,
path: impl AsRef<SystemVirtualPath>,
content: impl Into<Vec<u8>>,
) {
let path = path.as_ref();
let content = content.into();
self.test_system()
.memory_file_system()
.write_virtual_file(path, content);
@@ -318,6 +335,10 @@ impl System for InMemorySystem {
self.memory_fs.canonicalize(path)
}
fn read_to_end(&self, path: &SystemPath) -> Result<Vec<u8>> {
self.memory_fs.read_to_end(path)
}
fn read_to_string(&self, path: &SystemPath) -> Result<String> {
self.memory_fs.read_to_string(path)
}
@@ -394,6 +415,13 @@ impl System for InMemorySystem {
fn case_sensitivity(&self) -> CaseSensitivity {
CaseSensitivity::CaseSensitive
}
fn dyn_clone(&self) -> Box<dyn System> {
Box::new(Self {
user_config_directory: Mutex::new(self.user_config_directory.lock().unwrap().clone()),
memory_fs: self.memory_fs.clone(),
})
}
}
impl WritableSystem for InMemorySystem {
@@ -401,7 +429,7 @@ impl WritableSystem for InMemorySystem {
self.memory_fs.create_new_file(path)
}
fn write_file(&self, path: &SystemPath, content: &str) -> Result<()> {
fn write_file(&self, path: &SystemPath, content: &[u8]) -> Result<()> {
self.memory_fs.write_file(path, content)
}

View File

@@ -88,7 +88,7 @@ impl ToOwned for VendoredPath {
}
#[repr(transparent)]
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
#[derive(Debug, Eq, PartialEq, Clone, Hash, serde::Serialize, serde::Deserialize)]
pub struct VendoredPathBuf(Utf8PathBuf);
impl get_size2::GetSize for VendoredPathBuf {

View File

@@ -14,8 +14,11 @@ license = { workspace = true }
doctest = false
[dependencies]
ruff_text_size = { workspace = true }
ruff_text_size = { workspace = true, features = ["get-size"] }
get-size2 = { workspace = true }
is-macro = { workspace = true }
serde = { workspace = true, optional = true, features = [] }
[features]
serde = ["dep:serde", "ruff_text_size/serde"]

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff_linter"
version = "0.12.8"
version = "0.12.10"
publish = false
authors = { workspace = true }
edition = { workspace = true }
@@ -13,7 +13,6 @@ license = { workspace = true }
[lib]
[dependencies]
ruff_annotate_snippets = { workspace = true }
ruff_cache = { workspace = true }
ruff_db = { workspace = true, features = ["junit", "serde"] }
ruff_diagnostics = { workspace = true, features = ["serde"] }

View File

@@ -13,6 +13,7 @@ from airflow.api_connexion.security import requires_access
from airflow.contrib.aws_athena_hook import AWSAthenaHook
from airflow.datasets import DatasetAliasEvent
from airflow.operators.subdag import SubDagOperator
from airflow.secrets.cache import SecretCache
from airflow.secrets.local_filesystem import LocalFilesystemBackend
from airflow.triggers.external_task import TaskStateTrigger
from airflow.utils import dates
@@ -56,6 +57,9 @@ SubDagOperator()
# get_connection
LocalFilesystemBackend()
# airflow.secrets.cache
SecretCache()
# airflow.triggers.external_task
TaskStateTrigger()

View File

@@ -154,6 +154,11 @@ try:
except Exception as e:
raise ValueError from e
try:
...
except Exception as e:
raise e from ValueError("hello")
try:
pass
@@ -245,3 +250,9 @@ try:
pass
except (Exception, ValueError) as e:
raise e
# `from None` cause
try:
pass
except BaseException as e:
raise e from None

View File

@@ -0,0 +1,43 @@
class C: a = None
{C.a: None for C.a in "abc"}
print(C.a)
x = [None]
{x[0]: None for x[0] in "abc"}
print(x)
class C(list):
def __getitem__(self, index, /):
item = super().__getitem__(index)
if isinstance(index, slice): item = tuple(item)
return item
x = C()
{x[:0]: None for x[:0] in "abc"}
print(x)
class C:
a = None
def func():
{(C.a,): None for (C.a,) in "abc"} # OK
def func():
obj = type('obj', (), {'attr': 1})()
{(obj.attr,): None for (obj.attr,) in "abc"} # OK
def func():
lst = [1, 2, 3]
{(lst[0],): None for (lst[0],) in "abc"} # OK
def func():
lst = [1, 2, 3, 4, 5]
{(lst[1:3],): None for (lst[1:3],) in "abc"} # OK
# C420: side-effecting assignment targets
# These should NOT trigger C420 because they have side-effecting assignment targets
# See https://github.com/astral-sh/ruff/issues/19511

View File

@@ -161,3 +161,12 @@ r"""first
'no need' to escape
"swap" quote style
"use' ugly triple quotes""".split("\n")
# https://github.com/astral-sh/ruff/issues/19845
print("S\x1cP\x1dL\x1eI\x1fT".split())
print("\x1c\x1d\x1e\x1f>".split(maxsplit=0))
print("<\x1c\x1d\x1e\x1f".rsplit(maxsplit=0))
# leading/trailing whitespace should not count towards maxsplit
" a b c d ".split(maxsplit=2) # ["a", "b", "c d "]
" a b c d ".rsplit(maxsplit=2) # [" a b", "c", "d"]

View File

@@ -13,3 +13,11 @@ Path("tmp/python").symlink_to("usr/bin/python", target_is_directory=True) # Ok
fd = os.open(".", os.O_RDONLY)
os.symlink("source.txt", "link.txt", dir_fd=fd) # Ok: dir_fd is not supported by pathlib
os.close(fd)
os.symlink(src="usr/bin/python", dst="tmp/python", unknown=True)
os.symlink("usr/bin/python", dst="tmp/python", target_is_directory=False)
os.symlink(src="usr/bin/python", dst="tmp/python", dir_fd=None)
os.symlink("usr/bin/python", dst="tmp/python", target_is_directory= True )
os.symlink("usr/bin/python", dst="tmp/python", target_is_directory="nonboolean")

View File

@@ -106,4 +106,22 @@ os.replace("src", "dst", src_dir_fd=1)
os.replace("src", "dst", dst_dir_fd=2)
os.getcwd()
os.getcwdb()
os.getcwdb()
os.mkdir(path="directory")
os.mkdir(
# comment 1
"directory",
mode=0o777
)
os.mkdir("directory", mode=0o777, dir_fd=1)
os.makedirs("name", 0o777, exist_ok=False)
os.makedirs("name", 0o777, False)
os.makedirs(name="name", mode=0o777, exist_ok=False)
os.makedirs("name", unknown_kwarg=True)

View File

@@ -0,0 +1,4 @@
"""Hello, world!"""\
\
x = 1; y = 2

View File

@@ -0,0 +1,18 @@
from __future__ import nested_scopes, generators
from __future__ import with_statement, unicode_literals
from __future__ import absolute_import, division
from __future__ import generator_stop
from __future__ import print_function, nested_scopes, generator_stop
print(with_statement)
generators = 1
class Foo():
def boo(self):
print(division)
__all__ = ["print_function", "generator_stop"]

View File

@@ -69,3 +69,10 @@ a7: OptionalTE[typing.NamedTuple] = None
a8: typing_extensions.Optional[typing.NamedTuple] = None
a9: "Optional[NamedTuple]" = None
a10: Optional[NamedTupleTE] = None
# Test for: https://github.com/astral-sh/ruff/issues/19746
# Nested Optional types should be flattened
nested_optional: Optional[Optional[str]] = None
nested_optional_typing: typing.Optional[Optional[int]] = None
triple_nested_optional: Optional[Optional[Optional[str]]] = None

View File

@@ -118,3 +118,10 @@ def func():
return lambda: value
defaultdict(constant_factory("<missing>"))
def func():
defaultdict(default_factory=t"") # OK
def func():
defaultdict(default_factory=t"hello") # OK

View File

@@ -102,3 +102,8 @@ deque("abc") # OK
deque(b"abc") # OK
deque(f"" "a") # OK
deque(f"{x}" "") # OK
# https://github.com/astral-sh/ruff/issues/19951
deque(t"")
deque(t"" t"")
deque(t"{""}") # OK

View File

@@ -51,3 +51,11 @@ dbm.ndbm.open("db", "r", 0o600) # OK
os.fchmod(0, 256) # 0o400
os.fchmod(0, 493) # 0o755
# https://github.com/astral-sh/ruff/issues/19010
os.chmod("foo", 000) # Error
os.chmod("foo", 0000) # Error
os.chmod("foo", 0b0) # Error
os.chmod("foo", 0x0) # Error
os.chmod("foo", 0) # Ok

View File

@@ -1,10 +1,11 @@
use ruff_python_ast::PythonVersion;
use ruff_python_semantic::{Binding, ScopeKind};
use crate::checkers::ast::Checker;
use crate::codes::Rule;
use crate::rules::{
flake8_builtins, flake8_pyi, flake8_type_checking, flake8_unused_arguments, pep8_naming,
pyflakes, pylint, ruff,
pyflakes, pylint, pyupgrade, ruff,
};
/// Run lint rules over all deferred scopes in the [`SemanticModel`].
@@ -45,6 +46,7 @@ pub(crate) fn deferred_scopes(checker: &Checker) {
Rule::UnusedStaticMethodArgument,
Rule::UnusedUnpackedVariable,
Rule::UnusedVariable,
Rule::UnnecessaryFutureImport,
]) {
return;
}
@@ -224,6 +226,11 @@ pub(crate) fn deferred_scopes(checker: &Checker) {
if checker.is_rule_enabled(Rule::UnusedImport) {
pyflakes::rules::unused_import(checker, scope);
}
if checker.is_rule_enabled(Rule::UnnecessaryFutureImport) {
if checker.target_version() >= PythonVersion::PY37 {
pyupgrade::rules::unnecessary_future_import(checker, scope);
}
}
if checker.is_rule_enabled(Rule::ImportPrivateName) {
pylint::rules::import_private_name(checker, scope);

View File

@@ -1039,8 +1039,6 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
flake8_simplify::rules::zip_dict_keys_and_values(checker, call);
}
if checker.any_rule_enabled(&[
Rule::OsMkdir,
Rule::OsMakedirs,
Rule::OsStat,
Rule::OsPathJoin,
Rule::OsPathSplitext,
@@ -1120,6 +1118,15 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
if checker.is_rule_enabled(Rule::OsPathSamefile) {
flake8_use_pathlib::rules::os_path_samefile(checker, call, segments);
}
if checker.is_rule_enabled(Rule::OsMkdir) {
flake8_use_pathlib::rules::os_mkdir(checker, call, segments);
}
if checker.is_rule_enabled(Rule::OsMakedirs) {
flake8_use_pathlib::rules::os_makedirs(checker, call, segments);
}
if checker.is_rule_enabled(Rule::OsSymlink) {
flake8_use_pathlib::rules::os_symlink(checker, call, segments);
}
if checker.is_rule_enabled(Rule::PathConstructorCurrentDirectory) {
flake8_use_pathlib::rules::path_constructor_current_directory(
checker, call, segments,

View File

@@ -728,13 +728,6 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
pylint::rules::non_ascii_module_import(checker, alias);
}
}
if checker.is_rule_enabled(Rule::UnnecessaryFutureImport) {
if checker.target_version() >= PythonVersion::PY37 {
if let Some("__future__") = module {
pyupgrade::rules::unnecessary_future_import(checker, stmt, names);
}
}
}
if checker.is_rule_enabled(Rule::DeprecatedMockImport) {
pyupgrade::rules::deprecated_mock_import(checker, stmt);
}

View File

@@ -28,7 +28,7 @@ use itertools::Itertools;
use log::debug;
use rustc_hash::{FxHashMap, FxHashSet};
use ruff_db::diagnostic::Diagnostic;
use ruff_db::diagnostic::{Annotation, Diagnostic, IntoDiagnosticMessage, Span};
use ruff_diagnostics::{Applicability, Fix, IsolationLevel};
use ruff_notebook::{CellOffsets, NotebookIndex};
use ruff_python_ast::helpers::{collect_import_from_member, is_docstring_stmt, to_module_path};
@@ -3305,6 +3305,17 @@ impl DiagnosticGuard<'_, '_> {
Err(err) => log::debug!("Failed to create fix for {}: {}", self.name(), err),
}
}
/// Add a secondary annotation with the given message and range.
pub(crate) fn secondary_annotation<'a>(
&mut self,
message: impl IntoDiagnosticMessage + 'a,
range: impl Ranged,
) {
let span = Span::from(self.context.source_file.clone()).with_range(range.range());
let ann = Annotation::secondary(span).message(message);
self.diagnostic.as_mut().unwrap().annotate(ann);
}
}
impl std::ops::Deref for DiagnosticGuard<'_, '_> {

View File

@@ -921,8 +921,8 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
// flake8-use-pathlib
(Flake8UsePathlib, "100") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsPathAbspath),
(Flake8UsePathlib, "101") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsChmod),
(Flake8UsePathlib, "102") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsMkdir),
(Flake8UsePathlib, "103") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsMakedirs),
(Flake8UsePathlib, "102") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsMkdir),
(Flake8UsePathlib, "103") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsMakedirs),
(Flake8UsePathlib, "104") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsRename),
(Flake8UsePathlib, "105") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsReplace),
(Flake8UsePathlib, "106") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsRmdir),
@@ -954,7 +954,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Flake8UsePathlib, "207") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::Glob),
(Flake8UsePathlib, "208") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsListdir),
(Flake8UsePathlib, "210") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::InvalidPathlibWithSuffix),
(Flake8UsePathlib, "211") => (RuleGroup::Preview, rules::flake8_use_pathlib::violations::OsSymlink),
(Flake8UsePathlib, "211") => (RuleGroup::Preview, rules::flake8_use_pathlib::rules::OsSymlink),
// flake8-logging-format
(Flake8LoggingFormat, "001") => (RuleGroup::Stable, rules::flake8_logging_format::violations::LoggingStringFormat),

View File

@@ -63,9 +63,9 @@ impl<'a> Insertion<'a> {
return Insertion::inline(" ", location.add(offset).add(TextSize::of(';')), ";");
}
// If the first token after the docstring is a continuation character (i.e. "\"), advance
// an additional row to prevent inserting in the same logical line.
if match_continuation(locator.after(location)).is_some() {
// While the first token after the docstring is a continuation character (i.e. "\"), advance
// additional rows to prevent inserting in the same logical line.
while match_continuation(locator.after(location)).is_some() {
location = locator.full_line_end(location);
}
@@ -379,6 +379,17 @@ mod tests {
Insertion::own_line("", TextSize::from(22), "\n")
);
let contents = r#"
"""Hello, world!"""\
\
"#
.trim_start();
assert_eq!(
insert(contents)?,
Insertion::own_line("", TextSize::from(24), "\n")
);
let contents = r"
x = 1
"

View File

@@ -44,44 +44,15 @@ pub struct LinterResult {
/// Flag indicating that the parsed source code does not contain any
/// [`ParseError`]s
has_valid_syntax: bool,
/// Flag indicating that the parsed source code does not contain any [`ParseError`]s,
/// [`UnsupportedSyntaxError`]s, or [`SemanticSyntaxError`]s.
has_no_syntax_errors: bool,
}
impl LinterResult {
/// Returns `true` if the parsed source code contains any [`ParseError`]s *or*
/// [`UnsupportedSyntaxError`]s.
///
/// See [`LinterResult::has_invalid_syntax`] for a version specific to [`ParseError`]s.
pub fn has_syntax_errors(&self) -> bool {
!self.has_no_syntax_errors()
}
/// Returns `true` if the parsed source code does not contain any [`ParseError`]s *or*
/// [`UnsupportedSyntaxError`]s.
///
/// See [`LinterResult::has_valid_syntax`] for a version specific to [`ParseError`]s.
pub fn has_no_syntax_errors(&self) -> bool {
self.has_valid_syntax() && self.has_no_syntax_errors
}
/// Returns `true` if the parsed source code is valid i.e., it has no [`ParseError`]s.
///
/// Note that this does not include version-related [`UnsupportedSyntaxError`]s.
///
/// See [`LinterResult::has_no_syntax_errors`] for a version that takes these into account.
pub fn has_valid_syntax(&self) -> bool {
self.has_valid_syntax
}
/// Returns `true` if the parsed source code is invalid i.e., it has [`ParseError`]s.
///
/// Note that this does not include version-related [`UnsupportedSyntaxError`]s.
///
/// See [`LinterResult::has_no_syntax_errors`] for a version that takes these into account.
/// Note that this does not include version-related [`UnsupportedSyntaxError`]s or
/// [`SemanticSyntaxError`]s.
pub fn has_invalid_syntax(&self) -> bool {
!self.has_valid_syntax()
!self.has_valid_syntax
}
}
@@ -513,7 +484,6 @@ pub fn lint_only(
LinterResult {
has_valid_syntax: parsed.has_valid_syntax(),
has_no_syntax_errors: !diagnostics.iter().any(Diagnostic::is_invalid_syntax),
diagnostics,
}
}
@@ -670,7 +640,6 @@ pub fn lint_fix<'a>(
result: LinterResult {
diagnostics,
has_valid_syntax,
has_no_syntax_errors,
},
transformed,
fixed,

View File

@@ -1,202 +0,0 @@
use std::fmt::{Display, Formatter};
use std::num::NonZeroUsize;
use colored::{Color, ColoredString, Colorize, Styles};
use similar::{ChangeTag, TextDiff};
use ruff_db::diagnostic::Diagnostic;
use ruff_source_file::{OneIndexed, SourceFile};
use ruff_text_size::{Ranged, TextRange, TextSize};
use crate::text_helpers::ShowNonprinting;
use crate::{Applicability, Fix};
/// Renders a diff that shows the code fixes.
///
/// The implementation isn't fully fledged out and only used by tests. Before using in production, try
/// * Improve layout
/// * Replace tabs with spaces for a consistent experience across terminals
/// * Replace zero-width whitespaces
/// * Print a simpler diff if only a single line has changed
/// * Compute the diff from the [`Edit`] because diff calculation is expensive.
pub(super) struct Diff<'a> {
fix: &'a Fix,
source_code: &'a SourceFile,
}
impl<'a> Diff<'a> {
pub(crate) fn from_message(message: &'a Diagnostic) -> Option<Diff<'a>> {
message.fix().map(|fix| Diff {
source_code: message.expect_ruff_source_file(),
fix,
})
}
}
impl Display for Diff<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
// TODO(dhruvmanila): Add support for Notebook cells once it's user-facing
let mut output = String::with_capacity(self.source_code.source_text().len());
let mut last_end = TextSize::default();
for edit in self.fix.edits() {
output.push_str(
self.source_code
.slice(TextRange::new(last_end, edit.start())),
);
output.push_str(edit.content().unwrap_or_default());
last_end = edit.end();
}
output.push_str(&self.source_code.source_text()[usize::from(last_end)..]);
let diff = TextDiff::from_lines(self.source_code.source_text(), &output);
let message = match self.fix.applicability() {
// TODO(zanieb): Adjust this messaging once it's user-facing
Applicability::Safe => "Safe fix",
Applicability::Unsafe => "Unsafe fix",
Applicability::DisplayOnly => "Display-only fix",
};
writeln!(f, " {}", message.blue())?;
let (largest_old, largest_new) = diff
.ops()
.last()
.map(|op| (op.old_range().start, op.new_range().start))
.unwrap_or_default();
let digit_with =
calculate_print_width(OneIndexed::from_zero_indexed(largest_new.max(largest_old)));
for (idx, group) in diff.grouped_ops(3).iter().enumerate() {
if idx > 0 {
writeln!(f, "{:-^1$}", "-", 80)?;
}
for op in group {
for change in diff.iter_inline_changes(op) {
let sign = match change.tag() {
ChangeTag::Delete => "-",
ChangeTag::Insert => "+",
ChangeTag::Equal => " ",
};
let line_style = LineStyle::from(change.tag());
let old_index = change.old_index().map(OneIndexed::from_zero_indexed);
let new_index = change.new_index().map(OneIndexed::from_zero_indexed);
write!(
f,
"{} {} |{}",
Line {
index: old_index,
width: digit_with
},
Line {
index: new_index,
width: digit_with
},
line_style.apply_to(sign).bold()
)?;
for (emphasized, value) in change.iter_strings_lossy() {
let value = value.show_nonprinting();
if emphasized {
write!(f, "{}", line_style.apply_to(&value).underline().on_black())?;
} else {
write!(f, "{}", line_style.apply_to(&value))?;
}
}
if change.missing_newline() {
writeln!(f)?;
}
}
}
}
Ok(())
}
}
struct LineStyle {
fgcolor: Option<Color>,
style: Option<Styles>,
}
impl LineStyle {
fn apply_to(&self, input: &str) -> ColoredString {
let mut colored = ColoredString::from(input);
if let Some(color) = self.fgcolor {
colored = colored.color(color);
}
if let Some(style) = self.style {
match style {
Styles::Clear => colored.clear(),
Styles::Bold => colored.bold(),
Styles::Dimmed => colored.dimmed(),
Styles::Underline => colored.underline(),
Styles::Reversed => colored.reversed(),
Styles::Italic => colored.italic(),
Styles::Blink => colored.blink(),
Styles::Hidden => colored.hidden(),
Styles::Strikethrough => colored.strikethrough(),
}
} else {
colored
}
}
}
impl From<ChangeTag> for LineStyle {
fn from(value: ChangeTag) -> Self {
match value {
ChangeTag::Equal => LineStyle {
fgcolor: None,
style: Some(Styles::Dimmed),
},
ChangeTag::Delete => LineStyle {
fgcolor: Some(Color::Red),
style: None,
},
ChangeTag::Insert => LineStyle {
fgcolor: Some(Color::Green),
style: None,
},
}
}
}
struct Line {
index: Option<OneIndexed>,
width: NonZeroUsize,
}
impl Display for Line {
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
match self.index {
None => {
for _ in 0..self.width.get() {
f.write_str(" ")?;
}
Ok(())
}
Some(idx) => write!(f, "{:<width$}", idx, width = self.width.get()),
}
}
}
/// Calculate the length of the string representation of `value`
pub(super) fn calculate_print_width(mut value: OneIndexed) -> NonZeroUsize {
const TEN: OneIndexed = OneIndexed::from_zero_indexed(9);
let mut width = OneIndexed::ONE;
while value >= TEN {
value = OneIndexed::new(value.get() / 10).unwrap_or(OneIndexed::MIN);
width = width.checked_add(1).unwrap();
}
width
}

View File

@@ -88,20 +88,14 @@ impl Serialize for SerializedMessages<'_> {
}
fingerprints.insert(message_fingerprint);
let (description, check_name) = if let Some(code) = diagnostic.secondary_code() {
(diagnostic.body().to_string(), code.as_str())
} else {
let description = diagnostic.body();
let description_without_prefix = description
.strip_prefix("SyntaxError: ")
.unwrap_or(description);
(description_without_prefix.to_string(), "syntax-error")
};
let description = diagnostic.body();
let check_name = diagnostic.secondary_code_or_id();
let value = json!({
"check_name": check_name,
"description": description,
// GitLab doesn't display the separate `check_name` field in a Code Quality report,
// so prepend it to the description too.
"description": format!("{check_name}: {description}"),
"severity": "major",
"fingerprint": format!("{:x}", message_fingerprint),
"location": {

View File

@@ -1,3 +1,4 @@
use std::collections::BTreeMap;
use std::fmt::{Display, Formatter};
use std::io::Write;
use std::num::NonZeroUsize;
@@ -6,18 +7,15 @@ use colored::Colorize;
use ruff_db::diagnostic::Diagnostic;
use ruff_notebook::NotebookIndex;
use ruff_source_file::OneIndexed;
use ruff_source_file::{LineColumn, OneIndexed};
use crate::fs::relativize_path;
use crate::message::diff::calculate_print_width;
use crate::message::text::{MessageCodeFrame, RuleCodeAndBody};
use crate::message::{Emitter, EmitterContext, MessageWithLocation, group_diagnostics_by_filename};
use crate::message::{Emitter, EmitterContext};
use crate::settings::types::UnsafeFixes;
#[derive(Default)]
pub struct GroupedEmitter {
show_fix_status: bool,
show_source: bool,
unsafe_fixes: UnsafeFixes,
}
@@ -28,12 +26,6 @@ impl GroupedEmitter {
self
}
#[must_use]
pub fn with_show_source(mut self, show_source: bool) -> Self {
self.show_source = show_source;
self
}
#[must_use]
pub fn with_unsafe_fixes(mut self, unsafe_fixes: UnsafeFixes) -> Self {
self.unsafe_fixes = unsafe_fixes;
@@ -60,8 +52,8 @@ impl Emitter for GroupedEmitter {
max_column_length = max_column_length.max(message.start_location.column);
}
let row_length = calculate_print_width(max_row_length);
let column_length = calculate_print_width(max_column_length);
let row_length = max_row_length.digits();
let column_length = max_column_length.digits();
// Print the filename.
writeln!(writer, "{}:", relativize_path(&*filename).underline())?;
@@ -76,29 +68,53 @@ impl Emitter for GroupedEmitter {
message,
show_fix_status: self.show_fix_status,
unsafe_fixes: self.unsafe_fixes,
show_source: self.show_source,
row_length,
column_length,
}
)?;
}
// Print a blank line between files, unless we're showing the source, in which case
// we'll have already printed a blank line between messages.
if !self.show_source {
writeln!(writer)?;
}
// Print a blank line between files.
writeln!(writer)?;
}
Ok(())
}
}
struct MessageWithLocation<'a> {
message: &'a Diagnostic,
start_location: LineColumn,
}
impl std::ops::Deref for MessageWithLocation<'_> {
type Target = Diagnostic;
fn deref(&self) -> &Self::Target {
self.message
}
}
fn group_diagnostics_by_filename(
diagnostics: &[Diagnostic],
) -> BTreeMap<String, Vec<MessageWithLocation<'_>>> {
let mut grouped_messages = BTreeMap::default();
for diagnostic in diagnostics {
grouped_messages
.entry(diagnostic.expect_ruff_filename())
.or_insert_with(Vec::new)
.push(MessageWithLocation {
message: diagnostic,
start_location: diagnostic.expect_ruff_start_location(),
});
}
grouped_messages
}
struct DisplayGroupedMessage<'a> {
message: MessageWithLocation<'a>,
show_fix_status: bool,
unsafe_fixes: UnsafeFixes,
show_source: bool,
row_length: NonZeroUsize,
column_length: NonZeroUsize,
notebook_index: Option<&'a NotebookIndex>,
@@ -114,8 +130,7 @@ impl Display for DisplayGroupedMessage<'_> {
write!(
f,
" {row_padding}",
row_padding = " "
.repeat(self.row_length.get() - calculate_print_width(start_location.line).get())
row_padding = " ".repeat(self.row_length.get() - start_location.line.digits().get())
)?;
// Check if we're working on a jupyter notebook and translate positions with cell accordingly
@@ -142,9 +157,8 @@ impl Display for DisplayGroupedMessage<'_> {
f,
"{row}{sep}{col}{col_padding} {code_and_body}",
sep = ":".cyan(),
col_padding = " ".repeat(
self.column_length.get() - calculate_print_width(start_location.column).get()
),
col_padding =
" ".repeat(self.column_length.get() - start_location.column.digits().get()),
code_and_body = RuleCodeAndBody {
message,
show_fix_status: self.show_fix_status,
@@ -152,51 +166,50 @@ impl Display for DisplayGroupedMessage<'_> {
},
)?;
if self.show_source {
use std::fmt::Write;
let mut padded = PadAdapter::new(f);
writeln!(
padded,
"{}",
MessageCodeFrame {
message,
notebook_index: self.notebook_index
Ok(())
}
}
pub(super) struct RuleCodeAndBody<'a> {
pub(crate) message: &'a Diagnostic,
pub(crate) show_fix_status: bool,
pub(crate) unsafe_fixes: UnsafeFixes,
}
impl Display for RuleCodeAndBody<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
if self.show_fix_status {
if let Some(fix) = self.message.fix() {
// Do not display an indicator for inapplicable fixes
if fix.applies(self.unsafe_fixes.required_applicability()) {
if let Some(code) = self.message.secondary_code() {
write!(f, "{} ", code.red().bold())?;
}
return write!(
f,
"{fix}{body}",
fix = format_args!("[{}] ", "*".cyan()),
body = self.message.body(),
);
}
)?;
}
Ok(())
}
}
/// Adapter that adds a ' ' at the start of every line without the need to copy the string.
/// Inspired by Rust's `debug_struct()` internal implementation that also uses a `PadAdapter`.
struct PadAdapter<'buf> {
buf: &'buf mut (dyn std::fmt::Write + 'buf),
on_newline: bool,
}
impl<'buf> PadAdapter<'buf> {
fn new(buf: &'buf mut (dyn std::fmt::Write + 'buf)) -> Self {
Self {
buf,
on_newline: true,
}
}
}
impl std::fmt::Write for PadAdapter<'_> {
fn write_str(&mut self, s: &str) -> std::fmt::Result {
for s in s.split_inclusive('\n') {
if self.on_newline {
self.buf.write_str(" ")?;
}
self.on_newline = s.ends_with('\n');
self.buf.write_str(s)?;
}
Ok(())
if let Some(code) = self.message.secondary_code() {
write!(
f,
"{code} {body}",
code = code.red().bold(),
body = self.message.body(),
)
} else {
write!(
f,
"{code}: {body}",
code = self.message.id().as_str().red().bold(),
body = self.message.body(),
)
}
}
}
@@ -226,19 +239,9 @@ mod tests {
assert_snapshot!(content);
}
#[test]
fn show_source() {
let mut emitter = GroupedEmitter::default().with_show_source(true);
let content = capture_emitter_output(&mut emitter, &create_diagnostics());
assert_snapshot!(content);
}
#[test]
fn fix_status() {
let mut emitter = GroupedEmitter::default()
.with_show_fix_status(true)
.with_show_source(true);
let mut emitter = GroupedEmitter::default().with_show_fix_status(true);
let content = capture_emitter_output(&mut emitter, &create_diagnostics());
assert_snapshot!(content);
@@ -248,7 +251,6 @@ mod tests {
fn fix_status_unsafe() {
let mut emitter = GroupedEmitter::default()
.with_show_fix_status(true)
.with_show_source(true)
.with_unsafe_fixes(UnsafeFixes::Enabled);
let content = capture_emitter_output(&mut emitter, &create_diagnostics());

View File

@@ -1,7 +1,5 @@
use std::collections::BTreeMap;
use std::fmt::Display;
use std::io::Write;
use std::ops::Deref;
use rustc_hash::FxHashMap;
@@ -15,7 +13,7 @@ pub use github::GithubEmitter;
pub use gitlab::GitlabEmitter;
pub use grouped::GroupedEmitter;
use ruff_notebook::NotebookIndex;
use ruff_source_file::{LineColumn, SourceFile};
use ruff_source_file::SourceFile;
use ruff_text_size::{Ranged, TextRange, TextSize};
pub use sarif::SarifEmitter;
pub use text::TextEmitter;
@@ -23,7 +21,6 @@ pub use text::TextEmitter;
use crate::Fix;
use crate::registry::Rule;
mod diff;
mod github;
mod gitlab;
mod grouped;
@@ -134,35 +131,6 @@ impl FileResolver for EmitterContext<'_> {
}
}
struct MessageWithLocation<'a> {
message: &'a Diagnostic,
start_location: LineColumn,
}
impl Deref for MessageWithLocation<'_> {
type Target = Diagnostic;
fn deref(&self) -> &Self::Target {
self.message
}
}
fn group_diagnostics_by_filename(
diagnostics: &[Diagnostic],
) -> BTreeMap<String, Vec<MessageWithLocation<'_>>> {
let mut grouped_messages = BTreeMap::default();
for diagnostic in diagnostics {
grouped_messages
.entry(diagnostic.expect_ruff_filename())
.or_insert_with(Vec::new)
.push(MessageWithLocation {
message: diagnostic,
start_location: diagnostic.expect_ruff_start_location(),
});
}
grouped_messages
}
/// Display format for [`Diagnostic`]s.
///
/// The emitter serializes a slice of [`Diagnostic`]s and writes them to a [`Write`].

View File

@@ -5,7 +5,7 @@ expression: redact_fingerprint(&content)
[
{
"check_name": "F401",
"description": "`os` imported but unused",
"description": "F401: `os` imported but unused",
"fingerprint": "<redacted>",
"location": {
"path": "fib.py",
@@ -24,7 +24,7 @@ expression: redact_fingerprint(&content)
},
{
"check_name": "F841",
"description": "Local variable `x` is assigned to but never used",
"description": "F841: Local variable `x` is assigned to but never used",
"fingerprint": "<redacted>",
"location": {
"path": "fib.py",
@@ -43,7 +43,7 @@ expression: redact_fingerprint(&content)
},
{
"check_name": "F821",
"description": "Undefined name `a`",
"description": "F821: Undefined name `a`",
"fingerprint": "<redacted>",
"location": {
"path": "undef.py",

View File

@@ -4,8 +4,8 @@ expression: redact_fingerprint(&content)
---
[
{
"check_name": "syntax-error",
"description": "Expected one or more symbol names after import",
"check_name": "invalid-syntax",
"description": "invalid-syntax: Expected one or more symbol names after import",
"fingerprint": "<redacted>",
"location": {
"path": "syntax_errors.py",
@@ -23,8 +23,8 @@ expression: redact_fingerprint(&content)
"severity": "major"
},
{
"check_name": "syntax-error",
"description": "Expected ')', found newline",
"check_name": "invalid-syntax",
"description": "invalid-syntax: Expected ')', found newline",
"fingerprint": "<redacted>",
"location": {
"path": "syntax_errors.py",

View File

@@ -1,30 +1,10 @@
---
source: crates/ruff_linter/src/message/grouped.rs
expression: content
snapshot_kind: text
---
fib.py:
1:8 F401 `os` imported but unused
|
1 | import os
| ^^ F401
|
= help: Remove unused import: `os`
6:5 F841 Local variable `x` is assigned to but never used
|
4 | def fibonacci(n):
5 | """Compute the nth number in the Fibonacci sequence."""
6 | x = 1
| ^ F841
7 | if n == 0:
8 | return 0
|
= help: Remove assignment to unused variable `x`
undef.py:
1:4 F821 Undefined name `a`
|
1 | if a == 1: pass
| ^ F821
|

View File

@@ -1,30 +1,10 @@
---
source: crates/ruff_linter/src/message/grouped.rs
expression: content
snapshot_kind: text
---
fib.py:
1:8 F401 [*] `os` imported but unused
|
1 | import os
| ^^ F401
|
= help: Remove unused import: `os`
6:5 F841 [*] Local variable `x` is assigned to but never used
|
4 | def fibonacci(n):
5 | """Compute the nth number in the Fibonacci sequence."""
6 | x = 1
| ^ F841
7 | if n == 0:
8 | return 0
|
= help: Remove assignment to unused variable `x`
undef.py:
1:4 F821 Undefined name `a`
|
1 | if a == 1: pass
| ^ F821
|

View File

@@ -1,30 +0,0 @@
---
source: crates/ruff_linter/src/message/grouped.rs
expression: content
snapshot_kind: text
---
fib.py:
1:8 F401 `os` imported but unused
|
1 | import os
| ^^ F401
|
= help: Remove unused import: `os`
6:5 F841 Local variable `x` is assigned to but never used
|
4 | def fibonacci(n):
5 | """Compute the nth number in the Fibonacci sequence."""
6 | x = 1
| ^ F841
7 | if n == 0:
8 | return 0
|
= help: Remove assignment to unused variable `x`
undef.py:
1:4 F821 Undefined name `a`
|
1 | if a == 1: pass
| ^ F821
|

View File

@@ -1,28 +1,30 @@
---
source: crates/ruff_linter/src/message/text.rs
expression: content
snapshot_kind: text
---
fib.py:1:8: F401 `os` imported but unused
F401 `os` imported but unused
--> fib.py:1:8
|
1 | import os
| ^^ F401
| ^^
|
= help: Remove unused import: `os`
help: Remove unused import: `os`
fib.py:6:5: F841 Local variable `x` is assigned to but never used
F841 Local variable `x` is assigned to but never used
--> fib.py:6:5
|
4 | def fibonacci(n):
5 | """Compute the nth number in the Fibonacci sequence."""
6 | x = 1
| ^ F841
| ^
7 | if n == 0:
8 | return 0
|
= help: Remove assignment to unused variable `x`
help: Remove assignment to unused variable `x`
undef.py:1:4: F821 Undefined name `a`
F821 Undefined name `a`
--> undef.py:1:4
|
1 | if a == 1: pass
| ^ F821
| ^
|

View File

@@ -1,28 +1,30 @@
---
source: crates/ruff_linter/src/message/text.rs
expression: content
snapshot_kind: text
---
fib.py:1:8: F401 `os` imported but unused
F401 `os` imported but unused
--> fib.py:1:8
|
1 | import os
| ^^ F401
| ^^
|
= help: Remove unused import: `os`
help: Remove unused import: `os`
fib.py:6:5: F841 Local variable `x` is assigned to but never used
F841 Local variable `x` is assigned to but never used
--> fib.py:6:5
|
4 | def fibonacci(n):
5 | """Compute the nth number in the Fibonacci sequence."""
6 | x = 1
| ^ F841
| ^
7 | if n == 0:
8 | return 0
|
= help: Remove assignment to unused variable `x`
help: Remove assignment to unused variable `x`
undef.py:1:4: F821 Undefined name `a`
F821 Undefined name `a`
--> undef.py:1:4
|
1 | if a == 1: pass
| ^ F821
| ^
|

View File

@@ -1,28 +1,30 @@
---
source: crates/ruff_linter/src/message/text.rs
expression: content
snapshot_kind: text
---
fib.py:1:8: F401 [*] `os` imported but unused
F401 [*] `os` imported but unused
--> fib.py:1:8
|
1 | import os
| ^^ F401
| ^^
|
= help: Remove unused import: `os`
help: Remove unused import: `os`
fib.py:6:5: F841 [*] Local variable `x` is assigned to but never used
F841 [*] Local variable `x` is assigned to but never used
--> fib.py:6:5
|
4 | def fibonacci(n):
5 | """Compute the nth number in the Fibonacci sequence."""
6 | x = 1
| ^ F841
| ^
7 | if n == 0:
8 | return 0
|
= help: Remove assignment to unused variable `x`
help: Remove assignment to unused variable `x`
undef.py:1:4: F821 Undefined name `a`
F821 Undefined name `a`
--> undef.py:1:4
|
1 | if a == 1: pass
| ^ F821
| ^
|

View File

@@ -2,29 +2,32 @@
source: crates/ruff_linter/src/message/text.rs
expression: content
---
notebook.ipynb:cell 1:2:8: F401 [*] `os` imported but unused
F401 [*] `os` imported but unused
--> notebook.ipynb:cell 1:2:8
|
1 | # cell 1
2 | import os
| ^^ F401
| ^^
|
= help: Remove unused import: `os`
help: Remove unused import: `os`
notebook.ipynb:cell 2:2:8: F401 [*] `math` imported but unused
F401 [*] `math` imported but unused
--> notebook.ipynb:cell 2:2:8
|
1 | # cell 2
2 | import math
| ^^^^ F401
| ^^^^
3 |
4 | print('hello world')
|
= help: Remove unused import: `math`
help: Remove unused import: `math`
notebook.ipynb:cell 3:4:5: F841 [*] Local variable `x` is assigned to but never used
F841 [*] Local variable `x` is assigned to but never used
--> notebook.ipynb:cell 3:4:5
|
2 | def foo():
3 | print()
4 | x = 1
| ^ F841
| ^
|
= help: Remove assignment to unused variable `x`
help: Remove assignment to unused variable `x`

View File

@@ -2,16 +2,17 @@
source: crates/ruff_linter/src/message/text.rs
expression: content
---
syntax_errors.py:1:15: invalid-syntax: Expected one or more symbol names after import
invalid-syntax: Expected one or more symbol names after import
--> syntax_errors.py:1:15
|
1 | from os import
| ^
2 |
3 | if call(foo
4 | def bar():
|
syntax_errors.py:3:12: invalid-syntax: Expected ')', found newline
invalid-syntax: Expected ')', found newline
--> syntax_errors.py:3:12
|
1 | from os import
2 |

View File

@@ -1,41 +1,19 @@
use std::borrow::Cow;
use std::fmt::{Display, Formatter};
use std::io::Write;
use bitflags::bitflags;
use colored::Colorize;
use ruff_annotate_snippets::{Level, Renderer, Snippet};
use ruff_db::diagnostic::{
Diagnostic, DiagnosticFormat, DisplayDiagnosticConfig, SecondaryCode, ceil_char_boundary,
Diagnostic, DiagnosticFormat, DisplayDiagnosticConfig, DisplayDiagnostics,
};
use ruff_notebook::NotebookIndex;
use ruff_source_file::OneIndexed;
use ruff_text_size::{TextLen, TextRange, TextSize};
use crate::message::diff::Diff;
use crate::message::{Emitter, EmitterContext};
use crate::settings::types::UnsafeFixes;
bitflags! {
#[derive(Default)]
struct EmitterFlags: u8 {
/// Whether to show the diff of a fix, for diagnostics that have a fix.
const SHOW_FIX_DIFF = 1 << 1;
/// Whether to show the source code of a diagnostic.
const SHOW_SOURCE = 1 << 2;
}
}
pub struct TextEmitter {
flags: EmitterFlags,
config: DisplayDiagnosticConfig,
}
impl Default for TextEmitter {
fn default() -> Self {
Self {
flags: EmitterFlags::default(),
config: DisplayDiagnosticConfig::default()
.format(DiagnosticFormat::Concise)
.hide_severity(true)
@@ -53,13 +31,17 @@ impl TextEmitter {
#[must_use]
pub fn with_show_fix_diff(mut self, show_fix_diff: bool) -> Self {
self.flags.set(EmitterFlags::SHOW_FIX_DIFF, show_fix_diff);
self.config = self.config.show_fix_diff(show_fix_diff);
self
}
#[must_use]
pub fn with_show_source(mut self, show_source: bool) -> Self {
self.flags.set(EmitterFlags::SHOW_SOURCE, show_source);
self.config = self.config.format(if show_source {
DiagnosticFormat::Full
} else {
DiagnosticFormat::Concise
});
self
}
@@ -91,297 +73,16 @@ impl Emitter for TextEmitter {
diagnostics: &[Diagnostic],
context: &EmitterContext,
) -> anyhow::Result<()> {
for message in diagnostics {
write!(writer, "{}", message.display(context, &self.config))?;
let filename = message.expect_ruff_filename();
let notebook_index = context.notebook_index(&filename);
if self.flags.intersects(EmitterFlags::SHOW_SOURCE) {
// The `0..0` range is used to highlight file-level diagnostics.
if message.expect_range() != TextRange::default() {
writeln!(
writer,
"{}",
MessageCodeFrame {
message,
notebook_index
}
)?;
}
}
if self.flags.intersects(EmitterFlags::SHOW_FIX_DIFF) {
if let Some(diff) = Diff::from_message(message) {
writeln!(writer, "{diff}")?;
}
}
}
write!(
writer,
"{}",
DisplayDiagnostics::new(context, &self.config, diagnostics)
)?;
Ok(())
}
}
pub(super) struct RuleCodeAndBody<'a> {
pub(crate) message: &'a Diagnostic,
pub(crate) show_fix_status: bool,
pub(crate) unsafe_fixes: UnsafeFixes,
}
impl Display for RuleCodeAndBody<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
if self.show_fix_status {
if let Some(fix) = self.message.fix() {
// Do not display an indicator for inapplicable fixes
if fix.applies(self.unsafe_fixes.required_applicability()) {
if let Some(code) = self.message.secondary_code() {
write!(f, "{} ", code.red().bold())?;
}
return write!(
f,
"{fix}{body}",
fix = format_args!("[{}] ", "*".cyan()),
body = self.message.body(),
);
}
}
}
if let Some(code) = self.message.secondary_code() {
write!(
f,
"{code} {body}",
code = code.red().bold(),
body = self.message.body(),
)
} else {
write!(
f,
"{code}: {body}",
code = self.message.id().as_str().red().bold(),
body = self.message.body(),
)
}
}
}
pub(super) struct MessageCodeFrame<'a> {
pub(crate) message: &'a Diagnostic,
pub(crate) notebook_index: Option<&'a NotebookIndex>,
}
impl Display for MessageCodeFrame<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let suggestion = self.message.first_help_text();
let footers = if let Some(suggestion) = suggestion {
vec![Level::Help.title(suggestion)]
} else {
Vec::new()
};
let source_file = self.message.expect_ruff_source_file();
let source_code = source_file.to_source_code();
let content_start_index = source_code.line_index(self.message.expect_range().start());
let mut start_index = content_start_index.saturating_sub(2);
// If we're working with a Jupyter Notebook, skip the lines which are
// outside of the cell containing the diagnostic.
if let Some(index) = self.notebook_index {
let content_start_cell = index.cell(content_start_index).unwrap_or(OneIndexed::MIN);
while start_index < content_start_index {
if index.cell(start_index).unwrap_or(OneIndexed::MIN) == content_start_cell {
break;
}
start_index = start_index.saturating_add(1);
}
}
// Trim leading empty lines.
while start_index < content_start_index {
if !source_code.line_text(start_index).trim().is_empty() {
break;
}
start_index = start_index.saturating_add(1);
}
let content_end_index = source_code.line_index(self.message.expect_range().end());
let mut end_index = content_end_index
.saturating_add(2)
.min(OneIndexed::from_zero_indexed(source_code.line_count()));
// If we're working with a Jupyter Notebook, skip the lines which are
// outside of the cell containing the diagnostic.
if let Some(index) = self.notebook_index {
let content_end_cell = index.cell(content_end_index).unwrap_or(OneIndexed::MIN);
while end_index > content_end_index {
if index.cell(end_index).unwrap_or(OneIndexed::MIN) == content_end_cell {
break;
}
end_index = end_index.saturating_sub(1);
}
}
// Trim trailing empty lines.
while end_index > content_end_index {
if !source_code.line_text(end_index).trim().is_empty() {
break;
}
end_index = end_index.saturating_sub(1);
}
let start_offset = source_code.line_start(start_index);
let end_offset = source_code.line_end(end_index);
let source = replace_unprintable(
source_code.slice(TextRange::new(start_offset, end_offset)),
self.message.expect_range() - start_offset,
)
.fix_up_empty_spans_after_line_terminator();
let label = self
.message
.secondary_code()
.map(SecondaryCode::as_str)
.unwrap_or_default();
let line_start = self.notebook_index.map_or_else(
|| start_index.get(),
|notebook_index| {
notebook_index
.cell_row(start_index)
.unwrap_or(OneIndexed::MIN)
.get()
},
);
let span = usize::from(source.annotation_range.start())
..usize::from(source.annotation_range.end());
let annotation = Level::Error.span(span).label(label);
let snippet = Snippet::source(&source.text)
.line_start(line_start)
.annotation(annotation)
.fold(false);
let message = Level::None.title("").snippet(snippet).footers(footers);
let renderer = if !cfg!(test) && colored::control::SHOULD_COLORIZE.should_colorize() {
Renderer::styled()
} else {
Renderer::plain()
}
.cut_indicator("");
let rendered = renderer.render(message);
writeln!(f, "{rendered}")
}
}
/// Given some source code and an annotation range, this routine replaces
/// unprintable characters with printable representations of them.
///
/// The source code returned has an annotation that is updated to reflect
/// changes made to the source code (if any).
///
/// We don't need to normalize whitespace, such as converting tabs to spaces,
/// because `annotate-snippets` handles that internally. Similarly, it's safe to
/// modify the annotation ranges by inserting 3-byte Unicode replacements
/// because `annotate-snippets` will account for their actual width when
/// rendering and displaying the column to the user.
fn replace_unprintable(source: &str, annotation_range: TextRange) -> SourceCode<'_> {
let mut result = String::new();
let mut last_end = 0;
let mut range = annotation_range;
// Updates the range given by the caller whenever a single byte (at
// `index` in `source`) is replaced with `len` bytes.
//
// When the index occurs before the start of the range, the range is
// offset by `len`. When the range occurs after or at the start but before
// the end, then the end of the range only is offset by `len`.
let mut update_range = |index, len| {
if index < usize::from(annotation_range.start()) {
range += TextSize::new(len - 1);
} else if index < usize::from(annotation_range.end()) {
range = range.add_end(TextSize::new(len - 1));
}
};
// If `c` is an unprintable character, then this returns a printable
// representation of it (using a fancier Unicode codepoint).
let unprintable_replacement = |c: char| -> Option<char> {
match c {
'\x07' => Some('␇'),
'\x08' => Some('␈'),
'\x1b' => Some('␛'),
'\x7f' => Some('␡'),
_ => None,
}
};
for (index, c) in source.char_indices() {
if let Some(printable) = unprintable_replacement(c) {
result.push_str(&source[last_end..index]);
result.push(printable);
last_end = index + 1;
let len = printable.text_len().to_u32();
update_range(index, len);
}
}
// No tabs or unprintable chars
if result.is_empty() {
SourceCode {
annotation_range,
text: Cow::Borrowed(source),
}
} else {
result.push_str(&source[last_end..]);
SourceCode {
annotation_range: range,
text: Cow::Owned(result),
}
}
}
struct SourceCode<'a> {
text: Cow<'a, str>,
annotation_range: TextRange,
}
impl<'a> SourceCode<'a> {
/// This attempts to "fix up" the span on `SourceCode` in the case where
/// it's an empty span immediately following a line terminator.
///
/// At present, `annotate-snippets` (both upstream and our vendored copy)
/// will render annotations of such spans to point to the space immediately
/// following the previous line. But ideally, this should point to the space
/// immediately preceding the next line.
///
/// After attempting to fix `annotate-snippets` and giving up after a couple
/// hours, this routine takes a different tact: it adjusts the span to be
/// non-empty and it will cover the first codepoint of the following line.
/// This forces `annotate-snippets` to point to the right place.
///
/// See also: <https://github.com/astral-sh/ruff/issues/15509>
fn fix_up_empty_spans_after_line_terminator(self) -> SourceCode<'a> {
if !self.annotation_range.is_empty()
|| self.annotation_range.start() == TextSize::from(0)
|| self.annotation_range.start() >= self.text.text_len()
{
return self;
}
if self.text.as_bytes()[self.annotation_range.start().to_usize() - 1] != b'\n' {
return self;
}
let start = self.annotation_range.start();
let end = ceil_char_boundary(&self.text, start + TextSize::from(1));
SourceCode {
annotation_range: TextRange::new(start, end),
..self
}
}
}
#[cfg(test)]
mod tests {
use insta::assert_snapshot;

View File

@@ -159,6 +159,21 @@ pub(crate) const fn is_fix_os_getcwd_enabled(settings: &LinterSettings) -> bool
settings.preview.is_enabled()
}
// https://github.com/astral-sh/ruff/pull/19514
pub(crate) const fn is_fix_os_mkdir_enabled(settings: &LinterSettings) -> bool {
settings.preview.is_enabled()
}
// https://github.com/astral-sh/ruff/pull/19514
pub(crate) const fn is_fix_os_makedirs_enabled(settings: &LinterSettings) -> bool {
settings.preview.is_enabled()
}
// https://github.com/astral-sh/ruff/pull/20009
pub(crate) const fn is_fix_os_symlink_enabled(settings: &LinterSettings) -> bool {
settings.preview.is_enabled()
}
// https://github.com/astral-sh/ruff/pull/11436
// https://github.com/astral-sh/ruff/pull/11168
pub(crate) const fn is_dunder_init_fix_unused_import_enabled(settings: &LinterSettings) -> bool {
@@ -230,3 +245,8 @@ pub(crate) const fn is_add_future_annotations_imports_enabled(settings: &LinterS
pub(crate) const fn is_trailing_comma_type_params_enabled(settings: &LinterSettings) -> bool {
settings.preview.is_enabled()
}
// https://github.com/astral-sh/ruff/pull/19851
pub(crate) const fn is_maxsplit_without_separator_fix_enabled(settings: &LinterSettings) -> bool {
settings.preview.is_enabled()
}

View File

@@ -710,6 +710,10 @@ fn check_name(checker: &Checker, expr: &Expr, range: TextRange) {
},
// airflow.secrets
["airflow", "secrets", "cache", "SecretCache"] => Replacement::AutoImport {
module: "airflow.sdk",
name: "SecretCache",
},
["airflow", "secrets", "local_filesystem", "load_connections"] => Replacement::AutoImport {
module: "airflow.secrets.local_filesystem",
name: "load_connections_dict",

View File

@@ -1,29 +1,32 @@
---
source: crates/ruff_linter/src/rules/airflow/mod.rs
---
AIR001.py:11:1: AIR001 Task variable name should match the `task_id`: "my_task"
AIR001 Task variable name should match the `task_id`: "my_task"
--> AIR001.py:11:1
|
10 | my_task = PythonOperator(task_id="my_task", callable=my_callable)
11 | incorrect_name = PythonOperator(task_id="my_task") # AIR001
| ^^^^^^^^^^^^^^ AIR001
| ^^^^^^^^^^^^^^
12 |
13 | my_task = AirbyteTriggerSyncOperator(task_id="my_task", callable=my_callable)
|
AIR001.py:14:1: AIR001 Task variable name should match the `task_id`: "my_task"
AIR001 Task variable name should match the `task_id`: "my_task"
--> AIR001.py:14:1
|
13 | my_task = AirbyteTriggerSyncOperator(task_id="my_task", callable=my_callable)
14 | incorrect_name = AirbyteTriggerSyncOperator(task_id="my_task") # AIR001
| ^^^^^^^^^^^^^^ AIR001
| ^^^^^^^^^^^^^^
15 |
16 | my_task = AppflowFlowRunOperator(task_id="my_task", callable=my_callable)
|
AIR001.py:17:1: AIR001 Task variable name should match the `task_id`: "my_task"
AIR001 Task variable name should match the `task_id`: "my_task"
--> AIR001.py:17:1
|
16 | my_task = AppflowFlowRunOperator(task_id="my_task", callable=my_callable)
17 | incorrect_name = AppflowFlowRunOperator(task_id="my_task") # AIR001
| ^^^^^^^^^^^^^^ AIR001
| ^^^^^^^^^^^^^^
18 |
19 | # Consider only from the `airflow.operators` (or providers operators) module
|

View File

@@ -1,20 +1,22 @@
---
source: crates/ruff_linter/src/rules/airflow/mod.rs
---
AIR002.py:4:1: AIR002 DAG should have an explicit `schedule` argument
AIR002 DAG should have an explicit `schedule` argument
--> AIR002.py:4:1
|
2 | from airflow.timetables.simple import NullTimetable
3 |
4 | DAG(dag_id="class_default_schedule")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ AIR002
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
5 |
6 | DAG(dag_id="class_schedule", schedule="@hourly")
|
AIR002.py:13:2: AIR002 DAG should have an explicit `schedule` argument
AIR002 DAG should have an explicit `schedule` argument
--> AIR002.py:13:2
|
13 | @dag()
| ^^^^^ AIR002
| ^^^^^
14 | def decorator_default_schedule():
15 | pass
|

View File

@@ -1,46 +1,50 @@
---
source: crates/ruff_linter/src/rules/airflow/mod.rs
---
AIR301_airflow_plugin.py:7:5: AIR301 `operators` is removed in Airflow 3.0
AIR301 `operators` is removed in Airflow 3.0
--> AIR301_airflow_plugin.py:7:5
|
5 | name = "test_plugin"
6 | # --- Invalid extensions start
7 | operators = [PluginOperator]
| ^^^^^^^^^ AIR301
| ^^^^^^^^^
8 | sensors = [PluginSensorOperator]
9 | hooks = [PluginHook]
|
= help: This extension should just be imported as a regular python module.
help: This extension should just be imported as a regular python module.
AIR301_airflow_plugin.py:8:5: AIR301 `sensors` is removed in Airflow 3.0
AIR301 `sensors` is removed in Airflow 3.0
--> AIR301_airflow_plugin.py:8:5
|
6 | # --- Invalid extensions start
7 | operators = [PluginOperator]
8 | sensors = [PluginSensorOperator]
| ^^^^^^^ AIR301
| ^^^^^^^
9 | hooks = [PluginHook]
10 | executors = [PluginExecutor]
|
= help: This extension should just be imported as a regular python module.
help: This extension should just be imported as a regular python module.
AIR301_airflow_plugin.py:9:5: AIR301 `hooks` is removed in Airflow 3.0
AIR301 `hooks` is removed in Airflow 3.0
--> AIR301_airflow_plugin.py:9:5
|
7 | operators = [PluginOperator]
8 | sensors = [PluginSensorOperator]
9 | hooks = [PluginHook]
| ^^^^^ AIR301
| ^^^^^
10 | executors = [PluginExecutor]
11 | # --- Invalid extensions end
|
= help: This extension should just be imported as a regular python module.
help: This extension should just be imported as a regular python module.
AIR301_airflow_plugin.py:10:5: AIR301 `executors` is removed in Airflow 3.0
AIR301 `executors` is removed in Airflow 3.0
--> AIR301_airflow_plugin.py:10:5
|
8 | sensors = [PluginSensorOperator]
9 | hooks = [PluginHook]
10 | executors = [PluginExecutor]
| ^^^^^^^^^ AIR301
| ^^^^^^^^^
11 | # --- Invalid extensions end
12 | macros = [plugin_macro]
|
= help: This extension should just be imported as a regular python module.
help: This extension should just be imported as a regular python module.

View File

@@ -1,16 +1,17 @@
---
source: crates/ruff_linter/src/rules/airflow/mod.rs
---
AIR301_args.py:21:39: AIR301 [*] `schedule_interval` is removed in Airflow 3.0
AIR301 [*] `schedule_interval` is removed in Airflow 3.0
--> AIR301_args.py:21:39
|
19 | DAG(dag_id="class_schedule", schedule="@hourly")
20 |
21 | DAG(dag_id="class_schedule_interval", schedule_interval="@hourly")
| ^^^^^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^^^^^
22 |
23 | DAG(dag_id="class_timetable", timetable=NullTimetable())
|
= help: Use `schedule` instead
help: Use `schedule` instead
Safe fix
18 18 |
@@ -22,14 +23,15 @@ AIR301_args.py:21:39: AIR301 [*] `schedule_interval` is removed in Airflow 3.0
23 23 | DAG(dag_id="class_timetable", timetable=NullTimetable())
24 24 |
AIR301_args.py:23:31: AIR301 [*] `timetable` is removed in Airflow 3.0
AIR301 [*] `timetable` is removed in Airflow 3.0
--> AIR301_args.py:23:31
|
21 | DAG(dag_id="class_schedule_interval", schedule_interval="@hourly")
22 |
23 | DAG(dag_id="class_timetable", timetable=NullTimetable())
| ^^^^^^^^^ AIR301
| ^^^^^^^^^
|
= help: Use `schedule` instead
help: Use `schedule` instead
Safe fix
20 20 |
@@ -41,14 +43,15 @@ AIR301_args.py:23:31: AIR301 [*] `timetable` is removed in Airflow 3.0
25 25 |
26 26 | DAG(dag_id="class_fail_stop", fail_stop=True)
AIR301_args.py:26:31: AIR301 [*] `fail_stop` is removed in Airflow 3.0
AIR301 [*] `fail_stop` is removed in Airflow 3.0
--> AIR301_args.py:26:31
|
26 | DAG(dag_id="class_fail_stop", fail_stop=True)
| ^^^^^^^^^ AIR301
| ^^^^^^^^^
27 |
28 | DAG(dag_id="class_default_view", default_view="dag_default_view")
|
= help: Use `fail_fast` instead
help: Use `fail_fast` instead
Safe fix
23 23 | DAG(dag_id="class_timetable", timetable=NullTimetable())
@@ -60,34 +63,37 @@ AIR301_args.py:26:31: AIR301 [*] `fail_stop` is removed in Airflow 3.0
28 28 | DAG(dag_id="class_default_view", default_view="dag_default_view")
29 29 |
AIR301_args.py:28:34: AIR301 `default_view` is removed in Airflow 3.0
AIR301 `default_view` is removed in Airflow 3.0
--> AIR301_args.py:28:34
|
26 | DAG(dag_id="class_fail_stop", fail_stop=True)
27 |
28 | DAG(dag_id="class_default_view", default_view="dag_default_view")
| ^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^
29 |
30 | DAG(dag_id="class_orientation", orientation="BT")
|
AIR301_args.py:30:33: AIR301 `orientation` is removed in Airflow 3.0
AIR301 `orientation` is removed in Airflow 3.0
--> AIR301_args.py:30:33
|
28 | DAG(dag_id="class_default_view", default_view="dag_default_view")
29 |
30 | DAG(dag_id="class_orientation", orientation="BT")
| ^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^
31 |
32 | allow_future_exec_dates_dag = DAG(dag_id="class_allow_future_exec_dates")
|
AIR301_args.py:41:6: AIR301 [*] `schedule_interval` is removed in Airflow 3.0
AIR301 [*] `schedule_interval` is removed in Airflow 3.0
--> AIR301_args.py:41:6
|
41 | @dag(schedule_interval="0 * * * *")
| ^^^^^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^^^^^
42 | def decorator_schedule_interval():
43 | pass
|
= help: Use `schedule` instead
help: Use `schedule` instead
Safe fix
38 38 | pass
@@ -99,14 +105,15 @@ AIR301_args.py:41:6: AIR301 [*] `schedule_interval` is removed in Airflow 3.0
43 43 | pass
44 44 |
AIR301_args.py:46:6: AIR301 [*] `timetable` is removed in Airflow 3.0
AIR301 [*] `timetable` is removed in Airflow 3.0
--> AIR301_args.py:46:6
|
46 | @dag(timetable=NullTimetable())
| ^^^^^^^^^ AIR301
| ^^^^^^^^^
47 | def decorator_timetable():
48 | pass
|
= help: Use `schedule` instead
help: Use `schedule` instead
Safe fix
43 43 | pass
@@ -118,16 +125,17 @@ AIR301_args.py:46:6: AIR301 [*] `timetable` is removed in Airflow 3.0
48 48 | pass
49 49 |
AIR301_args.py:54:62: AIR301 [*] `execution_date` is removed in Airflow 3.0
AIR301 [*] `execution_date` is removed in Airflow 3.0
--> AIR301_args.py:54:62
|
52 | def decorator_deprecated_operator_args():
53 | trigger_dagrun_op = trigger_dagrun.TriggerDagRunOperator(
54 | task_id="trigger_dagrun_op1", trigger_dag_id="test", execution_date="2024-12-04"
| ^^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^^
55 | )
56 | trigger_dagrun_op2 = TriggerDagRunOperator(
|
= help: Use `logical_date` instead
help: Use `logical_date` instead
Safe fix
51 51 | @dag()
@@ -139,15 +147,16 @@ AIR301_args.py:54:62: AIR301 [*] `execution_date` is removed in Airflow 3.0
56 56 | trigger_dagrun_op2 = TriggerDagRunOperator(
57 57 | task_id="trigger_dagrun_op2", trigger_dag_id="test", execution_date="2024-12-04"
AIR301_args.py:57:62: AIR301 [*] `execution_date` is removed in Airflow 3.0
AIR301 [*] `execution_date` is removed in Airflow 3.0
--> AIR301_args.py:57:62
|
55 | )
56 | trigger_dagrun_op2 = TriggerDagRunOperator(
57 | task_id="trigger_dagrun_op2", trigger_dag_id="test", execution_date="2024-12-04"
| ^^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^^
58 | )
|
= help: Use `logical_date` instead
help: Use `logical_date` instead
Safe fix
54 54 | task_id="trigger_dagrun_op1", trigger_dag_id="test", execution_date="2024-12-04"
@@ -159,15 +168,16 @@ AIR301_args.py:57:62: AIR301 [*] `execution_date` is removed in Airflow 3.0
59 59 |
60 60 | branch_dt_op = datetime.BranchDateTimeOperator(
AIR301_args.py:61:33: AIR301 [*] `use_task_execution_day` is removed in Airflow 3.0
AIR301 [*] `use_task_execution_day` is removed in Airflow 3.0
--> AIR301_args.py:61:33
|
60 | branch_dt_op = datetime.BranchDateTimeOperator(
61 | task_id="branch_dt_op", use_task_execution_day=True, task_concurrency=5
| ^^^^^^^^^^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^^^^^^^^^^
62 | )
63 | branch_dt_op2 = BranchDateTimeOperator(
|
= help: Use `use_task_logical_date` instead
help: Use `use_task_logical_date` instead
Safe fix
58 58 | )
@@ -179,15 +189,16 @@ AIR301_args.py:61:33: AIR301 [*] `use_task_execution_day` is removed in Airflow
63 63 | branch_dt_op2 = BranchDateTimeOperator(
64 64 | task_id="branch_dt_op2",
AIR301_args.py:61:62: AIR301 [*] `task_concurrency` is removed in Airflow 3.0
AIR301 [*] `task_concurrency` is removed in Airflow 3.0
--> AIR301_args.py:61:62
|
60 | branch_dt_op = datetime.BranchDateTimeOperator(
61 | task_id="branch_dt_op", use_task_execution_day=True, task_concurrency=5
| ^^^^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^^^^
62 | )
63 | branch_dt_op2 = BranchDateTimeOperator(
|
= help: Use `max_active_tis_per_dag` instead
help: Use `max_active_tis_per_dag` instead
Safe fix
58 58 | )
@@ -199,16 +210,17 @@ AIR301_args.py:61:62: AIR301 [*] `task_concurrency` is removed in Airflow 3.0
63 63 | branch_dt_op2 = BranchDateTimeOperator(
64 64 | task_id="branch_dt_op2",
AIR301_args.py:65:9: AIR301 [*] `use_task_execution_day` is removed in Airflow 3.0
AIR301 [*] `use_task_execution_day` is removed in Airflow 3.0
--> AIR301_args.py:65:9
|
63 | branch_dt_op2 = BranchDateTimeOperator(
64 | task_id="branch_dt_op2",
65 | use_task_execution_day=True,
| ^^^^^^^^^^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^^^^^^^^^^
66 | sla=timedelta(seconds=10),
67 | )
|
= help: Use `use_task_logical_date` instead
help: Use `use_task_logical_date` instead
Safe fix
62 62 | )
@@ -220,15 +232,16 @@ AIR301_args.py:65:9: AIR301 [*] `use_task_execution_day` is removed in Airflow 3
67 67 | )
68 68 |
AIR301_args.py:92:9: AIR301 [*] `use_task_execution_day` is removed in Airflow 3.0
AIR301 [*] `use_task_execution_day` is removed in Airflow 3.0
--> AIR301_args.py:92:9
|
90 | follow_task_ids_if_true=None,
91 | week_day=1,
92 | use_task_execution_day=True,
| ^^^^^^^^^^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^^^^^^^^^^
93 | )
|
= help: Use `use_task_logical_date` instead
help: Use `use_task_logical_date` instead
Safe fix
89 89 | follow_task_ids_if_false=None,
@@ -240,49 +253,54 @@ AIR301_args.py:92:9: AIR301 [*] `use_task_execution_day` is removed in Airflow 3
94 94 |
95 95 | trigger_dagrun_op >> trigger_dagrun_op2
AIR301_args.py:102:15: AIR301 `filename_template` is removed in Airflow 3.0
AIR301 `filename_template` is removed in Airflow 3.0
--> AIR301_args.py:102:15
|
101 | # deprecated filename_template argument in FileTaskHandler
102 | S3TaskHandler(filename_template="/tmp/test")
| ^^^^^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^^^^^
103 | HdfsTaskHandler(filename_template="/tmp/test")
104 | ElasticsearchTaskHandler(filename_template="/tmp/test")
|
AIR301_args.py:103:17: AIR301 `filename_template` is removed in Airflow 3.0
AIR301 `filename_template` is removed in Airflow 3.0
--> AIR301_args.py:103:17
|
101 | # deprecated filename_template argument in FileTaskHandler
102 | S3TaskHandler(filename_template="/tmp/test")
103 | HdfsTaskHandler(filename_template="/tmp/test")
| ^^^^^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^^^^^
104 | ElasticsearchTaskHandler(filename_template="/tmp/test")
105 | GCSTaskHandler(filename_template="/tmp/test")
|
AIR301_args.py:104:26: AIR301 `filename_template` is removed in Airflow 3.0
AIR301 `filename_template` is removed in Airflow 3.0
--> AIR301_args.py:104:26
|
102 | S3TaskHandler(filename_template="/tmp/test")
103 | HdfsTaskHandler(filename_template="/tmp/test")
104 | ElasticsearchTaskHandler(filename_template="/tmp/test")
| ^^^^^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^^^^^
105 | GCSTaskHandler(filename_template="/tmp/test")
|
AIR301_args.py:105:16: AIR301 `filename_template` is removed in Airflow 3.0
AIR301 `filename_template` is removed in Airflow 3.0
--> AIR301_args.py:105:16
|
103 | HdfsTaskHandler(filename_template="/tmp/test")
104 | ElasticsearchTaskHandler(filename_template="/tmp/test")
105 | GCSTaskHandler(filename_template="/tmp/test")
| ^^^^^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^^^^^
106 |
107 | FabAuthManager(None)
|
AIR301_args.py:107:15: AIR301 `appbuilder` is removed in Airflow 3.0
AIR301 `appbuilder` is removed in Airflow 3.0
--> AIR301_args.py:107:15
|
105 | GCSTaskHandler(filename_template="/tmp/test")
106 |
107 | FabAuthManager(None)
| ^^^^^^ AIR301
| ^^^^^^
|
= help: The constructor takes no parameter now
help: The constructor takes no parameter now

View File

@@ -1,15 +1,16 @@
---
source: crates/ruff_linter/src/rules/airflow/mod.rs
---
AIR301_class_attribute.py:25:19: AIR301 [*] `iter_datasets` is removed in Airflow 3.0
AIR301 [*] `iter_datasets` is removed in Airflow 3.0
--> AIR301_class_attribute.py:25:19
|
23 | # airflow.Dataset
24 | dataset_from_root = DatasetFromRoot()
25 | dataset_from_root.iter_datasets()
| ^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^
26 | dataset_from_root.iter_dataset_aliases()
|
= help: Use `iter_assets` instead
help: Use `iter_assets` instead
Safe fix
22 22 |
@@ -21,16 +22,17 @@ AIR301_class_attribute.py:25:19: AIR301 [*] `iter_datasets` is removed in Airflo
27 27 |
28 28 | # airflow.datasets
AIR301_class_attribute.py:26:19: AIR301 [*] `iter_dataset_aliases` is removed in Airflow 3.0
AIR301 [*] `iter_dataset_aliases` is removed in Airflow 3.0
--> AIR301_class_attribute.py:26:19
|
24 | dataset_from_root = DatasetFromRoot()
25 | dataset_from_root.iter_datasets()
26 | dataset_from_root.iter_dataset_aliases()
| ^^^^^^^^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^^^^^^^^
27 |
28 | # airflow.datasets
|
= help: Use `iter_asset_aliases` instead
help: Use `iter_asset_aliases` instead
Safe fix
23 23 | # airflow.Dataset
@@ -42,15 +44,16 @@ AIR301_class_attribute.py:26:19: AIR301 [*] `iter_dataset_aliases` is removed in
28 28 | # airflow.datasets
29 29 | dataset_to_test_method_call = Dataset()
AIR301_class_attribute.py:30:29: AIR301 [*] `iter_datasets` is removed in Airflow 3.0
AIR301 [*] `iter_datasets` is removed in Airflow 3.0
--> AIR301_class_attribute.py:30:29
|
28 | # airflow.datasets
29 | dataset_to_test_method_call = Dataset()
30 | dataset_to_test_method_call.iter_datasets()
| ^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^
31 | dataset_to_test_method_call.iter_dataset_aliases()
|
= help: Use `iter_assets` instead
help: Use `iter_assets` instead
Safe fix
27 27 |
@@ -62,16 +65,17 @@ AIR301_class_attribute.py:30:29: AIR301 [*] `iter_datasets` is removed in Airflo
32 32 |
33 33 | alias_to_test_method_call = DatasetAlias()
AIR301_class_attribute.py:31:29: AIR301 [*] `iter_dataset_aliases` is removed in Airflow 3.0
AIR301 [*] `iter_dataset_aliases` is removed in Airflow 3.0
--> AIR301_class_attribute.py:31:29
|
29 | dataset_to_test_method_call = Dataset()
30 | dataset_to_test_method_call.iter_datasets()
31 | dataset_to_test_method_call.iter_dataset_aliases()
| ^^^^^^^^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^^^^^^^^
32 |
33 | alias_to_test_method_call = DatasetAlias()
|
= help: Use `iter_asset_aliases` instead
help: Use `iter_asset_aliases` instead
Safe fix
28 28 | # airflow.datasets
@@ -83,14 +87,15 @@ AIR301_class_attribute.py:31:29: AIR301 [*] `iter_dataset_aliases` is removed in
33 33 | alias_to_test_method_call = DatasetAlias()
34 34 | alias_to_test_method_call.iter_datasets()
AIR301_class_attribute.py:34:27: AIR301 [*] `iter_datasets` is removed in Airflow 3.0
AIR301 [*] `iter_datasets` is removed in Airflow 3.0
--> AIR301_class_attribute.py:34:27
|
33 | alias_to_test_method_call = DatasetAlias()
34 | alias_to_test_method_call.iter_datasets()
| ^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^
35 | alias_to_test_method_call.iter_dataset_aliases()
|
= help: Use `iter_assets` instead
help: Use `iter_assets` instead
Safe fix
31 31 | dataset_to_test_method_call.iter_dataset_aliases()
@@ -102,16 +107,17 @@ AIR301_class_attribute.py:34:27: AIR301 [*] `iter_datasets` is removed in Airflo
36 36 |
37 37 | any_to_test_method_call = DatasetAny()
AIR301_class_attribute.py:35:27: AIR301 [*] `iter_dataset_aliases` is removed in Airflow 3.0
AIR301 [*] `iter_dataset_aliases` is removed in Airflow 3.0
--> AIR301_class_attribute.py:35:27
|
33 | alias_to_test_method_call = DatasetAlias()
34 | alias_to_test_method_call.iter_datasets()
35 | alias_to_test_method_call.iter_dataset_aliases()
| ^^^^^^^^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^^^^^^^^
36 |
37 | any_to_test_method_call = DatasetAny()
|
= help: Use `iter_asset_aliases` instead
help: Use `iter_asset_aliases` instead
Safe fix
32 32 |
@@ -123,14 +129,15 @@ AIR301_class_attribute.py:35:27: AIR301 [*] `iter_dataset_aliases` is removed in
37 37 | any_to_test_method_call = DatasetAny()
38 38 | any_to_test_method_call.iter_datasets()
AIR301_class_attribute.py:38:25: AIR301 [*] `iter_datasets` is removed in Airflow 3.0
AIR301 [*] `iter_datasets` is removed in Airflow 3.0
--> AIR301_class_attribute.py:38:25
|
37 | any_to_test_method_call = DatasetAny()
38 | any_to_test_method_call.iter_datasets()
| ^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^
39 | any_to_test_method_call.iter_dataset_aliases()
|
= help: Use `iter_assets` instead
help: Use `iter_assets` instead
Safe fix
35 35 | alias_to_test_method_call.iter_dataset_aliases()
@@ -142,16 +149,17 @@ AIR301_class_attribute.py:38:25: AIR301 [*] `iter_datasets` is removed in Airflo
40 40 |
41 41 | # airflow.datasets.manager
AIR301_class_attribute.py:39:25: AIR301 [*] `iter_dataset_aliases` is removed in Airflow 3.0
AIR301 [*] `iter_dataset_aliases` is removed in Airflow 3.0
--> AIR301_class_attribute.py:39:25
|
37 | any_to_test_method_call = DatasetAny()
38 | any_to_test_method_call.iter_datasets()
39 | any_to_test_method_call.iter_dataset_aliases()
| ^^^^^^^^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^^^^^^^^
40 |
41 | # airflow.datasets.manager
|
= help: Use `iter_asset_aliases` instead
help: Use `iter_asset_aliases` instead
Safe fix
36 36 |
@@ -163,15 +171,16 @@ AIR301_class_attribute.py:39:25: AIR301 [*] `iter_dataset_aliases` is removed in
41 41 | # airflow.datasets.manager
42 42 | dm = DatasetManager()
AIR301_class_attribute.py:42:6: AIR301 [*] `airflow.datasets.manager.DatasetManager` is removed in Airflow 3.0
AIR301 [*] `airflow.datasets.manager.DatasetManager` is removed in Airflow 3.0
--> AIR301_class_attribute.py:42:6
|
41 | # airflow.datasets.manager
42 | dm = DatasetManager()
| ^^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^^
43 | dm.register_dataset_change()
44 | dm.create_datasets()
|
= help: Use `AssetManager` from `airflow.assets.manager` instead.
help: Use `AssetManager` from `airflow.assets.manager` instead.
Safe fix
19 19 | from airflow.providers_manager import ProvidersManager
@@ -191,16 +200,17 @@ AIR301_class_attribute.py:42:6: AIR301 [*] `airflow.datasets.manager.DatasetMana
44 45 | dm.create_datasets()
45 46 | dm.notify_dataset_created()
AIR301_class_attribute.py:43:4: AIR301 [*] `register_dataset_change` is removed in Airflow 3.0
AIR301 [*] `register_dataset_change` is removed in Airflow 3.0
--> AIR301_class_attribute.py:43:4
|
41 | # airflow.datasets.manager
42 | dm = DatasetManager()
43 | dm.register_dataset_change()
| ^^^^^^^^^^^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^^^^^^^^^^^
44 | dm.create_datasets()
45 | dm.notify_dataset_created()
|
= help: Use `register_asset_change` instead
help: Use `register_asset_change` instead
Safe fix
40 40 |
@@ -212,16 +222,17 @@ AIR301_class_attribute.py:43:4: AIR301 [*] `register_dataset_change` is removed
45 45 | dm.notify_dataset_created()
46 46 | dm.notify_dataset_changed()
AIR301_class_attribute.py:44:4: AIR301 [*] `create_datasets` is removed in Airflow 3.0
AIR301 [*] `create_datasets` is removed in Airflow 3.0
--> AIR301_class_attribute.py:44:4
|
42 | dm = DatasetManager()
43 | dm.register_dataset_change()
44 | dm.create_datasets()
| ^^^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^^^
45 | dm.notify_dataset_created()
46 | dm.notify_dataset_changed()
|
= help: Use `create_assets` instead
help: Use `create_assets` instead
Safe fix
41 41 | # airflow.datasets.manager
@@ -233,16 +244,17 @@ AIR301_class_attribute.py:44:4: AIR301 [*] `create_datasets` is removed in Airfl
46 46 | dm.notify_dataset_changed()
47 47 | dm.notify_dataset_alias_created()
AIR301_class_attribute.py:45:4: AIR301 [*] `notify_dataset_created` is removed in Airflow 3.0
AIR301 [*] `notify_dataset_created` is removed in Airflow 3.0
--> AIR301_class_attribute.py:45:4
|
43 | dm.register_dataset_change()
44 | dm.create_datasets()
45 | dm.notify_dataset_created()
| ^^^^^^^^^^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^^^^^^^^^^
46 | dm.notify_dataset_changed()
47 | dm.notify_dataset_alias_created()
|
= help: Use `notify_asset_created` instead
help: Use `notify_asset_created` instead
Safe fix
42 42 | dm = DatasetManager()
@@ -254,15 +266,16 @@ AIR301_class_attribute.py:45:4: AIR301 [*] `notify_dataset_created` is removed i
47 47 | dm.notify_dataset_alias_created()
48 48 |
AIR301_class_attribute.py:46:4: AIR301 [*] `notify_dataset_changed` is removed in Airflow 3.0
AIR301 [*] `notify_dataset_changed` is removed in Airflow 3.0
--> AIR301_class_attribute.py:46:4
|
44 | dm.create_datasets()
45 | dm.notify_dataset_created()
46 | dm.notify_dataset_changed()
| ^^^^^^^^^^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^^^^^^^^^^
47 | dm.notify_dataset_alias_created()
|
= help: Use `notify_asset_changed` instead
help: Use `notify_asset_changed` instead
Safe fix
43 43 | dm.register_dataset_change()
@@ -274,16 +287,17 @@ AIR301_class_attribute.py:46:4: AIR301 [*] `notify_dataset_changed` is removed i
48 48 |
49 49 | # airflow.lineage.hook
AIR301_class_attribute.py:47:4: AIR301 [*] `notify_dataset_alias_created` is removed in Airflow 3.0
AIR301 [*] `notify_dataset_alias_created` is removed in Airflow 3.0
--> AIR301_class_attribute.py:47:4
|
45 | dm.notify_dataset_created()
46 | dm.notify_dataset_changed()
47 | dm.notify_dataset_alias_created()
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
48 |
49 | # airflow.lineage.hook
|
= help: Use `notify_asset_alias_created` instead
help: Use `notify_asset_alias_created` instead
Safe fix
44 44 | dm.create_datasets()
@@ -295,14 +309,15 @@ AIR301_class_attribute.py:47:4: AIR301 [*] `notify_dataset_alias_created` is rem
49 49 | # airflow.lineage.hook
50 50 | dl_info = DatasetLineageInfo()
AIR301_class_attribute.py:50:11: AIR301 [*] `airflow.lineage.hook.DatasetLineageInfo` is removed in Airflow 3.0
AIR301 [*] `airflow.lineage.hook.DatasetLineageInfo` is removed in Airflow 3.0
--> AIR301_class_attribute.py:50:11
|
49 | # airflow.lineage.hook
50 | dl_info = DatasetLineageInfo()
| ^^^^^^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^^^^^^
51 | dl_info.dataset
|
= help: Use `AssetLineageInfo` from `airflow.lineage.hook` instead.
help: Use `AssetLineageInfo` from `airflow.lineage.hook` instead.
Safe fix
9 9 | DatasetAny,
@@ -323,16 +338,17 @@ AIR301_class_attribute.py:50:11: AIR301 [*] `airflow.lineage.hook.DatasetLineage
52 52 |
53 53 | hlc = HookLineageCollector()
AIR301_class_attribute.py:51:9: AIR301 [*] `dataset` is removed in Airflow 3.0
AIR301 [*] `dataset` is removed in Airflow 3.0
--> AIR301_class_attribute.py:51:9
|
49 | # airflow.lineage.hook
50 | dl_info = DatasetLineageInfo()
51 | dl_info.dataset
| ^^^^^^^ AIR301
| ^^^^^^^
52 |
53 | hlc = HookLineageCollector()
|
= help: Use `asset` instead
help: Use `asset` instead
Safe fix
48 48 |
@@ -344,15 +360,16 @@ AIR301_class_attribute.py:51:9: AIR301 [*] `dataset` is removed in Airflow 3.0
53 53 | hlc = HookLineageCollector()
54 54 | hlc.create_dataset()
AIR301_class_attribute.py:54:5: AIR301 [*] `create_dataset` is removed in Airflow 3.0
AIR301 [*] `create_dataset` is removed in Airflow 3.0
--> AIR301_class_attribute.py:54:5
|
53 | hlc = HookLineageCollector()
54 | hlc.create_dataset()
| ^^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^^
55 | hlc.add_input_dataset()
56 | hlc.add_output_dataset()
|
= help: Use `create_asset` instead
help: Use `create_asset` instead
Safe fix
51 51 | dl_info.dataset
@@ -364,16 +381,17 @@ AIR301_class_attribute.py:54:5: AIR301 [*] `create_dataset` is removed in Airflo
56 56 | hlc.add_output_dataset()
57 57 | hlc.collected_datasets()
AIR301_class_attribute.py:55:5: AIR301 [*] `add_input_dataset` is removed in Airflow 3.0
AIR301 [*] `add_input_dataset` is removed in Airflow 3.0
--> AIR301_class_attribute.py:55:5
|
53 | hlc = HookLineageCollector()
54 | hlc.create_dataset()
55 | hlc.add_input_dataset()
| ^^^^^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^^^^^
56 | hlc.add_output_dataset()
57 | hlc.collected_datasets()
|
= help: Use `add_input_asset` instead
help: Use `add_input_asset` instead
Safe fix
52 52 |
@@ -385,15 +403,16 @@ AIR301_class_attribute.py:55:5: AIR301 [*] `add_input_dataset` is removed in Air
57 57 | hlc.collected_datasets()
58 58 |
AIR301_class_attribute.py:56:5: AIR301 [*] `add_output_dataset` is removed in Airflow 3.0
AIR301 [*] `add_output_dataset` is removed in Airflow 3.0
--> AIR301_class_attribute.py:56:5
|
54 | hlc.create_dataset()
55 | hlc.add_input_dataset()
56 | hlc.add_output_dataset()
| ^^^^^^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^^^^^^
57 | hlc.collected_datasets()
|
= help: Use `add_output_asset` instead
help: Use `add_output_asset` instead
Safe fix
53 53 | hlc = HookLineageCollector()
@@ -405,16 +424,17 @@ AIR301_class_attribute.py:56:5: AIR301 [*] `add_output_dataset` is removed in Ai
58 58 |
59 59 | # airflow.providers.amazon.auth_manager.aws_auth_manager
AIR301_class_attribute.py:57:5: AIR301 [*] `collected_datasets` is removed in Airflow 3.0
AIR301 [*] `collected_datasets` is removed in Airflow 3.0
--> AIR301_class_attribute.py:57:5
|
55 | hlc.add_input_dataset()
56 | hlc.add_output_dataset()
57 | hlc.collected_datasets()
| ^^^^^^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^^^^^^
58 |
59 | # airflow.providers.amazon.auth_manager.aws_auth_manager
|
= help: Use `collected_assets` instead
help: Use `collected_assets` instead
Safe fix
54 54 | hlc.create_dataset()
@@ -426,16 +446,17 @@ AIR301_class_attribute.py:57:5: AIR301 [*] `collected_datasets` is removed in Ai
59 59 | # airflow.providers.amazon.auth_manager.aws_auth_manager
60 60 | aam = AwsAuthManager()
AIR301_class_attribute.py:61:5: AIR301 [*] `is_authorized_dataset` is removed in Airflow 3.0
AIR301 [*] `is_authorized_dataset` is removed in Airflow 3.0
--> AIR301_class_attribute.py:61:5
|
59 | # airflow.providers.amazon.auth_manager.aws_auth_manager
60 | aam = AwsAuthManager()
61 | aam.is_authorized_dataset()
| ^^^^^^^^^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^^^^^^^^^
62 |
63 | # airflow.providers.apache.beam.hooks
|
= help: Use `is_authorized_asset` instead
help: Use `is_authorized_asset` instead
Safe fix
58 58 |
@@ -447,15 +468,16 @@ AIR301_class_attribute.py:61:5: AIR301 [*] `is_authorized_dataset` is removed in
63 63 | # airflow.providers.apache.beam.hooks
64 64 | # check get_conn_uri is caught if the class inherits from an airflow hook
AIR301_class_attribute.py:73:13: AIR301 [*] `get_conn_uri` is removed in Airflow 3.0
AIR301 [*] `get_conn_uri` is removed in Airflow 3.0
--> AIR301_class_attribute.py:73:13
|
71 | # airflow.providers.google.cloud.secrets.secret_manager
72 | csm_backend = CloudSecretManagerBackend()
73 | csm_backend.get_conn_uri()
| ^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^
74 | csm_backend.get_connections()
|
= help: Use `get_conn_value` instead
help: Use `get_conn_value` instead
Safe fix
70 70 |
@@ -467,16 +489,17 @@ AIR301_class_attribute.py:73:13: AIR301 [*] `get_conn_uri` is removed in Airflow
75 75 |
76 76 | # airflow.providers.hashicorp.secrets.vault
AIR301_class_attribute.py:74:13: AIR301 [*] `get_connections` is removed in Airflow 3.0
AIR301 [*] `get_connections` is removed in Airflow 3.0
--> AIR301_class_attribute.py:74:13
|
72 | csm_backend = CloudSecretManagerBackend()
73 | csm_backend.get_conn_uri()
74 | csm_backend.get_connections()
| ^^^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^^^
75 |
76 | # airflow.providers.hashicorp.secrets.vault
|
= help: Use `get_connection` instead
help: Use `get_connection` instead
Safe fix
71 71 | # airflow.providers.google.cloud.secrets.secret_manager
@@ -488,15 +511,16 @@ AIR301_class_attribute.py:74:13: AIR301 [*] `get_connections` is removed in Airf
76 76 | # airflow.providers.hashicorp.secrets.vault
77 77 | vault_backend = VaultBackend()
AIR301_class_attribute.py:78:15: AIR301 [*] `get_conn_uri` is removed in Airflow 3.0
AIR301 [*] `get_conn_uri` is removed in Airflow 3.0
--> AIR301_class_attribute.py:78:15
|
76 | # airflow.providers.hashicorp.secrets.vault
77 | vault_backend = VaultBackend()
78 | vault_backend.get_conn_uri()
| ^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^
79 | vault_backend.get_connections()
|
= help: Use `get_conn_value` instead
help: Use `get_conn_value` instead
Safe fix
75 75 |
@@ -508,16 +532,17 @@ AIR301_class_attribute.py:78:15: AIR301 [*] `get_conn_uri` is removed in Airflow
80 80 |
81 81 | not_an_error = NotAir302SecretError()
AIR301_class_attribute.py:79:15: AIR301 [*] `get_connections` is removed in Airflow 3.0
AIR301 [*] `get_connections` is removed in Airflow 3.0
--> AIR301_class_attribute.py:79:15
|
77 | vault_backend = VaultBackend()
78 | vault_backend.get_conn_uri()
79 | vault_backend.get_connections()
| ^^^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^^^
80 |
81 | not_an_error = NotAir302SecretError()
|
= help: Use `get_connection` instead
help: Use `get_connection` instead
Safe fix
76 76 | # airflow.providers.hashicorp.secrets.vault
@@ -529,16 +554,17 @@ AIR301_class_attribute.py:79:15: AIR301 [*] `get_connections` is removed in Airf
81 81 | not_an_error = NotAir302SecretError()
82 82 | not_an_error.get_conn_uri()
AIR301_class_attribute.py:86:4: AIR301 [*] `initialize_providers_dataset_uri_resources` is removed in Airflow 3.0
AIR301 [*] `initialize_providers_dataset_uri_resources` is removed in Airflow 3.0
--> AIR301_class_attribute.py:86:4
|
84 | # airflow.providers_manager
85 | pm = ProvidersManager()
86 | pm.initialize_providers_dataset_uri_resources()
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
87 | pm.dataset_factories
88 | pm.dataset_uri_handlers
|
= help: Use `initialize_providers_asset_uri_resources` instead
help: Use `initialize_providers_asset_uri_resources` instead
Safe fix
83 83 |
@@ -550,16 +576,17 @@ AIR301_class_attribute.py:86:4: AIR301 [*] `initialize_providers_dataset_uri_res
88 88 | pm.dataset_uri_handlers
89 89 | pm.dataset_to_openlineage_converters
AIR301_class_attribute.py:87:4: AIR301 [*] `dataset_factories` is removed in Airflow 3.0
AIR301 [*] `dataset_factories` is removed in Airflow 3.0
--> AIR301_class_attribute.py:87:4
|
85 | pm = ProvidersManager()
86 | pm.initialize_providers_dataset_uri_resources()
87 | pm.dataset_factories
| ^^^^^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^^^^^
88 | pm.dataset_uri_handlers
89 | pm.dataset_to_openlineage_converters
|
= help: Use `asset_factories` instead
help: Use `asset_factories` instead
Safe fix
84 84 | # airflow.providers_manager
@@ -571,15 +598,16 @@ AIR301_class_attribute.py:87:4: AIR301 [*] `dataset_factories` is removed in Air
89 89 | pm.dataset_to_openlineage_converters
90 90 |
AIR301_class_attribute.py:88:4: AIR301 [*] `dataset_uri_handlers` is removed in Airflow 3.0
AIR301 [*] `dataset_uri_handlers` is removed in Airflow 3.0
--> AIR301_class_attribute.py:88:4
|
86 | pm.initialize_providers_dataset_uri_resources()
87 | pm.dataset_factories
88 | pm.dataset_uri_handlers
| ^^^^^^^^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^^^^^^^^
89 | pm.dataset_to_openlineage_converters
|
= help: Use `asset_uri_handlers` instead
help: Use `asset_uri_handlers` instead
Safe fix
85 85 | pm = ProvidersManager()
@@ -591,16 +619,17 @@ AIR301_class_attribute.py:88:4: AIR301 [*] `dataset_uri_handlers` is removed in
90 90 |
91 91 | # airflow.secrets.base_secrets
AIR301_class_attribute.py:89:4: AIR301 [*] `dataset_to_openlineage_converters` is removed in Airflow 3.0
AIR301 [*] `dataset_to_openlineage_converters` is removed in Airflow 3.0
--> AIR301_class_attribute.py:89:4
|
87 | pm.dataset_factories
88 | pm.dataset_uri_handlers
89 | pm.dataset_to_openlineage_converters
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
90 |
91 | # airflow.secrets.base_secrets
|
= help: Use `asset_to_openlineage_converters` instead
help: Use `asset_to_openlineage_converters` instead
Safe fix
86 86 | pm.initialize_providers_dataset_uri_resources()
@@ -612,15 +641,16 @@ AIR301_class_attribute.py:89:4: AIR301 [*] `dataset_to_openlineage_converters` i
91 91 | # airflow.secrets.base_secrets
92 92 | base_secret_backend = BaseSecretsBackend()
AIR301_class_attribute.py:93:21: AIR301 [*] `get_conn_uri` is removed in Airflow 3.0
AIR301 [*] `get_conn_uri` is removed in Airflow 3.0
--> AIR301_class_attribute.py:93:21
|
91 | # airflow.secrets.base_secrets
92 | base_secret_backend = BaseSecretsBackend()
93 | base_secret_backend.get_conn_uri()
| ^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^
94 | base_secret_backend.get_connections()
|
= help: Use `get_conn_value` instead
help: Use `get_conn_value` instead
Safe fix
90 90 |
@@ -632,16 +662,17 @@ AIR301_class_attribute.py:93:21: AIR301 [*] `get_conn_uri` is removed in Airflow
95 95 |
96 96 | # airflow.secrets.local_filesystem
AIR301_class_attribute.py:94:21: AIR301 [*] `get_connections` is removed in Airflow 3.0
AIR301 [*] `get_connections` is removed in Airflow 3.0
--> AIR301_class_attribute.py:94:21
|
92 | base_secret_backend = BaseSecretsBackend()
93 | base_secret_backend.get_conn_uri()
94 | base_secret_backend.get_connections()
| ^^^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^^^
95 |
96 | # airflow.secrets.local_filesystem
|
= help: Use `get_connection` instead
help: Use `get_connection` instead
Safe fix
91 91 | # airflow.secrets.base_secrets
@@ -653,14 +684,15 @@ AIR301_class_attribute.py:94:21: AIR301 [*] `get_connections` is removed in Airf
96 96 | # airflow.secrets.local_filesystem
97 97 | lfb = LocalFilesystemBackend()
AIR301_class_attribute.py:98:5: AIR301 [*] `get_connections` is removed in Airflow 3.0
AIR301 [*] `get_connections` is removed in Airflow 3.0
--> AIR301_class_attribute.py:98:5
|
96 | # airflow.secrets.local_filesystem
97 | lfb = LocalFilesystemBackend()
98 | lfb.get_connections()
| ^^^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^^^
|
= help: Use `get_connection` instead
help: Use `get_connection` instead
Safe fix
95 95 |

View File

@@ -1,310 +1,342 @@
---
source: crates/ruff_linter/src/rules/airflow/mod.rs
---
AIR301_context.py:22:41: AIR301 `conf` is removed in Airflow 3.0
AIR301 `conf` is removed in Airflow 3.0
--> AIR301_context.py:22:41
|
20 | @task
21 | def access_invalid_key_task_out_of_dag(**context):
22 | print("access invalid key", context["conf"])
| ^^^^^^ AIR301
| ^^^^^^
23 | print("access invalid key", context.get("conf"))
|
AIR301_context.py:23:45: AIR301 `conf` is removed in Airflow 3.0
AIR301 `conf` is removed in Airflow 3.0
--> AIR301_context.py:23:45
|
21 | def access_invalid_key_task_out_of_dag(**context):
22 | print("access invalid key", context["conf"])
23 | print("access invalid key", context.get("conf"))
| ^^^^^^ AIR301
| ^^^^^^
|
AIR301_context.py:28:5: AIR301 `execution_date` is removed in Airflow 3.0
AIR301 `execution_date` is removed in Airflow 3.0
--> AIR301_context.py:28:5
|
26 | @task
27 | def access_invalid_argument_task_out_of_dag(
28 | execution_date, tomorrow_ds, logical_date, **context
| ^^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^^
29 | ):
30 | print("execution date", execution_date)
|
AIR301_context.py:28:21: AIR301 `tomorrow_ds` is removed in Airflow 3.0
AIR301 `tomorrow_ds` is removed in Airflow 3.0
--> AIR301_context.py:28:21
|
26 | @task
27 | def access_invalid_argument_task_out_of_dag(
28 | execution_date, tomorrow_ds, logical_date, **context
| ^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^
29 | ):
30 | print("execution date", execution_date)
|
AIR301_context.py:31:45: AIR301 `conf` is removed in Airflow 3.0
AIR301 `conf` is removed in Airflow 3.0
--> AIR301_context.py:31:45
|
29 | ):
30 | print("execution date", execution_date)
31 | print("access invalid key", context.get("conf"))
| ^^^^^^ AIR301
| ^^^^^^
|
AIR301_context.py:40:30: AIR301 `execution_date` is removed in Airflow 3.0
AIR301 `execution_date` is removed in Airflow 3.0
--> AIR301_context.py:40:30
|
39 | # Removed usage - should trigger violations
40 | execution_date = context["execution_date"]
| ^^^^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^^^^
41 | next_ds = context["next_ds"]
42 | next_ds_nodash = context["next_ds_nodash"]
|
AIR301_context.py:41:23: AIR301 `next_ds` is removed in Airflow 3.0
AIR301 `next_ds` is removed in Airflow 3.0
--> AIR301_context.py:41:23
|
39 | # Removed usage - should trigger violations
40 | execution_date = context["execution_date"]
41 | next_ds = context["next_ds"]
| ^^^^^^^^^ AIR301
| ^^^^^^^^^
42 | next_ds_nodash = context["next_ds_nodash"]
43 | next_execution_date = context["next_execution_date"]
|
AIR301_context.py:42:30: AIR301 `next_ds_nodash` is removed in Airflow 3.0
AIR301 `next_ds_nodash` is removed in Airflow 3.0
--> AIR301_context.py:42:30
|
40 | execution_date = context["execution_date"]
41 | next_ds = context["next_ds"]
42 | next_ds_nodash = context["next_ds_nodash"]
| ^^^^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^^^^
43 | next_execution_date = context["next_execution_date"]
44 | prev_ds = context["prev_ds"]
|
AIR301_context.py:43:35: AIR301 `next_execution_date` is removed in Airflow 3.0
AIR301 `next_execution_date` is removed in Airflow 3.0
--> AIR301_context.py:43:35
|
41 | next_ds = context["next_ds"]
42 | next_ds_nodash = context["next_ds_nodash"]
43 | next_execution_date = context["next_execution_date"]
| ^^^^^^^^^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^^^^^^^^^
44 | prev_ds = context["prev_ds"]
45 | prev_ds_nodash = context["prev_ds_nodash"]
|
AIR301_context.py:44:23: AIR301 `prev_ds` is removed in Airflow 3.0
AIR301 `prev_ds` is removed in Airflow 3.0
--> AIR301_context.py:44:23
|
42 | next_ds_nodash = context["next_ds_nodash"]
43 | next_execution_date = context["next_execution_date"]
44 | prev_ds = context["prev_ds"]
| ^^^^^^^^^ AIR301
| ^^^^^^^^^
45 | prev_ds_nodash = context["prev_ds_nodash"]
46 | prev_execution_date = context["prev_execution_date"]
|
AIR301_context.py:45:30: AIR301 `prev_ds_nodash` is removed in Airflow 3.0
AIR301 `prev_ds_nodash` is removed in Airflow 3.0
--> AIR301_context.py:45:30
|
43 | next_execution_date = context["next_execution_date"]
44 | prev_ds = context["prev_ds"]
45 | prev_ds_nodash = context["prev_ds_nodash"]
| ^^^^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^^^^
46 | prev_execution_date = context["prev_execution_date"]
47 | prev_execution_date_success = context["prev_execution_date_success"]
|
AIR301_context.py:46:35: AIR301 `prev_execution_date` is removed in Airflow 3.0
AIR301 `prev_execution_date` is removed in Airflow 3.0
--> AIR301_context.py:46:35
|
44 | prev_ds = context["prev_ds"]
45 | prev_ds_nodash = context["prev_ds_nodash"]
46 | prev_execution_date = context["prev_execution_date"]
| ^^^^^^^^^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^^^^^^^^^
47 | prev_execution_date_success = context["prev_execution_date_success"]
48 | tomorrow_ds = context["tomorrow_ds"]
|
AIR301_context.py:47:43: AIR301 `prev_execution_date_success` is removed in Airflow 3.0
AIR301 `prev_execution_date_success` is removed in Airflow 3.0
--> AIR301_context.py:47:43
|
45 | prev_ds_nodash = context["prev_ds_nodash"]
46 | prev_execution_date = context["prev_execution_date"]
47 | prev_execution_date_success = context["prev_execution_date_success"]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
48 | tomorrow_ds = context["tomorrow_ds"]
49 | yesterday_ds = context["yesterday_ds"]
|
AIR301_context.py:48:27: AIR301 `tomorrow_ds` is removed in Airflow 3.0
AIR301 `tomorrow_ds` is removed in Airflow 3.0
--> AIR301_context.py:48:27
|
46 | prev_execution_date = context["prev_execution_date"]
47 | prev_execution_date_success = context["prev_execution_date_success"]
48 | tomorrow_ds = context["tomorrow_ds"]
| ^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^
49 | yesterday_ds = context["yesterday_ds"]
50 | yesterday_ds_nodash = context["yesterday_ds_nodash"]
|
AIR301_context.py:49:28: AIR301 `yesterday_ds` is removed in Airflow 3.0
AIR301 `yesterday_ds` is removed in Airflow 3.0
--> AIR301_context.py:49:28
|
47 | prev_execution_date_success = context["prev_execution_date_success"]
48 | tomorrow_ds = context["tomorrow_ds"]
49 | yesterday_ds = context["yesterday_ds"]
| ^^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^^
50 | yesterday_ds_nodash = context["yesterday_ds_nodash"]
|
AIR301_context.py:50:35: AIR301 `yesterday_ds_nodash` is removed in Airflow 3.0
AIR301 `yesterday_ds_nodash` is removed in Airflow 3.0
--> AIR301_context.py:50:35
|
48 | tomorrow_ds = context["tomorrow_ds"]
49 | yesterday_ds = context["yesterday_ds"]
50 | yesterday_ds_nodash = context["yesterday_ds_nodash"]
| ^^^^^^^^^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^^^^^^^^^
|
AIR301_context.py:56:30: AIR301 `execution_date` is removed in Airflow 3.0
AIR301 `execution_date` is removed in Airflow 3.0
--> AIR301_context.py:56:30
|
54 | def print_config_with_get_current_context():
55 | context = get_current_context()
56 | execution_date = context["execution_date"]
| ^^^^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^^^^
57 | next_ds = context["next_ds"]
58 | next_ds_nodash = context["next_ds_nodash"]
|
AIR301_context.py:57:23: AIR301 `next_ds` is removed in Airflow 3.0
AIR301 `next_ds` is removed in Airflow 3.0
--> AIR301_context.py:57:23
|
55 | context = get_current_context()
56 | execution_date = context["execution_date"]
57 | next_ds = context["next_ds"]
| ^^^^^^^^^ AIR301
| ^^^^^^^^^
58 | next_ds_nodash = context["next_ds_nodash"]
59 | next_execution_date = context["next_execution_date"]
|
AIR301_context.py:58:30: AIR301 `next_ds_nodash` is removed in Airflow 3.0
AIR301 `next_ds_nodash` is removed in Airflow 3.0
--> AIR301_context.py:58:30
|
56 | execution_date = context["execution_date"]
57 | next_ds = context["next_ds"]
58 | next_ds_nodash = context["next_ds_nodash"]
| ^^^^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^^^^
59 | next_execution_date = context["next_execution_date"]
60 | prev_ds = context["prev_ds"]
|
AIR301_context.py:59:35: AIR301 `next_execution_date` is removed in Airflow 3.0
AIR301 `next_execution_date` is removed in Airflow 3.0
--> AIR301_context.py:59:35
|
57 | next_ds = context["next_ds"]
58 | next_ds_nodash = context["next_ds_nodash"]
59 | next_execution_date = context["next_execution_date"]
| ^^^^^^^^^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^^^^^^^^^
60 | prev_ds = context["prev_ds"]
61 | prev_ds_nodash = context["prev_ds_nodash"]
|
AIR301_context.py:60:23: AIR301 `prev_ds` is removed in Airflow 3.0
AIR301 `prev_ds` is removed in Airflow 3.0
--> AIR301_context.py:60:23
|
58 | next_ds_nodash = context["next_ds_nodash"]
59 | next_execution_date = context["next_execution_date"]
60 | prev_ds = context["prev_ds"]
| ^^^^^^^^^ AIR301
| ^^^^^^^^^
61 | prev_ds_nodash = context["prev_ds_nodash"]
62 | prev_execution_date = context["prev_execution_date"]
|
AIR301_context.py:61:30: AIR301 `prev_ds_nodash` is removed in Airflow 3.0
AIR301 `prev_ds_nodash` is removed in Airflow 3.0
--> AIR301_context.py:61:30
|
59 | next_execution_date = context["next_execution_date"]
60 | prev_ds = context["prev_ds"]
61 | prev_ds_nodash = context["prev_ds_nodash"]
| ^^^^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^^^^
62 | prev_execution_date = context["prev_execution_date"]
63 | prev_execution_date_success = context["prev_execution_date_success"]
|
AIR301_context.py:62:35: AIR301 `prev_execution_date` is removed in Airflow 3.0
AIR301 `prev_execution_date` is removed in Airflow 3.0
--> AIR301_context.py:62:35
|
60 | prev_ds = context["prev_ds"]
61 | prev_ds_nodash = context["prev_ds_nodash"]
62 | prev_execution_date = context["prev_execution_date"]
| ^^^^^^^^^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^^^^^^^^^
63 | prev_execution_date_success = context["prev_execution_date_success"]
64 | tomorrow_ds = context["tomorrow_ds"]
|
AIR301_context.py:63:43: AIR301 `prev_execution_date_success` is removed in Airflow 3.0
AIR301 `prev_execution_date_success` is removed in Airflow 3.0
--> AIR301_context.py:63:43
|
61 | prev_ds_nodash = context["prev_ds_nodash"]
62 | prev_execution_date = context["prev_execution_date"]
63 | prev_execution_date_success = context["prev_execution_date_success"]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
64 | tomorrow_ds = context["tomorrow_ds"]
65 | yesterday_ds = context["yesterday_ds"]
|
AIR301_context.py:64:27: AIR301 `tomorrow_ds` is removed in Airflow 3.0
AIR301 `tomorrow_ds` is removed in Airflow 3.0
--> AIR301_context.py:64:27
|
62 | prev_execution_date = context["prev_execution_date"]
63 | prev_execution_date_success = context["prev_execution_date_success"]
64 | tomorrow_ds = context["tomorrow_ds"]
| ^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^
65 | yesterday_ds = context["yesterday_ds"]
66 | yesterday_ds_nodash = context["yesterday_ds_nodash"]
|
AIR301_context.py:65:28: AIR301 `yesterday_ds` is removed in Airflow 3.0
AIR301 `yesterday_ds` is removed in Airflow 3.0
--> AIR301_context.py:65:28
|
63 | prev_execution_date_success = context["prev_execution_date_success"]
64 | tomorrow_ds = context["tomorrow_ds"]
65 | yesterday_ds = context["yesterday_ds"]
| ^^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^^
66 | yesterday_ds_nodash = context["yesterday_ds_nodash"]
|
AIR301_context.py:66:35: AIR301 `yesterday_ds_nodash` is removed in Airflow 3.0
AIR301 `yesterday_ds_nodash` is removed in Airflow 3.0
--> AIR301_context.py:66:35
|
64 | tomorrow_ds = context["tomorrow_ds"]
65 | yesterday_ds = context["yesterday_ds"]
66 | yesterday_ds_nodash = context["yesterday_ds_nodash"]
| ^^^^^^^^^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^^^^^^^^^
|
AIR301_context.py:73:22: AIR301 `tomorrow_ds` is removed in Airflow 3.0
AIR301 `tomorrow_ds` is removed in Airflow 3.0
--> AIR301_context.py:73:22
|
71 | """Print the Airflow context and ds variable from the context."""
72 | print(ds)
73 | print(kwargs.get("tomorrow_ds"))
| ^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^
74 | c = get_current_context()
75 | c.get("execution_date")
|
AIR301_context.py:75:11: AIR301 `execution_date` is removed in Airflow 3.0
AIR301 `execution_date` is removed in Airflow 3.0
--> AIR301_context.py:75:11
|
73 | print(kwargs.get("tomorrow_ds"))
74 | c = get_current_context()
75 | c.get("execution_date")
| ^^^^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^^^^
|
AIR301_context.py:87:49: AIR301 `conf` is removed in Airflow 3.0
AIR301 `conf` is removed in Airflow 3.0
--> AIR301_context.py:87:49
|
85 | @task()
86 | def access_invalid_key_task(**context):
87 | print("access invalid key", context.get("conf"))
| ^^^^^^ AIR301
| ^^^^^^
88 |
89 | @task()
|
AIR301_context.py:90:42: AIR301 `execution_date` is removed in Airflow 3.0
AIR301 `execution_date` is removed in Airflow 3.0
--> AIR301_context.py:90:42
|
89 | @task()
90 | def access_invalid_key_explicit_task(execution_date):
| ^^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^^
91 | print(execution_date)
|
AIR301_context.py:111:5: AIR301 [*] `schedule_interval` is removed in Airflow 3.0
AIR301 [*] `schedule_interval` is removed in Airflow 3.0
--> AIR301_context.py:111:5
|
109 | with DAG(
110 | dag_id="example_dag",
111 | schedule_interval="@daily",
| ^^^^^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^^^^^
112 | start_date=datetime(2023, 1, 1),
113 | template_searchpath=["/templates"],
|
= help: Use `schedule` instead
help: Use `schedule` instead
Safe fix
108 108 |
@@ -316,11 +348,12 @@ AIR301_context.py:111:5: AIR301 [*] `schedule_interval` is removed in Airflow 3.
113 113 | template_searchpath=["/templates"],
114 114 | ) as dag:
AIR301_context.py:135:23: AIR301 `next_ds` is removed in Airflow 3.0
AIR301 `next_ds` is removed in Airflow 3.0
--> AIR301_context.py:135:23
|
134 | class CustomOperator(BaseOperator):
135 | def execute(self, next_ds, context):
| ^^^^^^^ AIR301
| ^^^^^^^
136 | execution_date = context["execution_date"]
137 | next_ds = context["next_ds"]
|

View File

@@ -1,294 +1,352 @@
---
source: crates/ruff_linter/src/rules/airflow/mod.rs
---
AIR301_names.py:38:1: AIR301 `airflow.PY36` is removed in Airflow 3.0
AIR301 `airflow.PY36` is removed in Airflow 3.0
--> AIR301_names.py:39:1
|
37 | # airflow root
38 | PY36, PY37, PY38, PY39, PY310, PY311, PY312
| ^^^^ AIR301
39 |
40 | # airflow.api_connexion.security
38 | # airflow root
39 | PY36, PY37, PY38, PY39, PY310, PY311, PY312
| ^^^^
40 |
41 | # airflow.api_connexion.security
|
= help: Use `sys.version_info` instead
help: Use `sys.version_info` instead
AIR301_names.py:38:7: AIR301 `airflow.PY37` is removed in Airflow 3.0
AIR301 `airflow.PY37` is removed in Airflow 3.0
--> AIR301_names.py:39:7
|
37 | # airflow root
38 | PY36, PY37, PY38, PY39, PY310, PY311, PY312
| ^^^^ AIR301
39 |
40 | # airflow.api_connexion.security
38 | # airflow root
39 | PY36, PY37, PY38, PY39, PY310, PY311, PY312
| ^^^^
40 |
41 | # airflow.api_connexion.security
|
= help: Use `sys.version_info` instead
help: Use `sys.version_info` instead
AIR301_names.py:38:13: AIR301 `airflow.PY38` is removed in Airflow 3.0
AIR301 `airflow.PY38` is removed in Airflow 3.0
--> AIR301_names.py:39:13
|
37 | # airflow root
38 | PY36, PY37, PY38, PY39, PY310, PY311, PY312
| ^^^^ AIR301
39 |
40 | # airflow.api_connexion.security
38 | # airflow root
39 | PY36, PY37, PY38, PY39, PY310, PY311, PY312
| ^^^^
40 |
41 | # airflow.api_connexion.security
|
= help: Use `sys.version_info` instead
help: Use `sys.version_info` instead
AIR301_names.py:38:19: AIR301 `airflow.PY39` is removed in Airflow 3.0
AIR301 `airflow.PY39` is removed in Airflow 3.0
--> AIR301_names.py:39:19
|
37 | # airflow root
38 | PY36, PY37, PY38, PY39, PY310, PY311, PY312
| ^^^^ AIR301
39 |
40 | # airflow.api_connexion.security
38 | # airflow root
39 | PY36, PY37, PY38, PY39, PY310, PY311, PY312
| ^^^^
40 |
41 | # airflow.api_connexion.security
|
= help: Use `sys.version_info` instead
help: Use `sys.version_info` instead
AIR301_names.py:38:25: AIR301 `airflow.PY310` is removed in Airflow 3.0
AIR301 `airflow.PY310` is removed in Airflow 3.0
--> AIR301_names.py:39:25
|
37 | # airflow root
38 | PY36, PY37, PY38, PY39, PY310, PY311, PY312
| ^^^^^ AIR301
39 |
40 | # airflow.api_connexion.security
38 | # airflow root
39 | PY36, PY37, PY38, PY39, PY310, PY311, PY312
| ^^^^^
40 |
41 | # airflow.api_connexion.security
|
= help: Use `sys.version_info` instead
help: Use `sys.version_info` instead
AIR301_names.py:38:32: AIR301 `airflow.PY311` is removed in Airflow 3.0
AIR301 `airflow.PY311` is removed in Airflow 3.0
--> AIR301_names.py:39:32
|
37 | # airflow root
38 | PY36, PY37, PY38, PY39, PY310, PY311, PY312
| ^^^^^ AIR301
39 |
40 | # airflow.api_connexion.security
38 | # airflow root
39 | PY36, PY37, PY38, PY39, PY310, PY311, PY312
| ^^^^^
40 |
41 | # airflow.api_connexion.security
|
= help: Use `sys.version_info` instead
help: Use `sys.version_info` instead
AIR301_names.py:38:39: AIR301 `airflow.PY312` is removed in Airflow 3.0
AIR301 `airflow.PY312` is removed in Airflow 3.0
--> AIR301_names.py:39:39
|
37 | # airflow root
38 | PY36, PY37, PY38, PY39, PY310, PY311, PY312
| ^^^^^ AIR301
39 |
40 | # airflow.api_connexion.security
38 | # airflow root
39 | PY36, PY37, PY38, PY39, PY310, PY311, PY312
| ^^^^^
40 |
41 | # airflow.api_connexion.security
|
= help: Use `sys.version_info` instead
help: Use `sys.version_info` instead
AIR301_names.py:41:1: AIR301 `airflow.api_connexion.security.requires_access` is removed in Airflow 3.0
AIR301 `airflow.api_connexion.security.requires_access` is removed in Airflow 3.0
--> AIR301_names.py:42:1
|
40 | # airflow.api_connexion.security
41 | requires_access
| ^^^^^^^^^^^^^^^ AIR301
42 |
43 | # airflow.contrib.*
41 | # airflow.api_connexion.security
42 | requires_access
| ^^^^^^^^^^^^^^^
43 |
44 | # airflow.contrib.*
|
= help: Use `airflow.api_fastapi.core_api.security.requires_access_*` instead
help: Use `airflow.api_fastapi.core_api.security.requires_access_*` instead
AIR301_names.py:44:1: AIR301 `airflow.contrib.aws_athena_hook.AWSAthenaHook` is removed in Airflow 3.0
AIR301 `airflow.contrib.aws_athena_hook.AWSAthenaHook` is removed in Airflow 3.0
--> AIR301_names.py:45:1
|
43 | # airflow.contrib.*
44 | AWSAthenaHook()
| ^^^^^^^^^^^^^ AIR301
44 | # airflow.contrib.*
45 | AWSAthenaHook()
| ^^^^^^^^^^^^^
|
= help: The whole `airflow.contrib` module has been removed.
help: The whole `airflow.contrib` module has been removed.
AIR301_names.py:48:1: AIR301 `airflow.datasets.DatasetAliasEvent` is removed in Airflow 3.0
AIR301 `airflow.datasets.DatasetAliasEvent` is removed in Airflow 3.0
--> AIR301_names.py:49:1
|
47 | # airflow.datasets
48 | DatasetAliasEvent()
| ^^^^^^^^^^^^^^^^^ AIR301
48 | # airflow.datasets
49 | DatasetAliasEvent()
| ^^^^^^^^^^^^^^^^^
|
AIR301_names.py:52:1: AIR301 `airflow.operators.subdag.SubDagOperator` is removed in Airflow 3.0
AIR301 `airflow.operators.subdag.SubDagOperator` is removed in Airflow 3.0
--> AIR301_names.py:53:1
|
51 | # airflow.operators.subdag.*
52 | SubDagOperator()
| ^^^^^^^^^^^^^^ AIR301
52 | # airflow.operators.subdag.*
53 | SubDagOperator()
| ^^^^^^^^^^^^^^
|
= help: The whole `airflow.subdag` module has been removed.
help: The whole `airflow.subdag` module has been removed.
AIR301_names.py:61:1: AIR301 `airflow.triggers.external_task.TaskStateTrigger` is removed in Airflow 3.0
AIR301 [*] `airflow.secrets.cache.SecretCache` is removed in Airflow 3.0
--> AIR301_names.py:61:1
|
60 | # airflow.triggers.external_task
61 | TaskStateTrigger()
| ^^^^^^^^^^^^^^^^ AIR301
62 |
63 | # airflow.utils.date
60 | # airflow.secrets.cache
61 | SecretCache()
| ^^^^^^^^^^^
|
help: Use `SecretCache` from `airflow.sdk` instead.
AIR301_names.py:64:1: AIR301 `airflow.utils.dates.date_range` is removed in Airflow 3.0
|
63 | # airflow.utils.date
64 | dates.date_range
| ^^^^^^^^^^^^^^^^ AIR301
65 | dates.days_ago
|
Unsafe fix
13 13 | from airflow.contrib.aws_athena_hook import AWSAthenaHook
14 14 | from airflow.datasets import DatasetAliasEvent
15 15 | from airflow.operators.subdag import SubDagOperator
16 |-from airflow.secrets.cache import SecretCache
17 16 | from airflow.secrets.local_filesystem import LocalFilesystemBackend
18 17 | from airflow.triggers.external_task import TaskStateTrigger
19 18 | from airflow.utils import dates
--------------------------------------------------------------------------------
34 33 | from airflow.utils.trigger_rule import TriggerRule
35 34 | from airflow.www.auth import has_access, has_access_dataset
36 35 | from airflow.www.utils import get_sensitive_variables_fields, should_hide_value_for_key
36 |+from airflow.sdk import SecretCache
37 37 |
38 38 | # airflow root
39 39 | PY36, PY37, PY38, PY39, PY310, PY311, PY312
AIR301_names.py:65:1: AIR301 `airflow.utils.dates.days_ago` is removed in Airflow 3.0
AIR301 `airflow.triggers.external_task.TaskStateTrigger` is removed in Airflow 3.0
--> AIR301_names.py:65:1
|
63 | # airflow.utils.date
64 | dates.date_range
65 | dates.days_ago
| ^^^^^^^^^^^^^^ AIR301
64 | # airflow.triggers.external_task
65 | TaskStateTrigger()
| ^^^^^^^^^^^^^^^^
66 |
67 | date_range
|
= help: Use `pendulum.today('UTC').add(days=-N, ...)` instead
AIR301_names.py:67:1: AIR301 `airflow.utils.dates.date_range` is removed in Airflow 3.0
|
65 | dates.days_ago
66 |
67 | date_range
| ^^^^^^^^^^ AIR301
68 | days_ago
69 | infer_time_unit
67 | # airflow.utils.date
|
AIR301_names.py:68:1: AIR301 `airflow.utils.dates.days_ago` is removed in Airflow 3.0
AIR301 `airflow.utils.dates.date_range` is removed in Airflow 3.0
--> AIR301_names.py:68:1
|
67 | date_range
68 | days_ago
| ^^^^^^^^ AIR301
69 | infer_time_unit
70 | parse_execution_date
|
= help: Use `pendulum.today('UTC').add(days=-N, ...)` instead
AIR301_names.py:69:1: AIR301 `airflow.utils.dates.infer_time_unit` is removed in Airflow 3.0
|
67 | date_range
68 | days_ago
69 | infer_time_unit
| ^^^^^^^^^^^^^^^ AIR301
70 | parse_execution_date
71 | round_time
67 | # airflow.utils.date
68 | dates.date_range
| ^^^^^^^^^^^^^^^^
69 | dates.days_ago
|
AIR301_names.py:70:1: AIR301 `airflow.utils.dates.parse_execution_date` is removed in Airflow 3.0
AIR301 `airflow.utils.dates.days_ago` is removed in Airflow 3.0
--> AIR301_names.py:69:1
|
68 | days_ago
69 | infer_time_unit
70 | parse_execution_date
| ^^^^^^^^^^^^^^^^^^^^ AIR301
71 | round_time
72 | scale_time_units
67 | # airflow.utils.date
68 | dates.date_range
69 | dates.days_ago
| ^^^^^^^^^^^^^^
70 |
71 | date_range
|
help: Use `pendulum.today('UTC').add(days=-N, ...)` instead
AIR301 `airflow.utils.dates.date_range` is removed in Airflow 3.0
--> AIR301_names.py:71:1
|
69 | dates.days_ago
70 |
71 | date_range
| ^^^^^^^^^^
72 | days_ago
73 | infer_time_unit
|
AIR301_names.py:71:1: AIR301 `airflow.utils.dates.round_time` is removed in Airflow 3.0
AIR301 `airflow.utils.dates.days_ago` is removed in Airflow 3.0
--> AIR301_names.py:72:1
|
69 | infer_time_unit
70 | parse_execution_date
71 | round_time
| ^^^^^^^^^^ AIR301
72 | scale_time_units
71 | date_range
72 | days_ago
| ^^^^^^^^
73 | infer_time_unit
74 | parse_execution_date
|
help: Use `pendulum.today('UTC').add(days=-N, ...)` instead
AIR301 `airflow.utils.dates.infer_time_unit` is removed in Airflow 3.0
--> AIR301_names.py:73:1
|
71 | date_range
72 | days_ago
73 | infer_time_unit
| ^^^^^^^^^^^^^^^
74 | parse_execution_date
75 | round_time
|
AIR301_names.py:72:1: AIR301 `airflow.utils.dates.scale_time_units` is removed in Airflow 3.0
AIR301 `airflow.utils.dates.parse_execution_date` is removed in Airflow 3.0
--> AIR301_names.py:74:1
|
70 | parse_execution_date
71 | round_time
72 | scale_time_units
| ^^^^^^^^^^^^^^^^ AIR301
73 |
74 | # This one was not deprecated.
72 | days_ago
73 | infer_time_unit
74 | parse_execution_date
| ^^^^^^^^^^^^^^^^^^^^
75 | round_time
76 | scale_time_units
|
AIR301_names.py:79:1: AIR301 `airflow.utils.dag_cycle_tester.test_cycle` is removed in Airflow 3.0
AIR301 `airflow.utils.dates.round_time` is removed in Airflow 3.0
--> AIR301_names.py:75:1
|
78 | # airflow.utils.dag_cycle_tester
79 | test_cycle
| ^^^^^^^^^^ AIR301
73 | infer_time_unit
74 | parse_execution_date
75 | round_time
| ^^^^^^^^^^
76 | scale_time_units
|
AIR301_names.py:83:1: AIR301 `airflow.utils.db.create_session` is removed in Airflow 3.0
AIR301 `airflow.utils.dates.scale_time_units` is removed in Airflow 3.0
--> AIR301_names.py:76:1
|
82 | # airflow.utils.db
83 | create_session
| ^^^^^^^^^^^^^^ AIR301
84 |
85 | # airflow.utils.decorators
74 | parse_execution_date
75 | round_time
76 | scale_time_units
| ^^^^^^^^^^^^^^^^
77 |
78 | # This one was not deprecated.
|
AIR301_names.py:86:1: AIR301 `airflow.utils.decorators.apply_defaults` is removed in Airflow 3.0
AIR301 `airflow.utils.dag_cycle_tester.test_cycle` is removed in Airflow 3.0
--> AIR301_names.py:83:1
|
85 | # airflow.utils.decorators
86 | apply_defaults
| ^^^^^^^^^^^^^^ AIR301
87 |
88 | # airflow.utils.file
|
= help: `apply_defaults` is now unconditionally done and can be safely removed.
AIR301_names.py:89:1: AIR301 `airflow.utils.file.mkdirs` is removed in Airflow 3.0
|
88 | # airflow.utils.file
89 | mkdirs
| ^^^^^^ AIR301
|
= help: Use `pathlib.Path({path}).mkdir` instead
AIR301_names.py:93:1: AIR301 `airflow.utils.state.SHUTDOWN` is removed in Airflow 3.0
|
92 | # airflow.utils.state
93 | SHUTDOWN
| ^^^^^^^^ AIR301
94 | terminating_states
82 | # airflow.utils.dag_cycle_tester
83 | test_cycle
| ^^^^^^^^^^
|
AIR301_names.py:94:1: AIR301 `airflow.utils.state.terminating_states` is removed in Airflow 3.0
AIR301 `airflow.utils.db.create_session` is removed in Airflow 3.0
--> AIR301_names.py:87:1
|
92 | # airflow.utils.state
93 | SHUTDOWN
94 | terminating_states
| ^^^^^^^^^^^^^^^^^^ AIR301
95 |
96 | # airflow.utils.trigger_rule
86 | # airflow.utils.db
87 | create_session
| ^^^^^^^^^^^^^^
88 |
89 | # airflow.utils.decorators
|
AIR301_names.py:97:1: AIR301 `airflow.utils.trigger_rule.TriggerRule.DUMMY` is removed in Airflow 3.0
AIR301 `airflow.utils.decorators.apply_defaults` is removed in Airflow 3.0
--> AIR301_names.py:90:1
|
96 | # airflow.utils.trigger_rule
97 | TriggerRule.DUMMY
| ^^^^^^^^^^^^^^^^^ AIR301
98 | TriggerRule.NONE_FAILED_OR_SKIPPED
89 | # airflow.utils.decorators
90 | apply_defaults
| ^^^^^^^^^^^^^^
91 |
92 | # airflow.utils.file
|
help: `apply_defaults` is now unconditionally done and can be safely removed.
AIR301 `airflow.utils.file.mkdirs` is removed in Airflow 3.0
--> AIR301_names.py:93:1
|
92 | # airflow.utils.file
93 | mkdirs
| ^^^^^^
|
help: Use `pathlib.Path({path}).mkdir` instead
AIR301 `airflow.utils.state.SHUTDOWN` is removed in Airflow 3.0
--> AIR301_names.py:97:1
|
96 | # airflow.utils.state
97 | SHUTDOWN
| ^^^^^^^^
98 | terminating_states
|
AIR301_names.py:98:1: AIR301 `airflow.utils.trigger_rule.TriggerRule.NONE_FAILED_OR_SKIPPED` is removed in Airflow 3.0
|
96 | # airflow.utils.trigger_rule
97 | TriggerRule.DUMMY
98 | TriggerRule.NONE_FAILED_OR_SKIPPED
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ AIR301
|
AIR301_names.py:102:1: AIR301 `airflow.www.auth.has_access` is removed in Airflow 3.0
AIR301 `airflow.utils.state.terminating_states` is removed in Airflow 3.0
--> AIR301_names.py:98:1
|
101 | # airflow.www.auth
102 | has_access
| ^^^^^^^^^^ AIR301
103 | has_access_dataset
96 | # airflow.utils.state
97 | SHUTDOWN
98 | terminating_states
| ^^^^^^^^^^^^^^^^^^
99 |
100 | # airflow.utils.trigger_rule
|
AIR301_names.py:103:1: AIR301 `airflow.www.auth.has_access_dataset` is removed in Airflow 3.0
AIR301 `airflow.utils.trigger_rule.TriggerRule.DUMMY` is removed in Airflow 3.0
--> AIR301_names.py:101:1
|
101 | # airflow.www.auth
102 | has_access
103 | has_access_dataset
| ^^^^^^^^^^^^^^^^^^ AIR301
104 |
105 | # airflow.www.utils
100 | # airflow.utils.trigger_rule
101 | TriggerRule.DUMMY
| ^^^^^^^^^^^^^^^^^
102 | TriggerRule.NONE_FAILED_OR_SKIPPED
|
AIR301_names.py:106:1: AIR301 `airflow.www.utils.get_sensitive_variables_fields` is removed in Airflow 3.0
AIR301 `airflow.utils.trigger_rule.TriggerRule.NONE_FAILED_OR_SKIPPED` is removed in Airflow 3.0
--> AIR301_names.py:102:1
|
105 | # airflow.www.utils
106 | get_sensitive_variables_fields
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ AIR301
107 | should_hide_value_for_key
100 | # airflow.utils.trigger_rule
101 | TriggerRule.DUMMY
102 | TriggerRule.NONE_FAILED_OR_SKIPPED
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
AIR301_names.py:107:1: AIR301 `airflow.www.utils.should_hide_value_for_key` is removed in Airflow 3.0
AIR301 `airflow.www.auth.has_access` is removed in Airflow 3.0
--> AIR301_names.py:106:1
|
105 | # airflow.www.utils
106 | get_sensitive_variables_fields
107 | should_hide_value_for_key
| ^^^^^^^^^^^^^^^^^^^^^^^^^ AIR301
105 | # airflow.www.auth
106 | has_access
| ^^^^^^^^^^
107 | has_access_dataset
|
AIR301 `airflow.www.auth.has_access_dataset` is removed in Airflow 3.0
--> AIR301_names.py:107:1
|
105 | # airflow.www.auth
106 | has_access
107 | has_access_dataset
| ^^^^^^^^^^^^^^^^^^
108 |
109 | # airflow.www.utils
|
AIR301 `airflow.www.utils.get_sensitive_variables_fields` is removed in Airflow 3.0
--> AIR301_names.py:110:1
|
109 | # airflow.www.utils
110 | get_sensitive_variables_fields
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
111 | should_hide_value_for_key
|
AIR301 `airflow.www.utils.should_hide_value_for_key` is removed in Airflow 3.0
--> AIR301_names.py:111:1
|
109 | # airflow.www.utils
110 | get_sensitive_variables_fields
111 | should_hide_value_for_key
| ^^^^^^^^^^^^^^^^^^^^^^^^^
|

View File

@@ -1,16 +1,17 @@
---
source: crates/ruff_linter/src/rules/airflow/mod.rs
---
AIR301_names_fix.py:17:1: AIR301 [*] `airflow.api_connexion.security.requires_access_dataset` is removed in Airflow 3.0
AIR301 [*] `airflow.api_connexion.security.requires_access_dataset` is removed in Airflow 3.0
--> AIR301_names_fix.py:17:1
|
15 | from airflow.security.permissions import RESOURCE_DATASET
16 |
17 | requires_access_dataset()
| ^^^^^^^^^^^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^^^^^^^^^^^
18 |
19 | DatasetDetails()
|
= help: Use `requires_access_asset` from `airflow.api_fastapi.core_api.security` instead.
help: Use `requires_access_asset` from `airflow.api_fastapi.core_api.security` instead.
Safe fix
13 13 | from airflow.metrics.validators import AllowListValidator, BlockListValidator
@@ -24,16 +25,17 @@ AIR301_names_fix.py:17:1: AIR301 [*] `airflow.api_connexion.security.requires_ac
19 20 | DatasetDetails()
20 21 |
AIR301_names_fix.py:19:1: AIR301 [*] `airflow.auth.managers.models.resource_details.DatasetDetails` is removed in Airflow 3.0
AIR301 [*] `airflow.auth.managers.models.resource_details.DatasetDetails` is removed in Airflow 3.0
--> AIR301_names_fix.py:19:1
|
17 | requires_access_dataset()
18 |
19 | DatasetDetails()
| ^^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^^
20 |
21 | DatasetManager()
|
= help: Use `AssetDetails` from `airflow.api_fastapi.auth.managers.models.resource_details` instead.
help: Use `AssetDetails` from `airflow.api_fastapi.auth.managers.models.resource_details` instead.
Safe fix
13 13 | from airflow.metrics.validators import AllowListValidator, BlockListValidator
@@ -49,16 +51,17 @@ AIR301_names_fix.py:19:1: AIR301 [*] `airflow.auth.managers.models.resource_deta
21 22 | DatasetManager()
22 23 | dataset_manager()
AIR301_names_fix.py:21:1: AIR301 [*] `airflow.datasets.manager.DatasetManager` is removed in Airflow 3.0
AIR301 [*] `airflow.datasets.manager.DatasetManager` is removed in Airflow 3.0
--> AIR301_names_fix.py:21:1
|
19 | DatasetDetails()
20 |
21 | DatasetManager()
| ^^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^^
22 | dataset_manager()
23 | resolve_dataset_manager()
|
= help: Use `AssetManager` from `airflow.assets.manager` instead.
help: Use `AssetManager` from `airflow.assets.manager` instead.
Safe fix
13 13 | from airflow.metrics.validators import AllowListValidator, BlockListValidator
@@ -76,14 +79,15 @@ AIR301_names_fix.py:21:1: AIR301 [*] `airflow.datasets.manager.DatasetManager` i
23 24 | resolve_dataset_manager()
24 25 |
AIR301_names_fix.py:22:1: AIR301 [*] `airflow.datasets.manager.dataset_manager` is removed in Airflow 3.0
AIR301 [*] `airflow.datasets.manager.dataset_manager` is removed in Airflow 3.0
--> AIR301_names_fix.py:22:1
|
21 | DatasetManager()
22 | dataset_manager()
| ^^^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^^^
23 | resolve_dataset_manager()
|
= help: Use `asset_manager` from `airflow.assets.manager` instead.
help: Use `asset_manager` from `airflow.assets.manager` instead.
Safe fix
13 13 | from airflow.metrics.validators import AllowListValidator, BlockListValidator
@@ -102,16 +106,17 @@ AIR301_names_fix.py:22:1: AIR301 [*] `airflow.datasets.manager.dataset_manager`
24 25 |
25 26 | DatasetLineageInfo()
AIR301_names_fix.py:23:1: AIR301 [*] `airflow.datasets.manager.resolve_dataset_manager` is removed in Airflow 3.0
AIR301 [*] `airflow.datasets.manager.resolve_dataset_manager` is removed in Airflow 3.0
--> AIR301_names_fix.py:23:1
|
21 | DatasetManager()
22 | dataset_manager()
23 | resolve_dataset_manager()
| ^^^^^^^^^^^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^^^^^^^^^^^
24 |
25 | DatasetLineageInfo()
|
= help: Use `resolve_asset_manager` from `airflow.assets.manager` instead.
help: Use `resolve_asset_manager` from `airflow.assets.manager` instead.
Safe fix
13 13 | from airflow.metrics.validators import AllowListValidator, BlockListValidator
@@ -131,16 +136,17 @@ AIR301_names_fix.py:23:1: AIR301 [*] `airflow.datasets.manager.resolve_dataset_m
25 26 | DatasetLineageInfo()
26 27 |
AIR301_names_fix.py:25:1: AIR301 [*] `airflow.lineage.hook.DatasetLineageInfo` is removed in Airflow 3.0
AIR301 [*] `airflow.lineage.hook.DatasetLineageInfo` is removed in Airflow 3.0
--> AIR301_names_fix.py:25:1
|
23 | resolve_dataset_manager()
24 |
25 | DatasetLineageInfo()
| ^^^^^^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^^^^^^
26 |
27 | AllowListValidator()
|
= help: Use `AssetLineageInfo` from `airflow.lineage.hook` instead.
help: Use `AssetLineageInfo` from `airflow.lineage.hook` instead.
Safe fix
9 9 | dataset_manager,
@@ -161,15 +167,16 @@ AIR301_names_fix.py:25:1: AIR301 [*] `airflow.lineage.hook.DatasetLineageInfo` i
27 27 | AllowListValidator()
28 28 | BlockListValidator()
AIR301_names_fix.py:27:1: AIR301 [*] `airflow.metrics.validators.AllowListValidator` is removed in Airflow 3.0
AIR301 [*] `airflow.metrics.validators.AllowListValidator` is removed in Airflow 3.0
--> AIR301_names_fix.py:27:1
|
25 | DatasetLineageInfo()
26 |
27 | AllowListValidator()
| ^^^^^^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^^^^^^
28 | BlockListValidator()
|
= help: Use `PatternAllowListValidator` from `airflow.metrics.validators` instead.
help: Use `PatternAllowListValidator` from `airflow.metrics.validators` instead.
Safe fix
10 10 | resolve_dataset_manager,
@@ -190,15 +197,16 @@ AIR301_names_fix.py:27:1: AIR301 [*] `airflow.metrics.validators.AllowListValida
29 29 |
30 30 | load_connections()
AIR301_names_fix.py:28:1: AIR301 [*] `airflow.metrics.validators.BlockListValidator` is removed in Airflow 3.0
AIR301 [*] `airflow.metrics.validators.BlockListValidator` is removed in Airflow 3.0
--> AIR301_names_fix.py:28:1
|
27 | AllowListValidator()
28 | BlockListValidator()
| ^^^^^^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^^^^^^
29 |
30 | load_connections()
|
= help: Use `PatternBlockListValidator` from `airflow.metrics.validators` instead.
help: Use `PatternBlockListValidator` from `airflow.metrics.validators` instead.
Safe fix
10 10 | resolve_dataset_manager,
@@ -219,16 +227,17 @@ AIR301_names_fix.py:28:1: AIR301 [*] `airflow.metrics.validators.BlockListValida
30 30 | load_connections()
31 31 |
AIR301_names_fix.py:30:1: AIR301 [*] `airflow.secrets.local_filesystem.load_connections` is removed in Airflow 3.0
AIR301 [*] `airflow.secrets.local_filesystem.load_connections` is removed in Airflow 3.0
--> AIR301_names_fix.py:30:1
|
28 | BlockListValidator()
29 |
30 | load_connections()
| ^^^^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^^^^
31 |
32 | RESOURCE_DATASET
|
= help: Use `load_connections_dict` from `airflow.secrets.local_filesystem` instead.
help: Use `load_connections_dict` from `airflow.secrets.local_filesystem` instead.
Safe fix
11 11 | )
@@ -249,14 +258,15 @@ AIR301_names_fix.py:30:1: AIR301 [*] `airflow.secrets.local_filesystem.load_conn
32 32 | RESOURCE_DATASET
33 33 |
AIR301_names_fix.py:32:1: AIR301 [*] `airflow.security.permissions.RESOURCE_DATASET` is removed in Airflow 3.0
AIR301 [*] `airflow.security.permissions.RESOURCE_DATASET` is removed in Airflow 3.0
--> AIR301_names_fix.py:32:1
|
30 | load_connections()
31 |
32 | RESOURCE_DATASET
| ^^^^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^^^^
|
= help: Use `RESOURCE_ASSET` from `airflow.security.permissions` instead.
help: Use `RESOURCE_ASSET` from `airflow.security.permissions` instead.
Safe fix
12 12 | from airflow.lineage.hook import DatasetLineageInfo
@@ -277,15 +287,16 @@ AIR301_names_fix.py:32:1: AIR301 [*] `airflow.security.permissions.RESOURCE_DATA
34 34 |
35 35 | from airflow.listeners.spec.dataset import (
AIR301_names_fix.py:40:1: AIR301 [*] `airflow.listeners.spec.dataset.on_dataset_created` is removed in Airflow 3.0
AIR301 [*] `airflow.listeners.spec.dataset.on_dataset_created` is removed in Airflow 3.0
--> AIR301_names_fix.py:40:1
|
38 | )
39 |
40 | on_dataset_created()
| ^^^^^^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^^^^^^
41 | on_dataset_changed()
|
= help: Use `on_asset_created` from `airflow.listeners.spec.asset` instead.
help: Use `on_asset_created` from `airflow.listeners.spec.asset` instead.
Safe fix
36 36 | on_dataset_changed,
@@ -299,13 +310,14 @@ AIR301_names_fix.py:40:1: AIR301 [*] `airflow.listeners.spec.dataset.on_dataset_
42 43 |
43 44 |
AIR301_names_fix.py:41:1: AIR301 [*] `airflow.listeners.spec.dataset.on_dataset_changed` is removed in Airflow 3.0
AIR301 [*] `airflow.listeners.spec.dataset.on_dataset_changed` is removed in Airflow 3.0
--> AIR301_names_fix.py:41:1
|
40 | on_dataset_created()
41 | on_dataset_changed()
| ^^^^^^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^^^^^^
|
= help: Use `on_asset_changed` from `airflow.listeners.spec.asset` instead.
help: Use `on_asset_changed` from `airflow.listeners.spec.asset` instead.
Safe fix
36 36 | on_dataset_changed,
@@ -320,16 +332,17 @@ AIR301_names_fix.py:41:1: AIR301 [*] `airflow.listeners.spec.dataset.on_dataset_
43 44 |
44 45 | # airflow.operators.python
AIR301_names_fix.py:47:1: AIR301 [*] `airflow.operators.python.get_current_context` is removed in Airflow 3.0
AIR301 [*] `airflow.operators.python.get_current_context` is removed in Airflow 3.0
--> AIR301_names_fix.py:47:1
|
45 | from airflow.operators.python import get_current_context
46 |
47 | get_current_context()
| ^^^^^^^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^^^^^^^
48 |
49 | # airflow.providers.mysql
|
= help: Use `get_current_context` from `airflow.sdk` instead.
help: Use `get_current_context` from `airflow.sdk` instead.
Unsafe fix
42 42 |
@@ -341,16 +354,17 @@ AIR301_names_fix.py:47:1: AIR301 [*] `airflow.operators.python.get_current_conte
47 47 | get_current_context()
48 48 |
AIR301_names_fix.py:52:1: AIR301 [*] `airflow.providers.mysql.datasets.mysql.sanitize_uri` is removed in Airflow 3.0
AIR301 [*] `airflow.providers.mysql.datasets.mysql.sanitize_uri` is removed in Airflow 3.0
--> AIR301_names_fix.py:52:1
|
50 | from airflow.providers.mysql.datasets.mysql import sanitize_uri
51 |
52 | sanitize_uri
| ^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^
53 |
54 | # airflow.providers.postgres
|
= help: Use `sanitize_uri` from `airflow.providers.mysql.assets.mysql` instead.
help: Use `sanitize_uri` from `airflow.providers.mysql.assets.mysql` instead.
Unsafe fix
47 47 | get_current_context()
@@ -362,16 +376,17 @@ AIR301_names_fix.py:52:1: AIR301 [*] `airflow.providers.mysql.datasets.mysql.san
52 52 | sanitize_uri
53 53 |
AIR301_names_fix.py:57:1: AIR301 [*] `airflow.providers.postgres.datasets.postgres.sanitize_uri` is removed in Airflow 3.0
AIR301 [*] `airflow.providers.postgres.datasets.postgres.sanitize_uri` is removed in Airflow 3.0
--> AIR301_names_fix.py:57:1
|
55 | from airflow.providers.postgres.datasets.postgres import sanitize_uri
56 |
57 | sanitize_uri
| ^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^
58 |
59 | # airflow.providers.trino
|
= help: Use `sanitize_uri` from `airflow.providers.postgres.assets.postgres` instead.
help: Use `sanitize_uri` from `airflow.providers.postgres.assets.postgres` instead.
Unsafe fix
52 52 | sanitize_uri
@@ -383,16 +398,17 @@ AIR301_names_fix.py:57:1: AIR301 [*] `airflow.providers.postgres.datasets.postgr
57 57 | sanitize_uri
58 58 |
AIR301_names_fix.py:62:1: AIR301 [*] `airflow.providers.trino.datasets.trino.sanitize_uri` is removed in Airflow 3.0
AIR301 [*] `airflow.providers.trino.datasets.trino.sanitize_uri` is removed in Airflow 3.0
--> AIR301_names_fix.py:62:1
|
60 | from airflow.providers.trino.datasets.trino import sanitize_uri
61 |
62 | sanitize_uri
| ^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^
63 |
64 | # airflow.notifications.basenotifier
|
= help: Use `sanitize_uri` from `airflow.providers.trino.assets.trino` instead.
help: Use `sanitize_uri` from `airflow.providers.trino.assets.trino` instead.
Unsafe fix
57 57 | sanitize_uri
@@ -404,16 +420,17 @@ AIR301_names_fix.py:62:1: AIR301 [*] `airflow.providers.trino.datasets.trino.san
62 62 | sanitize_uri
63 63 |
AIR301_names_fix.py:67:1: AIR301 [*] `airflow.notifications.basenotifier.BaseNotifier` is removed in Airflow 3.0
AIR301 [*] `airflow.notifications.basenotifier.BaseNotifier` is removed in Airflow 3.0
--> AIR301_names_fix.py:67:1
|
65 | from airflow.notifications.basenotifier import BaseNotifier
66 |
67 | BaseNotifier()
| ^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^
68 |
69 | # airflow.auth.manager
|
= help: Use `BaseNotifier` from `airflow.sdk.bases.notifier` instead.
help: Use `BaseNotifier` from `airflow.sdk.bases.notifier` instead.
Unsafe fix
62 62 | sanitize_uri
@@ -425,14 +442,15 @@ AIR301_names_fix.py:67:1: AIR301 [*] `airflow.notifications.basenotifier.BaseNot
67 67 | BaseNotifier()
68 68 |
AIR301_names_fix.py:72:1: AIR301 [*] `airflow.auth.managers.base_auth_manager.BaseAuthManager` is removed in Airflow 3.0
AIR301 [*] `airflow.auth.managers.base_auth_manager.BaseAuthManager` is removed in Airflow 3.0
--> AIR301_names_fix.py:72:1
|
70 | from airflow.auth.managers.base_auth_manager import BaseAuthManager
71 |
72 | BaseAuthManager()
| ^^^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^^^
|
= help: Use `BaseAuthManager` from `airflow.api_fastapi.auth.managers.base_auth_manager` instead.
help: Use `BaseAuthManager` from `airflow.api_fastapi.auth.managers.base_auth_manager` instead.
Unsafe fix
67 67 | BaseNotifier()
@@ -444,14 +462,15 @@ AIR301_names_fix.py:72:1: AIR301 [*] `airflow.auth.managers.base_auth_manager.Ba
72 72 | BaseAuthManager()
73 73 |
AIR301_names_fix.py:87:1: AIR301 [*] `airflow.configuration.get` is removed in Airflow 3.0
AIR301 [*] `airflow.configuration.get` is removed in Airflow 3.0
--> AIR301_names_fix.py:87:1
|
86 | # airflow.configuration
87 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set
| ^^^ AIR301
| ^^^
88 | from airflow.hooks.base_hook import BaseHook
|
= help: Use `conf.get` from `airflow.configuration` instead.
help: Use `conf.get` from `airflow.configuration` instead.
Safe fix
81 81 | has_option,
@@ -467,14 +486,15 @@ AIR301_names_fix.py:87:1: AIR301 [*] `airflow.configuration.get` is removed in A
89 90 |
90 91 | # airflow.hooks
AIR301_names_fix.py:87:6: AIR301 [*] `airflow.configuration.getboolean` is removed in Airflow 3.0
AIR301 [*] `airflow.configuration.getboolean` is removed in Airflow 3.0
--> AIR301_names_fix.py:87:6
|
86 | # airflow.configuration
87 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set
| ^^^^^^^^^^ AIR301
| ^^^^^^^^^^
88 | from airflow.hooks.base_hook import BaseHook
|
= help: Use `conf.getboolean` from `airflow.configuration` instead.
help: Use `conf.getboolean` from `airflow.configuration` instead.
Safe fix
81 81 | has_option,
@@ -490,14 +510,15 @@ AIR301_names_fix.py:87:6: AIR301 [*] `airflow.configuration.getboolean` is remov
89 90 |
90 91 | # airflow.hooks
AIR301_names_fix.py:87:18: AIR301 [*] `airflow.configuration.getfloat` is removed in Airflow 3.0
AIR301 [*] `airflow.configuration.getfloat` is removed in Airflow 3.0
--> AIR301_names_fix.py:87:18
|
86 | # airflow.configuration
87 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set
| ^^^^^^^^ AIR301
| ^^^^^^^^
88 | from airflow.hooks.base_hook import BaseHook
|
= help: Use `conf.getfloat` from `airflow.configuration` instead.
help: Use `conf.getfloat` from `airflow.configuration` instead.
Safe fix
81 81 | has_option,
@@ -513,14 +534,15 @@ AIR301_names_fix.py:87:18: AIR301 [*] `airflow.configuration.getfloat` is remove
89 90 |
90 91 | # airflow.hooks
AIR301_names_fix.py:87:28: AIR301 [*] `airflow.configuration.getint` is removed in Airflow 3.0
AIR301 [*] `airflow.configuration.getint` is removed in Airflow 3.0
--> AIR301_names_fix.py:87:28
|
86 | # airflow.configuration
87 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set
| ^^^^^^ AIR301
| ^^^^^^
88 | from airflow.hooks.base_hook import BaseHook
|
= help: Use `conf.getint` from `airflow.configuration` instead.
help: Use `conf.getint` from `airflow.configuration` instead.
Safe fix
81 81 | has_option,
@@ -536,14 +558,15 @@ AIR301_names_fix.py:87:28: AIR301 [*] `airflow.configuration.getint` is removed
89 90 |
90 91 | # airflow.hooks
AIR301_names_fix.py:87:36: AIR301 [*] `airflow.configuration.has_option` is removed in Airflow 3.0
AIR301 [*] `airflow.configuration.has_option` is removed in Airflow 3.0
--> AIR301_names_fix.py:87:36
|
86 | # airflow.configuration
87 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set
| ^^^^^^^^^^ AIR301
| ^^^^^^^^^^
88 | from airflow.hooks.base_hook import BaseHook
|
= help: Use `conf.has_option` from `airflow.configuration` instead.
help: Use `conf.has_option` from `airflow.configuration` instead.
Safe fix
81 81 | has_option,
@@ -559,14 +582,15 @@ AIR301_names_fix.py:87:36: AIR301 [*] `airflow.configuration.has_option` is remo
89 90 |
90 91 | # airflow.hooks
AIR301_names_fix.py:87:48: AIR301 [*] `airflow.configuration.remove_option` is removed in Airflow 3.0
AIR301 [*] `airflow.configuration.remove_option` is removed in Airflow 3.0
--> AIR301_names_fix.py:87:48
|
86 | # airflow.configuration
87 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set
| ^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^
88 | from airflow.hooks.base_hook import BaseHook
|
= help: Use `conf.remove_option` from `airflow.configuration` instead.
help: Use `conf.remove_option` from `airflow.configuration` instead.
Safe fix
81 81 | has_option,
@@ -582,14 +606,15 @@ AIR301_names_fix.py:87:48: AIR301 [*] `airflow.configuration.remove_option` is r
89 90 |
90 91 | # airflow.hooks
AIR301_names_fix.py:87:63: AIR301 [*] `airflow.configuration.as_dict` is removed in Airflow 3.0
AIR301 [*] `airflow.configuration.as_dict` is removed in Airflow 3.0
--> AIR301_names_fix.py:87:63
|
86 | # airflow.configuration
87 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set
| ^^^^^^^ AIR301
| ^^^^^^^
88 | from airflow.hooks.base_hook import BaseHook
|
= help: Use `conf.as_dict` from `airflow.configuration` instead.
help: Use `conf.as_dict` from `airflow.configuration` instead.
Safe fix
81 81 | has_option,
@@ -605,14 +630,15 @@ AIR301_names_fix.py:87:63: AIR301 [*] `airflow.configuration.as_dict` is removed
89 90 |
90 91 | # airflow.hooks
AIR301_names_fix.py:87:72: AIR301 [*] `airflow.configuration.set` is removed in Airflow 3.0
AIR301 [*] `airflow.configuration.set` is removed in Airflow 3.0
--> AIR301_names_fix.py:87:72
|
86 | # airflow.configuration
87 | get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set
| ^^^ AIR301
| ^^^
88 | from airflow.hooks.base_hook import BaseHook
|
= help: Use `conf.set` from `airflow.configuration` instead.
help: Use `conf.set` from `airflow.configuration` instead.
Safe fix
81 81 | has_option,
@@ -628,15 +654,16 @@ AIR301_names_fix.py:87:72: AIR301 [*] `airflow.configuration.set` is removed in
89 90 |
90 91 | # airflow.hooks
AIR301_names_fix.py:91:1: AIR301 [*] `airflow.hooks.base_hook.BaseHook` is removed in Airflow 3.0
AIR301 [*] `airflow.hooks.base_hook.BaseHook` is removed in Airflow 3.0
--> AIR301_names_fix.py:91:1
|
90 | # airflow.hooks
91 | BaseHook()
| ^^^^^^^^ AIR301
| ^^^^^^^^
92 |
93 | from airflow.sensors.base_sensor_operator import BaseSensorOperator
|
= help: Use `BaseHook` from `airflow.hooks.base` instead.
help: Use `BaseHook` from `airflow.hooks.base` instead.
Unsafe fix
85 85 |
@@ -648,14 +675,15 @@ AIR301_names_fix.py:91:1: AIR301 [*] `airflow.hooks.base_hook.BaseHook` is remov
90 90 | # airflow.hooks
91 91 | BaseHook()
AIR301_names_fix.py:96:1: AIR301 [*] `airflow.sensors.base_sensor_operator.BaseSensorOperator` is removed in Airflow 3.0
AIR301 [*] `airflow.sensors.base_sensor_operator.BaseSensorOperator` is removed in Airflow 3.0
--> AIR301_names_fix.py:96:1
|
95 | # airflow.sensors.base_sensor_operator
96 | BaseSensorOperator()
| ^^^^^^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^^^^^^
97 | BaseHook()
|
= help: Use `BaseSensorOperator` from `airflow.sdk.bases.sensor` instead.
help: Use `BaseSensorOperator` from `airflow.sdk.bases.sensor` instead.
Unsafe fix
90 90 | # airflow.hooks
@@ -667,16 +695,17 @@ AIR301_names_fix.py:96:1: AIR301 [*] `airflow.sensors.base_sensor_operator.BaseS
95 95 | # airflow.sensors.base_sensor_operator
96 96 | BaseSensorOperator()
AIR301_names_fix.py:97:1: AIR301 [*] `airflow.hooks.base_hook.BaseHook` is removed in Airflow 3.0
AIR301 [*] `airflow.hooks.base_hook.BaseHook` is removed in Airflow 3.0
--> AIR301_names_fix.py:97:1
|
95 | # airflow.sensors.base_sensor_operator
96 | BaseSensorOperator()
97 | BaseHook()
| ^^^^^^^^ AIR301
| ^^^^^^^^
98 |
99 | from airflow.utils.helpers import chain as helper_chain
|
= help: Use `BaseHook` from `airflow.hooks.base` instead.
help: Use `BaseHook` from `airflow.hooks.base` instead.
Unsafe fix
85 85 |
@@ -693,14 +722,15 @@ AIR301_names_fix.py:97:1: AIR301 [*] `airflow.hooks.base_hook.BaseHook` is remov
95 95 | # airflow.sensors.base_sensor_operator
96 96 | BaseSensorOperator()
AIR301_names_fix.py:103:1: AIR301 [*] `airflow.utils.helpers.chain` is removed in Airflow 3.0
AIR301 [*] `airflow.utils.helpers.chain` is removed in Airflow 3.0
--> AIR301_names_fix.py:103:1
|
102 | # airflow.utils.helpers
103 | helper_chain
| ^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^
104 | helper_cross_downstream
|
= help: Use `chain` from `airflow.sdk` instead.
help: Use `chain` from `airflow.sdk` instead.
Safe fix
98 98 |
@@ -715,16 +745,17 @@ AIR301_names_fix.py:103:1: AIR301 [*] `airflow.utils.helpers.chain` is removed i
105 106 |
106 107 | # airflow.utils.file
AIR301_names_fix.py:104:1: AIR301 [*] `airflow.utils.helpers.cross_downstream` is removed in Airflow 3.0
AIR301 [*] `airflow.utils.helpers.cross_downstream` is removed in Airflow 3.0
--> AIR301_names_fix.py:104:1
|
102 | # airflow.utils.helpers
103 | helper_chain
104 | helper_cross_downstream
| ^^^^^^^^^^^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^^^^^^^^^^^
105 |
106 | # airflow.utils.file
|
= help: Use `cross_downstream` from `airflow.sdk` instead.
help: Use `cross_downstream` from `airflow.sdk` instead.
Safe fix
98 98 |
@@ -740,16 +771,17 @@ AIR301_names_fix.py:104:1: AIR301 [*] `airflow.utils.helpers.cross_downstream` i
106 107 | # airflow.utils.file
107 108 | from airflow.utils.file import TemporaryDirectory
AIR301_names_fix.py:109:1: AIR301 [*] `airflow.utils.file.TemporaryDirectory` is removed in Airflow 3.0
AIR301 [*] `airflow.utils.file.TemporaryDirectory` is removed in Airflow 3.0
--> AIR301_names_fix.py:109:1
|
107 | from airflow.utils.file import TemporaryDirectory
108 |
109 | TemporaryDirectory()
| ^^^^^^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^^^^^^
110 |
111 | from airflow.utils.log import secrets_masker
|
= help: Use `TemporaryDirectory` from `tempfile` instead.
help: Use `TemporaryDirectory` from `tempfile` instead.
Unsafe fix
104 104 | helper_cross_downstream
@@ -761,13 +793,14 @@ AIR301_names_fix.py:109:1: AIR301 [*] `airflow.utils.file.TemporaryDirectory` is
109 109 | TemporaryDirectory()
110 110 |
AIR301_names_fix.py:114:1: AIR301 [*] `airflow.utils.log.secrets_masker` is removed in Airflow 3.0
AIR301 [*] `airflow.utils.log.secrets_masker` is removed in Airflow 3.0
--> AIR301_names_fix.py:114:1
|
113 | # airflow.utils.log
114 | secrets_masker
| ^^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^^
|
= help: Use `secrets_masker` from `airflow.sdk.execution_time` instead.
help: Use `secrets_masker` from `airflow.sdk.execution_time` instead.
Unsafe fix
108 108 |

View File

@@ -1,16 +1,17 @@
---
source: crates/ruff_linter/src/rules/airflow/mod.rs
---
AIR301_provider_names_fix.py:11:1: AIR301 [*] `airflow.providers.amazon.aws.auth_manager.avp.entities.AvpEntities.DATASET` is removed in Airflow 3.0
AIR301 [*] `airflow.providers.amazon.aws.auth_manager.avp.entities.AvpEntities.DATASET` is removed in Airflow 3.0
--> AIR301_provider_names_fix.py:11:1
|
9 | from airflow.security.permissions import RESOURCE_DATASET
10 |
11 | AvpEntities.DATASET
| ^^^^^^^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^^^^^^^
12 |
13 | # airflow.providers.openlineage.utils.utils
|
= help: Use `AvpEntities.ASSET` from `airflow.providers.amazon.aws.auth_manager.avp.entities` instead.
help: Use `AvpEntities.ASSET` from `airflow.providers.amazon.aws.auth_manager.avp.entities` instead.
Safe fix
8 8 | from airflow.secrets.local_filesystem import load_connections
@@ -22,14 +23,15 @@ AIR301_provider_names_fix.py:11:1: AIR301 [*] `airflow.providers.amazon.aws.auth
13 13 | # airflow.providers.openlineage.utils.utils
14 14 | DatasetInfo()
AIR301_provider_names_fix.py:14:1: AIR301 [*] `airflow.providers.openlineage.utils.utils.DatasetInfo` is removed in Airflow 3.0
AIR301 [*] `airflow.providers.openlineage.utils.utils.DatasetInfo` is removed in Airflow 3.0
--> AIR301_provider_names_fix.py:14:1
|
13 | # airflow.providers.openlineage.utils.utils
14 | DatasetInfo()
| ^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^
15 | translate_airflow_dataset()
|
= help: Use `AssetInfo` from `airflow.providers.openlineage.utils.utils` instead.
help: Use `AssetInfo` from `airflow.providers.openlineage.utils.utils` instead.
Safe fix
4 4 | from airflow.providers.openlineage.utils.utils import (
@@ -49,16 +51,17 @@ AIR301_provider_names_fix.py:14:1: AIR301 [*] `airflow.providers.openlineage.uti
16 17 |
17 18 | # airflow.secrets.local_filesystem
AIR301_provider_names_fix.py:15:1: AIR301 [*] `airflow.providers.openlineage.utils.utils.translate_airflow_dataset` is removed in Airflow 3.0
AIR301 [*] `airflow.providers.openlineage.utils.utils.translate_airflow_dataset` is removed in Airflow 3.0
--> AIR301_provider_names_fix.py:15:1
|
13 | # airflow.providers.openlineage.utils.utils
14 | DatasetInfo()
15 | translate_airflow_dataset()
| ^^^^^^^^^^^^^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^^^^^^^^^^^^^
16 |
17 | # airflow.secrets.local_filesystem
|
= help: Use `translate_airflow_asset` from `airflow.providers.openlineage.utils.utils` instead.
help: Use `translate_airflow_asset` from `airflow.providers.openlineage.utils.utils` instead.
Safe fix
4 4 | from airflow.providers.openlineage.utils.utils import (
@@ -78,15 +81,16 @@ AIR301_provider_names_fix.py:15:1: AIR301 [*] `airflow.providers.openlineage.uti
17 18 | # airflow.secrets.local_filesystem
18 19 | load_connections()
AIR301_provider_names_fix.py:18:1: AIR301 [*] `airflow.secrets.local_filesystem.load_connections` is removed in Airflow 3.0
AIR301 [*] `airflow.secrets.local_filesystem.load_connections` is removed in Airflow 3.0
--> AIR301_provider_names_fix.py:18:1
|
17 | # airflow.secrets.local_filesystem
18 | load_connections()
| ^^^^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^^^^
19 |
20 | # airflow.security.permissions
|
= help: Use `load_connections_dict` from `airflow.secrets.local_filesystem` instead.
help: Use `load_connections_dict` from `airflow.secrets.local_filesystem` instead.
Safe fix
5 5 | DatasetInfo,
@@ -107,15 +111,16 @@ AIR301_provider_names_fix.py:18:1: AIR301 [*] `airflow.secrets.local_filesystem.
20 20 | # airflow.security.permissions
21 21 | RESOURCE_DATASET
AIR301_provider_names_fix.py:21:1: AIR301 [*] `airflow.security.permissions.RESOURCE_DATASET` is removed in Airflow 3.0
AIR301 [*] `airflow.security.permissions.RESOURCE_DATASET` is removed in Airflow 3.0
--> AIR301_provider_names_fix.py:21:1
|
20 | # airflow.security.permissions
21 | RESOURCE_DATASET
| ^^^^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^^^^
22 |
23 | from airflow.providers.amazon.aws.datasets.s3 import (
|
= help: Use `RESOURCE_ASSET` from `airflow.security.permissions` instead.
help: Use `RESOURCE_ASSET` from `airflow.security.permissions` instead.
Safe fix
6 6 | translate_airflow_dataset,
@@ -136,15 +141,16 @@ AIR301_provider_names_fix.py:21:1: AIR301 [*] `airflow.security.permissions.RESO
23 23 | from airflow.providers.amazon.aws.datasets.s3 import (
24 24 | convert_dataset_to_openlineage as s3_convert_dataset_to_openlineage,
AIR301_provider_names_fix.py:28:1: AIR301 [*] `airflow.providers.amazon.aws.datasets.s3.create_dataset` is removed in Airflow 3.0
AIR301 [*] `airflow.providers.amazon.aws.datasets.s3.create_dataset` is removed in Airflow 3.0
--> AIR301_provider_names_fix.py:28:1
|
26 | from airflow.providers.amazon.aws.datasets.s3 import create_dataset as s3_create_dataset
27 |
28 | s3_create_dataset()
| ^^^^^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^^^^^
29 | s3_convert_dataset_to_openlineage()
|
= help: Use `create_asset` from `airflow.providers.amazon.aws.assets.s3` instead.
help: Use `create_asset` from `airflow.providers.amazon.aws.assets.s3` instead.
Safe fix
24 24 | convert_dataset_to_openlineage as s3_convert_dataset_to_openlineage,
@@ -158,15 +164,16 @@ AIR301_provider_names_fix.py:28:1: AIR301 [*] `airflow.providers.amazon.aws.data
30 31 |
31 32 | from airflow.providers.common.io.dataset.file import (
AIR301_provider_names_fix.py:29:1: AIR301 [*] `airflow.providers.amazon.aws.datasets.s3.convert_dataset_to_openlineage` is removed in Airflow 3.0
AIR301 [*] `airflow.providers.amazon.aws.datasets.s3.convert_dataset_to_openlineage` is removed in Airflow 3.0
--> AIR301_provider_names_fix.py:29:1
|
28 | s3_create_dataset()
29 | s3_convert_dataset_to_openlineage()
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
30 |
31 | from airflow.providers.common.io.dataset.file import (
|
= help: Use `convert_asset_to_openlineage` from `airflow.providers.amazon.aws.assets.s3` instead.
help: Use `convert_asset_to_openlineage` from `airflow.providers.amazon.aws.assets.s3` instead.
Safe fix
24 24 | convert_dataset_to_openlineage as s3_convert_dataset_to_openlineage,
@@ -181,16 +188,17 @@ AIR301_provider_names_fix.py:29:1: AIR301 [*] `airflow.providers.amazon.aws.data
31 32 | from airflow.providers.common.io.dataset.file import (
32 33 | convert_dataset_to_openlineage as io_convert_dataset_to_openlineage,
AIR301_provider_names_fix.py:45:1: AIR301 [*] `airflow.providers.google.datasets.bigquery.create_dataset` is removed in Airflow 3.0
AIR301 [*] `airflow.providers.google.datasets.bigquery.create_dataset` is removed in Airflow 3.0
--> AIR301_provider_names_fix.py:45:1
|
43 | )
44 |
45 | bigquery_create_dataset()
| ^^^^^^^^^^^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^^^^^^^^^^^
46 |
47 | # airflow.providers.google.datasets.gcs
|
= help: Use `create_asset` from `airflow.providers.google.assets.bigquery` instead.
help: Use `create_asset` from `airflow.providers.google.assets.bigquery` instead.
Safe fix
41 41 | from airflow.providers.google.datasets.bigquery import (
@@ -204,15 +212,16 @@ AIR301_provider_names_fix.py:45:1: AIR301 [*] `airflow.providers.google.datasets
47 48 | # airflow.providers.google.datasets.gcs
48 49 | from airflow.providers.google.datasets.gcs import (
AIR301_provider_names_fix.py:53:1: AIR301 [*] `airflow.providers.google.datasets.gcs.create_dataset` is removed in Airflow 3.0
AIR301 [*] `airflow.providers.google.datasets.gcs.create_dataset` is removed in Airflow 3.0
--> AIR301_provider_names_fix.py:53:1
|
51 | from airflow.providers.google.datasets.gcs import create_dataset as gcs_create_dataset
52 |
53 | gcs_create_dataset()
| ^^^^^^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^^^^^^
54 | gcs_convert_dataset_to_openlineage()
|
= help: Use `create_asset` from `airflow.providers.google.assets.gcs` instead.
help: Use `create_asset` from `airflow.providers.google.assets.gcs` instead.
Safe fix
49 49 | convert_dataset_to_openlineage as gcs_convert_dataset_to_openlineage,
@@ -224,13 +233,14 @@ AIR301_provider_names_fix.py:53:1: AIR301 [*] `airflow.providers.google.datasets
54 |+create_asset()
54 55 | gcs_convert_dataset_to_openlineage()
AIR301_provider_names_fix.py:54:1: AIR301 [*] `airflow.providers.google.datasets.gcs.convert_dataset_to_openlineage` is removed in Airflow 3.0
AIR301 [*] `airflow.providers.google.datasets.gcs.convert_dataset_to_openlineage` is removed in Airflow 3.0
--> AIR301_provider_names_fix.py:54:1
|
53 | gcs_create_dataset()
54 | gcs_convert_dataset_to_openlineage()
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ AIR301
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: Use `convert_asset_to_openlineage` from `airflow.providers.google.assets.gcs` instead.
help: Use `convert_asset_to_openlineage` from `airflow.providers.google.assets.gcs` instead.
Safe fix
49 49 | convert_dataset_to_openlineage as gcs_convert_dataset_to_openlineage,

View File

@@ -1,15 +1,16 @@
---
source: crates/ruff_linter/src/rules/airflow/mod.rs
---
AIR302_amazon.py:14:1: AIR302 [*] `airflow.hooks.S3_hook.S3Hook` is moved into `amazon` provider in Airflow 3.0;
AIR302 [*] `airflow.hooks.S3_hook.S3Hook` is moved into `amazon` provider in Airflow 3.0;
--> AIR302_amazon.py:14:1
|
12 | from airflow.sensors.s3_key_sensor import S3KeySensor
13 |
14 | S3Hook()
| ^^^^^^ AIR302
| ^^^^^^
15 | provide_bucket_name()
|
= help: Install `apache-airflow-providers-amazon>=1.0.0` and use `S3Hook` from `airflow.providers.amazon.aws.hooks.s3` instead.
help: Install `apache-airflow-providers-amazon>=1.0.0` and use `S3Hook` from `airflow.providers.amazon.aws.hooks.s3` instead.
Unsafe fix
1 1 | from __future__ import annotations
@@ -28,15 +29,16 @@ AIR302_amazon.py:14:1: AIR302 [*] `airflow.hooks.S3_hook.S3Hook` is moved into `
14 14 | S3Hook()
15 15 | provide_bucket_name()
AIR302_amazon.py:15:1: AIR302 [*] `airflow.hooks.S3_hook.provide_bucket_name` is moved into `amazon` provider in Airflow 3.0;
AIR302 [*] `airflow.hooks.S3_hook.provide_bucket_name` is moved into `amazon` provider in Airflow 3.0;
--> AIR302_amazon.py:15:1
|
14 | S3Hook()
15 | provide_bucket_name()
| ^^^^^^^^^^^^^^^^^^^ AIR302
| ^^^^^^^^^^^^^^^^^^^
16 |
17 | GCSToS3Operator()
|
= help: Install `apache-airflow-providers-amazon>=1.0.0` and use `provide_bucket_name` from `airflow.providers.amazon.aws.hooks.s3` instead.
help: Install `apache-airflow-providers-amazon>=1.0.0` and use `provide_bucket_name` from `airflow.providers.amazon.aws.hooks.s3` instead.
Unsafe fix
2 2 |
@@ -55,16 +57,17 @@ AIR302_amazon.py:15:1: AIR302 [*] `airflow.hooks.S3_hook.provide_bucket_name` is
14 14 | S3Hook()
15 15 | provide_bucket_name()
AIR302_amazon.py:17:1: AIR302 [*] `airflow.operators.gcs_to_s3.GCSToS3Operator` is moved into `amazon` provider in Airflow 3.0;
AIR302 [*] `airflow.operators.gcs_to_s3.GCSToS3Operator` is moved into `amazon` provider in Airflow 3.0;
--> AIR302_amazon.py:17:1
|
15 | provide_bucket_name()
16 |
17 | GCSToS3Operator()
| ^^^^^^^^^^^^^^^ AIR302
| ^^^^^^^^^^^^^^^
18 | GoogleApiToS3Operator()
19 | RedshiftToS3Operator()
|
= help: Install `apache-airflow-providers-amazon>=1.0.0` and use `GCSToS3Operator` from `airflow.providers.amazon.aws.transfers.gcs_to_s3` instead.
help: Install `apache-airflow-providers-amazon>=1.0.0` and use `GCSToS3Operator` from `airflow.providers.amazon.aws.transfers.gcs_to_s3` instead.
Unsafe fix
4 4 | S3Hook,
@@ -81,15 +84,16 @@ AIR302_amazon.py:17:1: AIR302 [*] `airflow.operators.gcs_to_s3.GCSToS3Operator`
14 14 | S3Hook()
15 15 | provide_bucket_name()
AIR302_amazon.py:18:1: AIR302 [*] `airflow.operators.google_api_to_s3_transfer.GoogleApiToS3Operator` is moved into `amazon` provider in Airflow 3.0;
AIR302 [*] `airflow.operators.google_api_to_s3_transfer.GoogleApiToS3Operator` is moved into `amazon` provider in Airflow 3.0;
--> AIR302_amazon.py:18:1
|
17 | GCSToS3Operator()
18 | GoogleApiToS3Operator()
| ^^^^^^^^^^^^^^^^^^^^^ AIR302
| ^^^^^^^^^^^^^^^^^^^^^
19 | RedshiftToS3Operator()
20 | S3FileTransformOperator()
|
= help: Install `apache-airflow-providers-amazon>=1.0.0` and use `GoogleApiToS3Operator` from `airflow.providers.amazon.aws.transfers.google_api_to_s3` instead.
help: Install `apache-airflow-providers-amazon>=1.0.0` and use `GoogleApiToS3Operator` from `airflow.providers.amazon.aws.transfers.google_api_to_s3` instead.
Unsafe fix
5 5 | provide_bucket_name,
@@ -105,16 +109,17 @@ AIR302_amazon.py:18:1: AIR302 [*] `airflow.operators.google_api_to_s3_transfer.G
14 14 | S3Hook()
15 15 | provide_bucket_name()
AIR302_amazon.py:19:1: AIR302 [*] `airflow.operators.redshift_to_s3_operator.RedshiftToS3Operator` is moved into `amazon` provider in Airflow 3.0;
AIR302 [*] `airflow.operators.redshift_to_s3_operator.RedshiftToS3Operator` is moved into `amazon` provider in Airflow 3.0;
--> AIR302_amazon.py:19:1
|
17 | GCSToS3Operator()
18 | GoogleApiToS3Operator()
19 | RedshiftToS3Operator()
| ^^^^^^^^^^^^^^^^^^^^ AIR302
| ^^^^^^^^^^^^^^^^^^^^
20 | S3FileTransformOperator()
21 | S3ToRedshiftOperator()
|
= help: Install `apache-airflow-providers-amazon>=1.0.0` and use `RedshiftToS3Operator` from `airflow.providers.amazon.aws.transfers.redshift_to_s3` instead.
help: Install `apache-airflow-providers-amazon>=1.0.0` and use `RedshiftToS3Operator` from `airflow.providers.amazon.aws.transfers.redshift_to_s3` instead.
Unsafe fix
6 6 | )
@@ -129,16 +134,17 @@ AIR302_amazon.py:19:1: AIR302 [*] `airflow.operators.redshift_to_s3_operator.Red
14 14 | S3Hook()
15 15 | provide_bucket_name()
AIR302_amazon.py:20:1: AIR302 [*] `airflow.operators.s3_file_transform_operator.S3FileTransformOperator` is moved into `amazon` provider in Airflow 3.0;
AIR302 [*] `airflow.operators.s3_file_transform_operator.S3FileTransformOperator` is moved into `amazon` provider in Airflow 3.0;
--> AIR302_amazon.py:20:1
|
18 | GoogleApiToS3Operator()
19 | RedshiftToS3Operator()
20 | S3FileTransformOperator()
| ^^^^^^^^^^^^^^^^^^^^^^^ AIR302
| ^^^^^^^^^^^^^^^^^^^^^^^
21 | S3ToRedshiftOperator()
22 | S3KeySensor()
|
= help: Install `apache-airflow-providers-amazon>=3.0.0` and use `S3FileTransformOperator` from `airflow.providers.amazon.aws.operators.s3` instead.
help: Install `apache-airflow-providers-amazon>=3.0.0` and use `S3FileTransformOperator` from `airflow.providers.amazon.aws.operators.s3` instead.
Unsafe fix
7 7 | from airflow.operators.gcs_to_s3 import GCSToS3Operator
@@ -152,15 +158,16 @@ AIR302_amazon.py:20:1: AIR302 [*] `airflow.operators.s3_file_transform_operator.
14 14 | S3Hook()
15 15 | provide_bucket_name()
AIR302_amazon.py:21:1: AIR302 [*] `airflow.operators.s3_to_redshift_operator.S3ToRedshiftOperator` is moved into `amazon` provider in Airflow 3.0;
AIR302 [*] `airflow.operators.s3_to_redshift_operator.S3ToRedshiftOperator` is moved into `amazon` provider in Airflow 3.0;
--> AIR302_amazon.py:21:1
|
19 | RedshiftToS3Operator()
20 | S3FileTransformOperator()
21 | S3ToRedshiftOperator()
| ^^^^^^^^^^^^^^^^^^^^ AIR302
| ^^^^^^^^^^^^^^^^^^^^
22 | S3KeySensor()
|
= help: Install `apache-airflow-providers-amazon>=1.0.0` and use `S3ToRedshiftOperator` from `airflow.providers.amazon.aws.transfers.s3_to_redshift` instead.
help: Install `apache-airflow-providers-amazon>=1.0.0` and use `S3ToRedshiftOperator` from `airflow.providers.amazon.aws.transfers.s3_to_redshift` instead.
Unsafe fix
8 8 | from airflow.operators.google_api_to_s3_transfer import GoogleApiToS3Operator
@@ -173,16 +180,17 @@ AIR302_amazon.py:21:1: AIR302 [*] `airflow.operators.s3_to_redshift_operator.S3T
14 14 | S3Hook()
15 15 | provide_bucket_name()
AIR302_amazon.py:22:1: AIR302 [*] `airflow.sensors.s3_key_sensor.S3KeySensor` is moved into `amazon` provider in Airflow 3.0;
AIR302 [*] `airflow.sensors.s3_key_sensor.S3KeySensor` is moved into `amazon` provider in Airflow 3.0;
--> AIR302_amazon.py:22:1
|
20 | S3FileTransformOperator()
21 | S3ToRedshiftOperator()
22 | S3KeySensor()
| ^^^^^^^^^^^ AIR302
| ^^^^^^^^^^^
23 |
24 | from airflow.operators.google_api_to_s3_transfer import GoogleApiToS3Transfer
|
= help: Install `apache-airflow-providers-amazon>=1.0.0` and use `S3KeySensor` from `airflow.providers.amazon.aws.sensors.s3` instead.
help: Install `apache-airflow-providers-amazon>=1.0.0` and use `S3KeySensor` from `airflow.providers.amazon.aws.sensors.s3` instead.
Unsafe fix
9 9 | from airflow.operators.redshift_to_s3_operator import RedshiftToS3Operator
@@ -194,16 +202,17 @@ AIR302_amazon.py:22:1: AIR302 [*] `airflow.sensors.s3_key_sensor.S3KeySensor` is
14 14 | S3Hook()
15 15 | provide_bucket_name()
AIR302_amazon.py:26:1: AIR302 [*] `airflow.operators.google_api_to_s3_transfer.GoogleApiToS3Transfer` is moved into `amazon` provider in Airflow 3.0;
AIR302 [*] `airflow.operators.google_api_to_s3_transfer.GoogleApiToS3Transfer` is moved into `amazon` provider in Airflow 3.0;
--> AIR302_amazon.py:26:1
|
24 | from airflow.operators.google_api_to_s3_transfer import GoogleApiToS3Transfer
25 |
26 | GoogleApiToS3Transfer()
| ^^^^^^^^^^^^^^^^^^^^^ AIR302
| ^^^^^^^^^^^^^^^^^^^^^
27 |
28 | from airflow.operators.redshift_to_s3_operator import RedshiftToS3Transfer
|
= help: Install `apache-airflow-providers-amazon>=1.0.0` and use `GoogleApiToS3Operator` from `airflow.providers.amazon.aws.transfers.google_api_to_s3` instead.
help: Install `apache-airflow-providers-amazon>=1.0.0` and use `GoogleApiToS3Operator` from `airflow.providers.amazon.aws.transfers.google_api_to_s3` instead.
Unsafe fix
22 22 | S3KeySensor()
@@ -214,16 +223,17 @@ AIR302_amazon.py:26:1: AIR302 [*] `airflow.operators.google_api_to_s3_transfer.G
26 27 | GoogleApiToS3Transfer()
27 28 |
AIR302_amazon.py:30:1: AIR302 [*] `airflow.operators.redshift_to_s3_operator.RedshiftToS3Transfer` is moved into `amazon` provider in Airflow 3.0;
AIR302 [*] `airflow.operators.redshift_to_s3_operator.RedshiftToS3Transfer` is moved into `amazon` provider in Airflow 3.0;
--> AIR302_amazon.py:30:1
|
28 | from airflow.operators.redshift_to_s3_operator import RedshiftToS3Transfer
29 |
30 | RedshiftToS3Transfer()
| ^^^^^^^^^^^^^^^^^^^^ AIR302
| ^^^^^^^^^^^^^^^^^^^^
31 |
32 | from airflow.operators.s3_to_redshift_operator import S3ToRedshiftTransfer
|
= help: Install `apache-airflow-providers-amazon>=1.0.0` and use `RedshiftToS3Operator` from `airflow.providers.amazon.aws.transfers.redshift_to_s3` instead.
help: Install `apache-airflow-providers-amazon>=1.0.0` and use `RedshiftToS3Operator` from `airflow.providers.amazon.aws.transfers.redshift_to_s3` instead.
Unsafe fix
26 26 | GoogleApiToS3Transfer()
@@ -234,14 +244,15 @@ AIR302_amazon.py:30:1: AIR302 [*] `airflow.operators.redshift_to_s3_operator.Red
30 31 | RedshiftToS3Transfer()
31 32 |
AIR302_amazon.py:34:1: AIR302 [*] `airflow.operators.s3_to_redshift_operator.S3ToRedshiftTransfer` is moved into `amazon` provider in Airflow 3.0;
AIR302 [*] `airflow.operators.s3_to_redshift_operator.S3ToRedshiftTransfer` is moved into `amazon` provider in Airflow 3.0;
--> AIR302_amazon.py:34:1
|
32 | from airflow.operators.s3_to_redshift_operator import S3ToRedshiftTransfer
33 |
34 | S3ToRedshiftTransfer()
| ^^^^^^^^^^^^^^^^^^^^ AIR302
| ^^^^^^^^^^^^^^^^^^^^
|
= help: Install `apache-airflow-providers-amazon>=1.0.0` and use `S3ToRedshiftOperator` from `airflow.providers.amazon.aws.transfers.s3_to_redshift` instead.
help: Install `apache-airflow-providers-amazon>=1.0.0` and use `S3ToRedshiftOperator` from `airflow.providers.amazon.aws.transfers.s3_to_redshift` instead.
Unsafe fix
30 30 | RedshiftToS3Transfer()

View File

@@ -1,16 +1,17 @@
---
source: crates/ruff_linter/src/rules/airflow/mod.rs
---
AIR302_celery.py:9:1: AIR302 [*] `airflow.config_templates.default_celery.DEFAULT_CELERY_CONFIG` is moved into `celery` provider in Airflow 3.0;
AIR302 [*] `airflow.config_templates.default_celery.DEFAULT_CELERY_CONFIG` is moved into `celery` provider in Airflow 3.0;
--> AIR302_celery.py:9:1
|
7 | )
8 |
9 | DEFAULT_CELERY_CONFIG
| ^^^^^^^^^^^^^^^^^^^^^ AIR302
| ^^^^^^^^^^^^^^^^^^^^^
10 |
11 | app
|
= help: Install `apache-airflow-providers-celery>=3.3.0` and use `DEFAULT_CELERY_CONFIG` from `airflow.providers.celery.executors.default_celery` instead.
help: Install `apache-airflow-providers-celery>=3.3.0` and use `DEFAULT_CELERY_CONFIG` from `airflow.providers.celery.executors.default_celery` instead.
Unsafe fix
1 1 | from __future__ import annotations
@@ -25,15 +26,16 @@ AIR302_celery.py:9:1: AIR302 [*] `airflow.config_templates.default_celery.DEFAUL
9 9 | DEFAULT_CELERY_CONFIG
10 10 |
AIR302_celery.py:11:1: AIR302 [*] `airflow.executors.celery_executor.app` is moved into `celery` provider in Airflow 3.0;
AIR302 [*] `airflow.executors.celery_executor.app` is moved into `celery` provider in Airflow 3.0;
--> AIR302_celery.py:11:1
|
9 | DEFAULT_CELERY_CONFIG
10 |
11 | app
| ^^^ AIR302
| ^^^
12 | CeleryExecutor()
|
= help: Install `apache-airflow-providers-celery>=3.3.0` and use `app` from `airflow.providers.celery.executors.celery_executor_utils` instead.
help: Install `apache-airflow-providers-celery>=3.3.0` and use `app` from `airflow.providers.celery.executors.celery_executor_utils` instead.
Unsafe fix
3 3 | from airflow.config_templates.default_celery import DEFAULT_CELERY_CONFIG
@@ -46,13 +48,14 @@ AIR302_celery.py:11:1: AIR302 [*] `airflow.executors.celery_executor.app` is mov
9 9 | DEFAULT_CELERY_CONFIG
10 10 |
AIR302_celery.py:12:1: AIR302 [*] `airflow.executors.celery_executor.CeleryExecutor` is moved into `celery` provider in Airflow 3.0;
AIR302 [*] `airflow.executors.celery_executor.CeleryExecutor` is moved into `celery` provider in Airflow 3.0;
--> AIR302_celery.py:12:1
|
11 | app
12 | CeleryExecutor()
| ^^^^^^^^^^^^^^ AIR302
| ^^^^^^^^^^^^^^
|
= help: Install `apache-airflow-providers-celery>=3.3.0` and use `CeleryExecutor` from `airflow.providers.celery.executors.celery_executor` instead.
help: Install `apache-airflow-providers-celery>=3.3.0` and use `CeleryExecutor` from `airflow.providers.celery.executors.celery_executor` instead.
Unsafe fix
2 2 |

View File

@@ -1,15 +1,16 @@
---
source: crates/ruff_linter/src/rules/airflow/mod.rs
---
AIR302_common_sql.py:8:1: AIR302 [*] `airflow.hooks.dbapi.ConnectorProtocol` is moved into `common-sql` provider in Airflow 3.0;
AIR302 [*] `airflow.hooks.dbapi.ConnectorProtocol` is moved into `common-sql` provider in Airflow 3.0;
--> AIR302_common_sql.py:8:1
|
6 | )
7 |
8 | ConnectorProtocol()
| ^^^^^^^^^^^^^^^^^ AIR302
| ^^^^^^^^^^^^^^^^^
9 | DbApiHook()
|
= help: Install `apache-airflow-providers-common-sql>=1.0.0` and use `ConnectorProtocol` from `airflow.providers.common.sql.hooks.sql` instead.
help: Install `apache-airflow-providers-common-sql>=1.0.0` and use `ConnectorProtocol` from `airflow.providers.common.sql.hooks.sql` instead.
Unsafe fix
1 1 | from __future__ import annotations
@@ -23,15 +24,16 @@ AIR302_common_sql.py:8:1: AIR302 [*] `airflow.hooks.dbapi.ConnectorProtocol` is
8 8 | ConnectorProtocol()
9 9 | DbApiHook()
AIR302_common_sql.py:9:1: AIR302 [*] `airflow.hooks.dbapi.DbApiHook` is moved into `common-sql` provider in Airflow 3.0;
AIR302 [*] `airflow.hooks.dbapi.DbApiHook` is moved into `common-sql` provider in Airflow 3.0;
--> AIR302_common_sql.py:9:1
|
8 | ConnectorProtocol()
9 | DbApiHook()
| ^^^^^^^^^ AIR302
| ^^^^^^^^^
10 |
11 | from airflow.hooks.dbapi_hook import DbApiHook
|
= help: Install `apache-airflow-providers-common-sql>=1.0.0` and use `DbApiHook` from `airflow.providers.common.sql.hooks.sql` instead.
help: Install `apache-airflow-providers-common-sql>=1.0.0` and use `DbApiHook` from `airflow.providers.common.sql.hooks.sql` instead.
Unsafe fix
2 2 |
@@ -44,15 +46,16 @@ AIR302_common_sql.py:9:1: AIR302 [*] `airflow.hooks.dbapi.DbApiHook` is moved in
8 8 | ConnectorProtocol()
9 9 | DbApiHook()
AIR302_common_sql.py:14:1: AIR302 [*] `airflow.hooks.dbapi_hook.DbApiHook` is moved into `common-sql` provider in Airflow 3.0;
AIR302 [*] `airflow.hooks.dbapi_hook.DbApiHook` is moved into `common-sql` provider in Airflow 3.0;
--> AIR302_common_sql.py:14:1
|
12 | from airflow.operators.check_operator import SQLCheckOperator
13 |
14 | DbApiHook()
| ^^^^^^^^^ AIR302
| ^^^^^^^^^
15 | SQLCheckOperator()
|
= help: Install `apache-airflow-providers-common-sql>=1.0.0` and use `DbApiHook` from `airflow.providers.common.sql.hooks.sql` instead.
help: Install `apache-airflow-providers-common-sql>=1.0.0` and use `DbApiHook` from `airflow.providers.common.sql.hooks.sql` instead.
Unsafe fix
8 8 | ConnectorProtocol()
@@ -65,13 +68,14 @@ AIR302_common_sql.py:14:1: AIR302 [*] `airflow.hooks.dbapi_hook.DbApiHook` is mo
14 14 | DbApiHook()
15 15 | SQLCheckOperator()
AIR302_common_sql.py:15:1: AIR302 [*] `airflow.operators.check_operator.SQLCheckOperator` is moved into `common-sql` provider in Airflow 3.0;
AIR302 [*] `airflow.operators.check_operator.SQLCheckOperator` is moved into `common-sql` provider in Airflow 3.0;
--> AIR302_common_sql.py:15:1
|
14 | DbApiHook()
15 | SQLCheckOperator()
| ^^^^^^^^^^^^^^^^ AIR302
| ^^^^^^^^^^^^^^^^
|
= help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `SQLCheckOperator` from `airflow.providers.common.sql.operators.sql` instead.
help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `SQLCheckOperator` from `airflow.providers.common.sql.operators.sql` instead.
Unsafe fix
9 9 | DbApiHook()
@@ -83,15 +87,16 @@ AIR302_common_sql.py:15:1: AIR302 [*] `airflow.operators.check_operator.SQLCheck
14 14 | DbApiHook()
15 15 | SQLCheckOperator()
AIR302_common_sql.py:21:1: AIR302 [*] `airflow.operators.sql.SQLCheckOperator` is moved into `common-sql` provider in Airflow 3.0;
AIR302 [*] `airflow.operators.sql.SQLCheckOperator` is moved into `common-sql` provider in Airflow 3.0;
--> AIR302_common_sql.py:21:1
|
19 | from airflow.operators.sql import SQLCheckOperator
20 |
21 | SQLCheckOperator()
| ^^^^^^^^^^^^^^^^ AIR302
| ^^^^^^^^^^^^^^^^
22 | CheckOperator()
|
= help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `SQLCheckOperator` from `airflow.providers.common.sql.operators.sql` instead.
help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `SQLCheckOperator` from `airflow.providers.common.sql.operators.sql` instead.
Unsafe fix
16 16 |
@@ -103,13 +108,14 @@ AIR302_common_sql.py:21:1: AIR302 [*] `airflow.operators.sql.SQLCheckOperator` i
21 21 | SQLCheckOperator()
22 22 | CheckOperator()
AIR302_common_sql.py:22:1: AIR302 [*] `airflow.operators.check_operator.CheckOperator` is moved into `common-sql` provider in Airflow 3.0;
AIR302 [*] `airflow.operators.check_operator.CheckOperator` is moved into `common-sql` provider in Airflow 3.0;
--> AIR302_common_sql.py:22:1
|
21 | SQLCheckOperator()
22 | CheckOperator()
| ^^^^^^^^^^^^^ AIR302
| ^^^^^^^^^^^^^
|
= help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `SQLCheckOperator` from `airflow.providers.common.sql.operators.sql` instead.
help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `SQLCheckOperator` from `airflow.providers.common.sql.operators.sql` instead.
Unsafe fix
17 17 |
@@ -120,14 +126,15 @@ AIR302_common_sql.py:22:1: AIR302 [*] `airflow.operators.check_operator.CheckOpe
21 22 | SQLCheckOperator()
22 23 | CheckOperator()
AIR302_common_sql.py:27:1: AIR302 [*] `airflow.operators.druid_check_operator.CheckOperator` is moved into `common-sql` provider in Airflow 3.0;
AIR302 [*] `airflow.operators.druid_check_operator.CheckOperator` is moved into `common-sql` provider in Airflow 3.0;
--> AIR302_common_sql.py:27:1
|
25 | from airflow.operators.druid_check_operator import CheckOperator
26 |
27 | CheckOperator()
| ^^^^^^^^^^^^^ AIR302
| ^^^^^^^^^^^^^
|
= help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `SQLCheckOperator` from `airflow.providers.common.sql.operators.sql` instead.
help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `SQLCheckOperator` from `airflow.providers.common.sql.operators.sql` instead.
Unsafe fix
23 23 |
@@ -138,14 +145,15 @@ AIR302_common_sql.py:27:1: AIR302 [*] `airflow.operators.druid_check_operator.Ch
27 28 | CheckOperator()
28 29 |
AIR302_common_sql.py:32:1: AIR302 [*] `airflow.operators.presto_check_operator.CheckOperator` is moved into `common-sql` provider in Airflow 3.0;
AIR302 [*] `airflow.operators.presto_check_operator.CheckOperator` is moved into `common-sql` provider in Airflow 3.0;
--> AIR302_common_sql.py:32:1
|
30 | from airflow.operators.presto_check_operator import CheckOperator
31 |
32 | CheckOperator()
| ^^^^^^^^^^^^^ AIR302
| ^^^^^^^^^^^^^
|
= help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `SQLCheckOperator` from `airflow.providers.common.sql.operators.sql` instead.
help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `SQLCheckOperator` from `airflow.providers.common.sql.operators.sql` instead.
Unsafe fix
28 28 |
@@ -156,16 +164,17 @@ AIR302_common_sql.py:32:1: AIR302 [*] `airflow.operators.presto_check_operator.C
32 33 | CheckOperator()
33 34 |
AIR302_common_sql.py:42:1: AIR302 [*] `airflow.operators.druid_check_operator.DruidCheckOperator` is moved into `common-sql` provider in Airflow 3.0;
AIR302 [*] `airflow.operators.druid_check_operator.DruidCheckOperator` is moved into `common-sql` provider in Airflow 3.0;
--> AIR302_common_sql.py:42:1
|
40 | from airflow.operators.presto_check_operator import PrestoCheckOperator
41 |
42 | DruidCheckOperator()
| ^^^^^^^^^^^^^^^^^^ AIR302
| ^^^^^^^^^^^^^^^^^^
43 | PrestoCheckOperator()
44 | IntervalCheckOperator()
|
= help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `SQLCheckOperator` from `airflow.providers.common.sql.operators.sql` instead.
help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `SQLCheckOperator` from `airflow.providers.common.sql.operators.sql` instead.
Unsafe fix
38 38 | )
@@ -176,15 +185,16 @@ AIR302_common_sql.py:42:1: AIR302 [*] `airflow.operators.druid_check_operator.Dr
42 43 | DruidCheckOperator()
43 44 | PrestoCheckOperator()
AIR302_common_sql.py:43:1: AIR302 [*] `airflow.operators.presto_check_operator.PrestoCheckOperator` is moved into `common-sql` provider in Airflow 3.0;
AIR302 [*] `airflow.operators.presto_check_operator.PrestoCheckOperator` is moved into `common-sql` provider in Airflow 3.0;
--> AIR302_common_sql.py:43:1
|
42 | DruidCheckOperator()
43 | PrestoCheckOperator()
| ^^^^^^^^^^^^^^^^^^^ AIR302
| ^^^^^^^^^^^^^^^^^^^
44 | IntervalCheckOperator()
45 | SQLIntervalCheckOperator()
|
= help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `SQLCheckOperator` from `airflow.providers.common.sql.operators.sql` instead.
help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `SQLCheckOperator` from `airflow.providers.common.sql.operators.sql` instead.
Unsafe fix
38 38 | )
@@ -195,15 +205,16 @@ AIR302_common_sql.py:43:1: AIR302 [*] `airflow.operators.presto_check_operator.P
42 43 | DruidCheckOperator()
43 44 | PrestoCheckOperator()
AIR302_common_sql.py:44:1: AIR302 [*] `airflow.operators.check_operator.IntervalCheckOperator` is moved into `common-sql` provider in Airflow 3.0;
AIR302 [*] `airflow.operators.check_operator.IntervalCheckOperator` is moved into `common-sql` provider in Airflow 3.0;
--> AIR302_common_sql.py:44:1
|
42 | DruidCheckOperator()
43 | PrestoCheckOperator()
44 | IntervalCheckOperator()
| ^^^^^^^^^^^^^^^^^^^^^ AIR302
| ^^^^^^^^^^^^^^^^^^^^^
45 | SQLIntervalCheckOperator()
|
= help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `SQLIntervalCheckOperator` from `airflow.providers.common.sql.operators.sql` instead.
help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `SQLIntervalCheckOperator` from `airflow.providers.common.sql.operators.sql` instead.
Unsafe fix
34 34 |
@@ -218,14 +229,15 @@ AIR302_common_sql.py:44:1: AIR302 [*] `airflow.operators.check_operator.Interval
42 42 | DruidCheckOperator()
43 43 | PrestoCheckOperator()
AIR302_common_sql.py:45:1: AIR302 [*] `airflow.operators.check_operator.SQLIntervalCheckOperator` is moved into `common-sql` provider in Airflow 3.0;
AIR302 [*] `airflow.operators.check_operator.SQLIntervalCheckOperator` is moved into `common-sql` provider in Airflow 3.0;
--> AIR302_common_sql.py:45:1
|
43 | PrestoCheckOperator()
44 | IntervalCheckOperator()
45 | SQLIntervalCheckOperator()
| ^^^^^^^^^^^^^^^^^^^^^^^^ AIR302
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `SQLIntervalCheckOperator` from `airflow.providers.common.sql.operators.sql` instead.
help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `SQLIntervalCheckOperator` from `airflow.providers.common.sql.operators.sql` instead.
Unsafe fix
34 34 |
@@ -240,16 +252,17 @@ AIR302_common_sql.py:45:1: AIR302 [*] `airflow.operators.check_operator.SQLInter
42 42 | DruidCheckOperator()
43 43 | PrestoCheckOperator()
AIR302_common_sql.py:54:1: AIR302 [*] `airflow.operators.presto_check_operator.IntervalCheckOperator` is moved into `common-sql` provider in Airflow 3.0;
AIR302 [*] `airflow.operators.presto_check_operator.IntervalCheckOperator` is moved into `common-sql` provider in Airflow 3.0;
--> AIR302_common_sql.py:54:1
|
52 | from airflow.operators.sql import SQLIntervalCheckOperator
53 |
54 | IntervalCheckOperator()
| ^^^^^^^^^^^^^^^^^^^^^ AIR302
| ^^^^^^^^^^^^^^^^^^^^^
55 | SQLIntervalCheckOperator()
56 | PrestoIntervalCheckOperator()
|
= help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `SQLIntervalCheckOperator` from `airflow.providers.common.sql.operators.sql` instead.
help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `SQLIntervalCheckOperator` from `airflow.providers.common.sql.operators.sql` instead.
Unsafe fix
50 50 | PrestoIntervalCheckOperator,
@@ -260,14 +273,15 @@ AIR302_common_sql.py:54:1: AIR302 [*] `airflow.operators.presto_check_operator.I
54 55 | IntervalCheckOperator()
55 56 | SQLIntervalCheckOperator()
AIR302_common_sql.py:55:1: AIR302 [*] `airflow.operators.sql.SQLIntervalCheckOperator` is moved into `common-sql` provider in Airflow 3.0;
AIR302 [*] `airflow.operators.sql.SQLIntervalCheckOperator` is moved into `common-sql` provider in Airflow 3.0;
--> AIR302_common_sql.py:55:1
|
54 | IntervalCheckOperator()
55 | SQLIntervalCheckOperator()
| ^^^^^^^^^^^^^^^^^^^^^^^^ AIR302
| ^^^^^^^^^^^^^^^^^^^^^^^^
56 | PrestoIntervalCheckOperator()
|
= help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `SQLIntervalCheckOperator` from `airflow.providers.common.sql.operators.sql` instead.
help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `SQLIntervalCheckOperator` from `airflow.providers.common.sql.operators.sql` instead.
Unsafe fix
49 49 | IntervalCheckOperator,
@@ -279,14 +293,15 @@ AIR302_common_sql.py:55:1: AIR302 [*] `airflow.operators.sql.SQLIntervalCheckOpe
54 54 | IntervalCheckOperator()
55 55 | SQLIntervalCheckOperator()
AIR302_common_sql.py:56:1: AIR302 [*] `airflow.operators.presto_check_operator.PrestoIntervalCheckOperator` is moved into `common-sql` provider in Airflow 3.0;
AIR302 [*] `airflow.operators.presto_check_operator.PrestoIntervalCheckOperator` is moved into `common-sql` provider in Airflow 3.0;
--> AIR302_common_sql.py:56:1
|
54 | IntervalCheckOperator()
55 | SQLIntervalCheckOperator()
56 | PrestoIntervalCheckOperator()
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ AIR302
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `SQLIntervalCheckOperator` from `airflow.providers.common.sql.operators.sql` instead.
help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `SQLIntervalCheckOperator` from `airflow.providers.common.sql.operators.sql` instead.
Unsafe fix
50 50 | PrestoIntervalCheckOperator,
@@ -297,15 +312,16 @@ AIR302_common_sql.py:56:1: AIR302 [*] `airflow.operators.presto_check_operator.P
54 55 | IntervalCheckOperator()
55 56 | SQLIntervalCheckOperator()
AIR302_common_sql.py:64:1: AIR302 [*] `airflow.operators.check_operator.SQLThresholdCheckOperator` is moved into `common-sql` provider in Airflow 3.0;
AIR302 [*] `airflow.operators.check_operator.SQLThresholdCheckOperator` is moved into `common-sql` provider in Airflow 3.0;
--> AIR302_common_sql.py:64:1
|
62 | )
63 |
64 | SQLThresholdCheckOperator()
| ^^^^^^^^^^^^^^^^^^^^^^^^^ AIR302
| ^^^^^^^^^^^^^^^^^^^^^^^^^
65 | ThresholdCheckOperator()
|
= help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `SQLThresholdCheckOperator` from `airflow.providers.common.sql.operators.sql` instead.
help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `SQLThresholdCheckOperator` from `airflow.providers.common.sql.operators.sql` instead.
Unsafe fix
57 57 |
@@ -319,13 +335,14 @@ AIR302_common_sql.py:64:1: AIR302 [*] `airflow.operators.check_operator.SQLThres
64 64 | SQLThresholdCheckOperator()
65 65 | ThresholdCheckOperator()
AIR302_common_sql.py:65:1: AIR302 [*] `airflow.operators.check_operator.ThresholdCheckOperator` is moved into `common-sql` provider in Airflow 3.0;
AIR302 [*] `airflow.operators.check_operator.ThresholdCheckOperator` is moved into `common-sql` provider in Airflow 3.0;
--> AIR302_common_sql.py:65:1
|
64 | SQLThresholdCheckOperator()
65 | ThresholdCheckOperator()
| ^^^^^^^^^^^^^^^^^^^^^^ AIR302
| ^^^^^^^^^^^^^^^^^^^^^^
|
= help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `SQLThresholdCheckOperator` from `airflow.providers.common.sql.operators.sql` instead.
help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `SQLThresholdCheckOperator` from `airflow.providers.common.sql.operators.sql` instead.
Unsafe fix
57 57 |
@@ -339,14 +356,15 @@ AIR302_common_sql.py:65:1: AIR302 [*] `airflow.operators.check_operator.Threshol
64 64 | SQLThresholdCheckOperator()
65 65 | ThresholdCheckOperator()
AIR302_common_sql.py:70:1: AIR302 [*] `airflow.operators.sql.SQLThresholdCheckOperator` is moved into `common-sql` provider in Airflow 3.0;
AIR302 [*] `airflow.operators.sql.SQLThresholdCheckOperator` is moved into `common-sql` provider in Airflow 3.0;
--> AIR302_common_sql.py:70:1
|
68 | from airflow.operators.sql import SQLThresholdCheckOperator
69 |
70 | SQLThresholdCheckOperator()
| ^^^^^^^^^^^^^^^^^^^^^^^^^ AIR302
| ^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `SQLThresholdCheckOperator` from `airflow.providers.common.sql.operators.sql` instead.
help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `SQLThresholdCheckOperator` from `airflow.providers.common.sql.operators.sql` instead.
Unsafe fix
65 65 | ThresholdCheckOperator()
@@ -358,15 +376,16 @@ AIR302_common_sql.py:70:1: AIR302 [*] `airflow.operators.sql.SQLThresholdCheckOp
70 70 | SQLThresholdCheckOperator()
71 71 |
AIR302_common_sql.py:78:1: AIR302 [*] `airflow.operators.check_operator.SQLValueCheckOperator` is moved into `common-sql` provider in Airflow 3.0;
AIR302 [*] `airflow.operators.check_operator.SQLValueCheckOperator` is moved into `common-sql` provider in Airflow 3.0;
--> AIR302_common_sql.py:78:1
|
76 | )
77 |
78 | SQLValueCheckOperator()
| ^^^^^^^^^^^^^^^^^^^^^ AIR302
| ^^^^^^^^^^^^^^^^^^^^^
79 | ValueCheckOperator()
|
= help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `SQLValueCheckOperator` from `airflow.providers.common.sql.operators.sql` instead.
help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `SQLValueCheckOperator` from `airflow.providers.common.sql.operators.sql` instead.
Unsafe fix
71 71 |
@@ -380,13 +399,14 @@ AIR302_common_sql.py:78:1: AIR302 [*] `airflow.operators.check_operator.SQLValue
78 78 | SQLValueCheckOperator()
79 79 | ValueCheckOperator()
AIR302_common_sql.py:79:1: AIR302 [*] `airflow.operators.check_operator.ValueCheckOperator` is moved into `common-sql` provider in Airflow 3.0;
AIR302 [*] `airflow.operators.check_operator.ValueCheckOperator` is moved into `common-sql` provider in Airflow 3.0;
--> AIR302_common_sql.py:79:1
|
78 | SQLValueCheckOperator()
79 | ValueCheckOperator()
| ^^^^^^^^^^^^^^^^^^ AIR302
| ^^^^^^^^^^^^^^^^^^
|
= help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `SQLValueCheckOperator` from `airflow.providers.common.sql.operators.sql` instead.
help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `SQLValueCheckOperator` from `airflow.providers.common.sql.operators.sql` instead.
Unsafe fix
71 71 |
@@ -400,16 +420,17 @@ AIR302_common_sql.py:79:1: AIR302 [*] `airflow.operators.check_operator.ValueChe
78 78 | SQLValueCheckOperator()
79 79 | ValueCheckOperator()
AIR302_common_sql.py:88:1: AIR302 [*] `airflow.operators.sql.SQLValueCheckOperator` is moved into `common-sql` provider in Airflow 3.0;
AIR302 [*] `airflow.operators.sql.SQLValueCheckOperator` is moved into `common-sql` provider in Airflow 3.0;
--> AIR302_common_sql.py:88:1
|
86 | from airflow.operators.sql import SQLValueCheckOperator
87 |
88 | SQLValueCheckOperator()
| ^^^^^^^^^^^^^^^^^^^^^ AIR302
| ^^^^^^^^^^^^^^^^^^^^^
89 | ValueCheckOperator()
90 | PrestoValueCheckOperator()
|
= help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `SQLValueCheckOperator` from `airflow.providers.common.sql.operators.sql` instead.
help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `SQLValueCheckOperator` from `airflow.providers.common.sql.operators.sql` instead.
Unsafe fix
83 83 | PrestoValueCheckOperator,
@@ -421,14 +442,15 @@ AIR302_common_sql.py:88:1: AIR302 [*] `airflow.operators.sql.SQLValueCheckOperat
88 88 | SQLValueCheckOperator()
89 89 | ValueCheckOperator()
AIR302_common_sql.py:89:1: AIR302 [*] `airflow.operators.presto_check_operator.ValueCheckOperator` is moved into `common-sql` provider in Airflow 3.0;
AIR302 [*] `airflow.operators.presto_check_operator.ValueCheckOperator` is moved into `common-sql` provider in Airflow 3.0;
--> AIR302_common_sql.py:89:1
|
88 | SQLValueCheckOperator()
89 | ValueCheckOperator()
| ^^^^^^^^^^^^^^^^^^ AIR302
| ^^^^^^^^^^^^^^^^^^
90 | PrestoValueCheckOperator()
|
= help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `SQLValueCheckOperator` from `airflow.providers.common.sql.operators.sql` instead.
help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `SQLValueCheckOperator` from `airflow.providers.common.sql.operators.sql` instead.
Unsafe fix
84 84 | ValueCheckOperator,
@@ -439,14 +461,15 @@ AIR302_common_sql.py:89:1: AIR302 [*] `airflow.operators.presto_check_operator.V
88 89 | SQLValueCheckOperator()
89 90 | ValueCheckOperator()
AIR302_common_sql.py:90:1: AIR302 [*] `airflow.operators.presto_check_operator.PrestoValueCheckOperator` is moved into `common-sql` provider in Airflow 3.0;
AIR302 [*] `airflow.operators.presto_check_operator.PrestoValueCheckOperator` is moved into `common-sql` provider in Airflow 3.0;
--> AIR302_common_sql.py:90:1
|
88 | SQLValueCheckOperator()
89 | ValueCheckOperator()
90 | PrestoValueCheckOperator()
| ^^^^^^^^^^^^^^^^^^^^^^^^ AIR302
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `SQLValueCheckOperator` from `airflow.providers.common.sql.operators.sql` instead.
help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `SQLValueCheckOperator` from `airflow.providers.common.sql.operators.sql` instead.
Unsafe fix
84 84 | ValueCheckOperator,
@@ -457,16 +480,17 @@ AIR302_common_sql.py:90:1: AIR302 [*] `airflow.operators.presto_check_operator.P
88 89 | SQLValueCheckOperator()
89 90 | ValueCheckOperator()
AIR302_common_sql.py:102:1: AIR302 [*] `airflow.operators.sql.BaseSQLOperator` is moved into `common-sql` provider in Airflow 3.0;
AIR302 [*] `airflow.operators.sql.BaseSQLOperator` is moved into `common-sql` provider in Airflow 3.0;
--> AIR302_common_sql.py:102:1
|
100 | )
101 |
102 | BaseSQLOperator()
| ^^^^^^^^^^^^^^^ AIR302
| ^^^^^^^^^^^^^^^
103 | BranchSQLOperator()
104 | SQLTableCheckOperator()
|
= help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `BaseSQLOperator` from `airflow.providers.common.sql.operators.sql` instead.
help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `BaseSQLOperator` from `airflow.providers.common.sql.operators.sql` instead.
Unsafe fix
91 91 |
@@ -484,15 +508,16 @@ AIR302_common_sql.py:102:1: AIR302 [*] `airflow.operators.sql.BaseSQLOperator` i
102 102 | BaseSQLOperator()
103 103 | BranchSQLOperator()
AIR302_common_sql.py:103:1: AIR302 [*] `airflow.operators.sql.BranchSQLOperator` is moved into `common-sql` provider in Airflow 3.0;
AIR302 [*] `airflow.operators.sql.BranchSQLOperator` is moved into `common-sql` provider in Airflow 3.0;
--> AIR302_common_sql.py:103:1
|
102 | BaseSQLOperator()
103 | BranchSQLOperator()
| ^^^^^^^^^^^^^^^^^ AIR302
| ^^^^^^^^^^^^^^^^^
104 | SQLTableCheckOperator()
105 | SQLColumnCheckOperator()
|
= help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `BranchSQLOperator` from `airflow.providers.common.sql.operators.sql` instead.
help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `BranchSQLOperator` from `airflow.providers.common.sql.operators.sql` instead.
Unsafe fix
92 92 |
@@ -509,16 +534,17 @@ AIR302_common_sql.py:103:1: AIR302 [*] `airflow.operators.sql.BranchSQLOperator`
102 102 | BaseSQLOperator()
103 103 | BranchSQLOperator()
AIR302_common_sql.py:104:1: AIR302 [*] `airflow.operators.sql.SQLTableCheckOperator` is moved into `common-sql` provider in Airflow 3.0;
AIR302 [*] `airflow.operators.sql.SQLTableCheckOperator` is moved into `common-sql` provider in Airflow 3.0;
--> AIR302_common_sql.py:104:1
|
102 | BaseSQLOperator()
103 | BranchSQLOperator()
104 | SQLTableCheckOperator()
| ^^^^^^^^^^^^^^^^^^^^^ AIR302
| ^^^^^^^^^^^^^^^^^^^^^
105 | SQLColumnCheckOperator()
106 | _convert_to_float_if_possible()
|
= help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `SQLTableCheckOperator` from `airflow.providers.common.sql.operators.sql` instead.
help: Install `apache-airflow-providers-common-sql>=1.1.0` and use `SQLTableCheckOperator` from `airflow.providers.common.sql.operators.sql` instead.
Unsafe fix
94 94 | BaseSQLOperator,
@@ -533,16 +559,17 @@ AIR302_common_sql.py:104:1: AIR302 [*] `airflow.operators.sql.SQLTableCheckOpera
102 102 | BaseSQLOperator()
103 103 | BranchSQLOperator()
AIR302_common_sql.py:105:1: AIR302 [*] `airflow.operators.sql.SQLColumnCheckOperator` is moved into `common-sql` provider in Airflow 3.0;
AIR302 [*] `airflow.operators.sql.SQLColumnCheckOperator` is moved into `common-sql` provider in Airflow 3.0;
--> AIR302_common_sql.py:105:1
|
103 | BranchSQLOperator()
104 | SQLTableCheckOperator()
105 | SQLColumnCheckOperator()
| ^^^^^^^^^^^^^^^^^^^^^^ AIR302
| ^^^^^^^^^^^^^^^^^^^^^^
106 | _convert_to_float_if_possible()
107 | parse_boolean()
|
= help: Install `apache-airflow-providers-common-sql>=1.0.0` and use `SQLColumnCheckOperator` from `airflow.providers.common.sql.operators.sql` instead.
help: Install `apache-airflow-providers-common-sql>=1.0.0` and use `SQLColumnCheckOperator` from `airflow.providers.common.sql.operators.sql` instead.
Unsafe fix
93 93 | from airflow.operators.sql import (
@@ -558,15 +585,16 @@ AIR302_common_sql.py:105:1: AIR302 [*] `airflow.operators.sql.SQLColumnCheckOper
102 102 | BaseSQLOperator()
103 103 | BranchSQLOperator()
AIR302_common_sql.py:106:1: AIR302 [*] `airflow.operators.sql._convert_to_float_if_possible` is moved into `common-sql` provider in Airflow 3.0;
AIR302 [*] `airflow.operators.sql._convert_to_float_if_possible` is moved into `common-sql` provider in Airflow 3.0;
--> AIR302_common_sql.py:106:1
|
104 | SQLTableCheckOperator()
105 | SQLColumnCheckOperator()
106 | _convert_to_float_if_possible()
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ AIR302
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
107 | parse_boolean()
|
= help: Install `apache-airflow-providers-common-sql>=1.0.0` and use `_convert_to_float_if_possible` from `airflow.providers.common.sql.operators.sql` instead.
help: Install `apache-airflow-providers-common-sql>=1.0.0` and use `_convert_to_float_if_possible` from `airflow.providers.common.sql.operators.sql` instead.
Unsafe fix
95 95 | BranchSQLOperator,
@@ -580,14 +608,15 @@ AIR302_common_sql.py:106:1: AIR302 [*] `airflow.operators.sql._convert_to_float_
102 102 | BaseSQLOperator()
103 103 | BranchSQLOperator()
AIR302_common_sql.py:107:1: AIR302 [*] `airflow.operators.sql.parse_boolean` is moved into `common-sql` provider in Airflow 3.0;
AIR302 [*] `airflow.operators.sql.parse_boolean` is moved into `common-sql` provider in Airflow 3.0;
--> AIR302_common_sql.py:107:1
|
105 | SQLColumnCheckOperator()
106 | _convert_to_float_if_possible()
107 | parse_boolean()
| ^^^^^^^^^^^^^ AIR302
| ^^^^^^^^^^^^^
|
= help: Install `apache-airflow-providers-common-sql>=1.0.0` and use `parse_boolean` from `airflow.providers.common.sql.operators.sql` instead.
help: Install `apache-airflow-providers-common-sql>=1.0.0` and use `parse_boolean` from `airflow.providers.common.sql.operators.sql` instead.
Unsafe fix
96 96 | SQLColumnCheckOperator,
@@ -600,14 +629,15 @@ AIR302_common_sql.py:107:1: AIR302 [*] `airflow.operators.sql.parse_boolean` is
102 102 | BaseSQLOperator()
103 103 | BranchSQLOperator()
AIR302_common_sql.py:112:1: AIR302 [*] `airflow.sensors.sql.SqlSensor` is moved into `common-sql` provider in Airflow 3.0;
AIR302 [*] `airflow.sensors.sql.SqlSensor` is moved into `common-sql` provider in Airflow 3.0;
--> AIR302_common_sql.py:112:1
|
110 | from airflow.sensors.sql import SqlSensor
111 |
112 | SqlSensor()
| ^^^^^^^^^ AIR302
| ^^^^^^^^^
|
= help: Install `apache-airflow-providers-common-sql>=1.0.0` and use `SqlSensor` from `airflow.providers.common.sql.sensors.sql` instead.
help: Install `apache-airflow-providers-common-sql>=1.0.0` and use `SqlSensor` from `airflow.providers.common.sql.sensors.sql` instead.
Unsafe fix
107 107 | parse_boolean()
@@ -619,14 +649,15 @@ AIR302_common_sql.py:112:1: AIR302 [*] `airflow.sensors.sql.SqlSensor` is moved
112 112 | SqlSensor()
113 113 |
AIR302_common_sql.py:117:1: AIR302 [*] `airflow.sensors.sql_sensor.SqlSensor` is moved into `common-sql` provider in Airflow 3.0;
AIR302 [*] `airflow.sensors.sql_sensor.SqlSensor` is moved into `common-sql` provider in Airflow 3.0;
--> AIR302_common_sql.py:117:1
|
115 | from airflow.sensors.sql_sensor import SqlSensor
116 |
117 | SqlSensor()
| ^^^^^^^^^ AIR302
| ^^^^^^^^^
|
= help: Install `apache-airflow-providers-common-sql>=1.0.0` and use `SqlSensor` from `airflow.providers.common.sql.sensors.sql` instead.
help: Install `apache-airflow-providers-common-sql>=1.0.0` and use `SqlSensor` from `airflow.providers.common.sql.sensors.sql` instead.
Unsafe fix
112 112 | SqlSensor()

View File

@@ -1,14 +1,15 @@
---
source: crates/ruff_linter/src/rules/airflow/mod.rs
---
AIR302_daskexecutor.py:5:1: AIR302 [*] `airflow.executors.dask_executor.DaskExecutor` is moved into `daskexecutor` provider in Airflow 3.0;
AIR302 [*] `airflow.executors.dask_executor.DaskExecutor` is moved into `daskexecutor` provider in Airflow 3.0;
--> AIR302_daskexecutor.py:5:1
|
3 | from airflow.executors.dask_executor import DaskExecutor
4 |
5 | DaskExecutor()
| ^^^^^^^^^^^^ AIR302
| ^^^^^^^^^^^^
|
= help: Install `apache-airflow-providers-daskexecutor>=1.0.0` and use `DaskExecutor` from `airflow.providers.daskexecutor.executors.dask_executor` instead.
help: Install `apache-airflow-providers-daskexecutor>=1.0.0` and use `DaskExecutor` from `airflow.providers.daskexecutor.executors.dask_executor` instead.
Unsafe fix
1 1 | from __future__ import annotations

View File

@@ -1,15 +1,16 @@
---
source: crates/ruff_linter/src/rules/airflow/mod.rs
---
AIR302_druid.py:12:1: AIR302 [*] `airflow.hooks.druid_hook.DruidDbApiHook` is moved into `apache-druid` provider in Airflow 3.0;
AIR302 [*] `airflow.hooks.druid_hook.DruidDbApiHook` is moved into `apache-druid` provider in Airflow 3.0;
--> AIR302_druid.py:12:1
|
10 | )
11 |
12 | DruidDbApiHook()
| ^^^^^^^^^^^^^^ AIR302
| ^^^^^^^^^^^^^^
13 | DruidHook()
|
= help: Install `apache-airflow-providers-apache-druid>=1.0.0` and use `DruidDbApiHook` from `airflow.providers.apache.druid.hooks.druid` instead.
help: Install `apache-airflow-providers-apache-druid>=1.0.0` and use `DruidDbApiHook` from `airflow.providers.apache.druid.hooks.druid` instead.
Unsafe fix
1 1 | from __future__ import annotations
@@ -27,15 +28,16 @@ AIR302_druid.py:12:1: AIR302 [*] `airflow.hooks.druid_hook.DruidDbApiHook` is mo
12 12 | DruidDbApiHook()
13 13 | DruidHook()
AIR302_druid.py:13:1: AIR302 [*] `airflow.hooks.druid_hook.DruidHook` is moved into `apache-druid` provider in Airflow 3.0;
AIR302 [*] `airflow.hooks.druid_hook.DruidHook` is moved into `apache-druid` provider in Airflow 3.0;
--> AIR302_druid.py:13:1
|
12 | DruidDbApiHook()
13 | DruidHook()
| ^^^^^^^^^ AIR302
| ^^^^^^^^^
14 |
15 | HiveToDruidOperator()
|
= help: Install `apache-airflow-providers-apache-druid>=1.0.0` and use `DruidHook` from `airflow.providers.apache.druid.hooks.druid` instead.
help: Install `apache-airflow-providers-apache-druid>=1.0.0` and use `DruidHook` from `airflow.providers.apache.druid.hooks.druid` instead.
Unsafe fix
2 2 |
@@ -52,15 +54,16 @@ AIR302_druid.py:13:1: AIR302 [*] `airflow.hooks.druid_hook.DruidHook` is moved i
12 12 | DruidDbApiHook()
13 13 | DruidHook()
AIR302_druid.py:15:1: AIR302 [*] `airflow.operators.hive_to_druid.HiveToDruidOperator` is moved into `apache-druid` provider in Airflow 3.0;
AIR302 [*] `airflow.operators.hive_to_druid.HiveToDruidOperator` is moved into `apache-druid` provider in Airflow 3.0;
--> AIR302_druid.py:15:1
|
13 | DruidHook()
14 |
15 | HiveToDruidOperator()
| ^^^^^^^^^^^^^^^^^^^ AIR302
| ^^^^^^^^^^^^^^^^^^^
16 | HiveToDruidTransfer()
|
= help: Install `apache-airflow-providers-apache-druid>=1.0.0` and use `HiveToDruidOperator` from `airflow.providers.apache.druid.transfers.hive_to_druid` instead.
help: Install `apache-airflow-providers-apache-druid>=1.0.0` and use `HiveToDruidOperator` from `airflow.providers.apache.druid.transfers.hive_to_druid` instead.
Unsafe fix
5 5 | DruidHook,
@@ -74,13 +77,14 @@ AIR302_druid.py:15:1: AIR302 [*] `airflow.operators.hive_to_druid.HiveToDruidOpe
12 12 | DruidDbApiHook()
13 13 | DruidHook()
AIR302_druid.py:16:1: AIR302 [*] `airflow.operators.hive_to_druid.HiveToDruidTransfer` is moved into `apache-druid` provider in Airflow 3.0;
AIR302 [*] `airflow.operators.hive_to_druid.HiveToDruidTransfer` is moved into `apache-druid` provider in Airflow 3.0;
--> AIR302_druid.py:16:1
|
15 | HiveToDruidOperator()
16 | HiveToDruidTransfer()
| ^^^^^^^^^^^^^^^^^^^ AIR302
| ^^^^^^^^^^^^^^^^^^^
|
= help: Install `apache-airflow-providers-apache-druid>=1.0.0` and use `HiveToDruidOperator` from `airflow.providers.apache.druid.transfers.hive_to_druid` instead.
help: Install `apache-airflow-providers-apache-druid>=1.0.0` and use `HiveToDruidOperator` from `airflow.providers.apache.druid.transfers.hive_to_druid` instead.
Unsafe fix
5 5 | DruidHook,

View File

@@ -1,16 +1,17 @@
---
source: crates/ruff_linter/src/rules/airflow/mod.rs
---
AIR302_fab.py:10:1: AIR302 [*] `airflow.api.auth.backend.basic_auth.CLIENT_AUTH` is moved into `fab` provider in Airflow 3.0;
AIR302 [*] `airflow.api.auth.backend.basic_auth.CLIENT_AUTH` is moved into `fab` provider in Airflow 3.0;
--> AIR302_fab.py:10:1
|
8 | )
9 |
10 | CLIENT_AUTH
| ^^^^^^^^^^^ AIR302
| ^^^^^^^^^^^
11 | init_app()
12 | auth_current_user()
|
= help: Install `apache-airflow-providers-fab>=1.0.0` and use `CLIENT_AUTH` from `airflow.providers.fab.auth_manager.api.auth.backend.basic_auth` instead.
help: Install `apache-airflow-providers-fab>=1.0.0` and use `CLIENT_AUTH` from `airflow.providers.fab.auth_manager.api.auth.backend.basic_auth` instead.
Unsafe fix
1 1 | from __future__ import annotations
@@ -26,15 +27,16 @@ AIR302_fab.py:10:1: AIR302 [*] `airflow.api.auth.backend.basic_auth.CLIENT_AUTH`
10 10 | CLIENT_AUTH
11 11 | init_app()
AIR302_fab.py:11:1: AIR302 [*] `airflow.api.auth.backend.basic_auth.init_app` is moved into `fab` provider in Airflow 3.0;
AIR302 [*] `airflow.api.auth.backend.basic_auth.init_app` is moved into `fab` provider in Airflow 3.0;
--> AIR302_fab.py:11:1
|
10 | CLIENT_AUTH
11 | init_app()
| ^^^^^^^^ AIR302
| ^^^^^^^^
12 | auth_current_user()
13 | requires_authentication()
|
= help: Install `apache-airflow-providers-fab>=1.0.0` and use `init_app` from `airflow.providers.fab.auth_manager.api.auth.backend.basic_auth` instead.
help: Install `apache-airflow-providers-fab>=1.0.0` and use `init_app` from `airflow.providers.fab.auth_manager.api.auth.backend.basic_auth` instead.
Unsafe fix
3 3 | from airflow.api.auth.backend.basic_auth import (
@@ -48,15 +50,16 @@ AIR302_fab.py:11:1: AIR302 [*] `airflow.api.auth.backend.basic_auth.init_app` is
10 10 | CLIENT_AUTH
11 11 | init_app()
AIR302_fab.py:12:1: AIR302 [*] `airflow.api.auth.backend.basic_auth.auth_current_user` is moved into `fab` provider in Airflow 3.0;
AIR302 [*] `airflow.api.auth.backend.basic_auth.auth_current_user` is moved into `fab` provider in Airflow 3.0;
--> AIR302_fab.py:12:1
|
10 | CLIENT_AUTH
11 | init_app()
12 | auth_current_user()
| ^^^^^^^^^^^^^^^^^ AIR302
| ^^^^^^^^^^^^^^^^^
13 | requires_authentication()
|
= help: Install `apache-airflow-providers-fab>=1.0.0` and use `auth_current_user` from `airflow.providers.fab.auth_manager.api.auth.backend.basic_auth` instead.
help: Install `apache-airflow-providers-fab>=1.0.0` and use `auth_current_user` from `airflow.providers.fab.auth_manager.api.auth.backend.basic_auth` instead.
Unsafe fix
2 2 |
@@ -71,16 +74,17 @@ AIR302_fab.py:12:1: AIR302 [*] `airflow.api.auth.backend.basic_auth.auth_current
10 10 | CLIENT_AUTH
11 11 | init_app()
AIR302_fab.py:13:1: AIR302 [*] `airflow.api.auth.backend.basic_auth.requires_authentication` is moved into `fab` provider in Airflow 3.0;
AIR302 [*] `airflow.api.auth.backend.basic_auth.requires_authentication` is moved into `fab` provider in Airflow 3.0;
--> AIR302_fab.py:13:1
|
11 | init_app()
12 | auth_current_user()
13 | requires_authentication()
| ^^^^^^^^^^^^^^^^^^^^^^^ AIR302
| ^^^^^^^^^^^^^^^^^^^^^^^
14 |
15 | from airflow.api.auth.backend.kerberos_auth import (
|
= help: Install `apache-airflow-providers-fab>=1.0.0` and use `requires_authentication` from `airflow.providers.fab.auth_manager.api.auth.backend.basic_auth` instead.
help: Install `apache-airflow-providers-fab>=1.0.0` and use `requires_authentication` from `airflow.providers.fab.auth_manager.api.auth.backend.basic_auth` instead.
Unsafe fix
4 4 | CLIENT_AUTH,
@@ -93,16 +97,17 @@ AIR302_fab.py:13:1: AIR302 [*] `airflow.api.auth.backend.basic_auth.requires_aut
10 10 | CLIENT_AUTH
11 11 | init_app()
AIR302_fab.py:23:1: AIR302 [*] `airflow.api.auth.backend.kerberos_auth.log` is moved into `fab` provider in Airflow 3.0;
AIR302 [*] `airflow.api.auth.backend.kerberos_auth.log` is moved into `fab` provider in Airflow 3.0;
--> AIR302_fab.py:23:1
|
21 | )
22 |
23 | log()
| ^^^ AIR302
| ^^^
24 | CLIENT_AUTH
25 | find_user()
|
= help: Install `apache-airflow-providers-fab>=1.0.0` and use `log` from `airflow.providers.fab.auth_manager.api.auth.backend.kerberos_auth` instead.
help: Install `apache-airflow-providers-fab>=1.0.0` and use `log` from `airflow.providers.fab.auth_manager.api.auth.backend.kerberos_auth` instead.
Unsafe fix
16 16 | CLIENT_AUTH,
@@ -116,15 +121,16 @@ AIR302_fab.py:23:1: AIR302 [*] `airflow.api.auth.backend.kerberos_auth.log` is m
23 23 | log()
24 24 | CLIENT_AUTH
AIR302_fab.py:24:1: AIR302 [*] `airflow.api.auth.backend.kerberos_auth.CLIENT_AUTH` is moved into `fab` provider in Airflow 3.0;
AIR302 [*] `airflow.api.auth.backend.kerberos_auth.CLIENT_AUTH` is moved into `fab` provider in Airflow 3.0;
--> AIR302_fab.py:24:1
|
23 | log()
24 | CLIENT_AUTH
| ^^^^^^^^^^^ AIR302
| ^^^^^^^^^^^
25 | find_user()
26 | init_app()
|
= help: Install `apache-airflow-providers-fab>=1.0.0` and use `CLIENT_AUTH` from `airflow.providers.fab.auth_manager.api.auth.backend.kerberos_auth` instead.
help: Install `apache-airflow-providers-fab>=1.0.0` and use `CLIENT_AUTH` from `airflow.providers.fab.auth_manager.api.auth.backend.kerberos_auth` instead.
Unsafe fix
13 13 | requires_authentication()
@@ -141,16 +147,17 @@ AIR302_fab.py:24:1: AIR302 [*] `airflow.api.auth.backend.kerberos_auth.CLIENT_AU
23 23 | log()
24 24 | CLIENT_AUTH
AIR302_fab.py:25:1: AIR302 [*] `airflow.api.auth.backend.kerberos_auth.find_user` is moved into `fab` provider in Airflow 3.0;
AIR302 [*] `airflow.api.auth.backend.kerberos_auth.find_user` is moved into `fab` provider in Airflow 3.0;
--> AIR302_fab.py:25:1
|
23 | log()
24 | CLIENT_AUTH
25 | find_user()
| ^^^^^^^^^ AIR302
| ^^^^^^^^^
26 | init_app()
27 | requires_authentication()
|
= help: Install `apache-airflow-providers-fab>=1.0.0` and use `find_user` from `airflow.providers.fab.auth_manager.api.auth.backend.kerberos_auth` instead.
help: Install `apache-airflow-providers-fab>=1.0.0` and use `find_user` from `airflow.providers.fab.auth_manager.api.auth.backend.kerberos_auth` instead.
Unsafe fix
14 14 |
@@ -166,15 +173,16 @@ AIR302_fab.py:25:1: AIR302 [*] `airflow.api.auth.backend.kerberos_auth.find_user
23 23 | log()
24 24 | CLIENT_AUTH
AIR302_fab.py:26:1: AIR302 [*] `airflow.api.auth.backend.kerberos_auth.init_app` is moved into `fab` provider in Airflow 3.0;
AIR302 [*] `airflow.api.auth.backend.kerberos_auth.init_app` is moved into `fab` provider in Airflow 3.0;
--> AIR302_fab.py:26:1
|
24 | CLIENT_AUTH
25 | find_user()
26 | init_app()
| ^^^^^^^^ AIR302
| ^^^^^^^^
27 | requires_authentication()
|
= help: Install `apache-airflow-providers-fab>=1.0.0` and use `init_app` from `airflow.providers.fab.auth_manager.api.auth.backend.kerberos_auth` instead.
help: Install `apache-airflow-providers-fab>=1.0.0` and use `init_app` from `airflow.providers.fab.auth_manager.api.auth.backend.kerberos_auth` instead.
Unsafe fix
15 15 | from airflow.api.auth.backend.kerberos_auth import (
@@ -189,16 +197,17 @@ AIR302_fab.py:26:1: AIR302 [*] `airflow.api.auth.backend.kerberos_auth.init_app`
23 23 | log()
24 24 | CLIENT_AUTH
AIR302_fab.py:27:1: AIR302 [*] `airflow.api.auth.backend.kerberos_auth.requires_authentication` is moved into `fab` provider in Airflow 3.0;
AIR302 [*] `airflow.api.auth.backend.kerberos_auth.requires_authentication` is moved into `fab` provider in Airflow 3.0;
--> AIR302_fab.py:27:1
|
25 | find_user()
26 | init_app()
27 | requires_authentication()
| ^^^^^^^^^^^^^^^^^^^^^^^ AIR302
| ^^^^^^^^^^^^^^^^^^^^^^^
28 |
29 | from airflow.auth.managers.fab.api.auth.backend.kerberos_auth import (
|
= help: Install `apache-airflow-providers-fab>=1.0.0` and use `requires_authentication` from `airflow.providers.fab.auth_manager.api.auth.backend.kerberos_auth` instead.
help: Install `apache-airflow-providers-fab>=1.0.0` and use `requires_authentication` from `airflow.providers.fab.auth_manager.api.auth.backend.kerberos_auth` instead.
Unsafe fix
17 17 | find_user,
@@ -211,16 +220,17 @@ AIR302_fab.py:27:1: AIR302 [*] `airflow.api.auth.backend.kerberos_auth.requires_
23 23 | log()
24 24 | CLIENT_AUTH
AIR302_fab.py:37:1: AIR302 [*] `airflow.auth.managers.fab.api.auth.backend.kerberos_auth.log` is moved into `fab` provider in Airflow 3.0;
AIR302 [*] `airflow.auth.managers.fab.api.auth.backend.kerberos_auth.log` is moved into `fab` provider in Airflow 3.0;
--> AIR302_fab.py:37:1
|
35 | )
36 |
37 | log()
| ^^^ AIR302
| ^^^
38 | CLIENT_AUTH
39 | find_user()
|
= help: Install `apache-airflow-providers-fab>=1.0.0` and use `log` from `airflow.providers.fab.auth_manager.api.auth.backend.kerberos_auth` instead.
help: Install `apache-airflow-providers-fab>=1.0.0` and use `log` from `airflow.providers.fab.auth_manager.api.auth.backend.kerberos_auth` instead.
Unsafe fix
30 30 | CLIENT_AUTH,
@@ -234,15 +244,16 @@ AIR302_fab.py:37:1: AIR302 [*] `airflow.auth.managers.fab.api.auth.backend.kerbe
37 37 | log()
38 38 | CLIENT_AUTH
AIR302_fab.py:38:1: AIR302 [*] `airflow.auth.managers.fab.api.auth.backend.kerberos_auth.CLIENT_AUTH` is moved into `fab` provider in Airflow 3.0;
AIR302 [*] `airflow.auth.managers.fab.api.auth.backend.kerberos_auth.CLIENT_AUTH` is moved into `fab` provider in Airflow 3.0;
--> AIR302_fab.py:38:1
|
37 | log()
38 | CLIENT_AUTH
| ^^^^^^^^^^^ AIR302
| ^^^^^^^^^^^
39 | find_user()
40 | init_app()
|
= help: Install `apache-airflow-providers-fab>=1.0.0` and use `CLIENT_AUTH` from `airflow.providers.fab.auth_manager.api.auth.backend.kerberos_auth` instead.
help: Install `apache-airflow-providers-fab>=1.0.0` and use `CLIENT_AUTH` from `airflow.providers.fab.auth_manager.api.auth.backend.kerberos_auth` instead.
Unsafe fix
27 27 | requires_authentication()
@@ -259,16 +270,17 @@ AIR302_fab.py:38:1: AIR302 [*] `airflow.auth.managers.fab.api.auth.backend.kerbe
37 37 | log()
38 38 | CLIENT_AUTH
AIR302_fab.py:39:1: AIR302 [*] `airflow.auth.managers.fab.api.auth.backend.kerberos_auth.find_user` is moved into `fab` provider in Airflow 3.0;
AIR302 [*] `airflow.auth.managers.fab.api.auth.backend.kerberos_auth.find_user` is moved into `fab` provider in Airflow 3.0;
--> AIR302_fab.py:39:1
|
37 | log()
38 | CLIENT_AUTH
39 | find_user()
| ^^^^^^^^^ AIR302
| ^^^^^^^^^
40 | init_app()
41 | requires_authentication()
|
= help: Install `apache-airflow-providers-fab>=1.0.0` and use `find_user` from `airflow.providers.fab.auth_manager.api.auth.backend.kerberos_auth` instead.
help: Install `apache-airflow-providers-fab>=1.0.0` and use `find_user` from `airflow.providers.fab.auth_manager.api.auth.backend.kerberos_auth` instead.
Unsafe fix
28 28 |
@@ -284,15 +296,16 @@ AIR302_fab.py:39:1: AIR302 [*] `airflow.auth.managers.fab.api.auth.backend.kerbe
37 37 | log()
38 38 | CLIENT_AUTH
AIR302_fab.py:40:1: AIR302 [*] `airflow.auth.managers.fab.api.auth.backend.kerberos_auth.init_app` is moved into `fab` provider in Airflow 3.0;
AIR302 [*] `airflow.auth.managers.fab.api.auth.backend.kerberos_auth.init_app` is moved into `fab` provider in Airflow 3.0;
--> AIR302_fab.py:40:1
|
38 | CLIENT_AUTH
39 | find_user()
40 | init_app()
| ^^^^^^^^ AIR302
| ^^^^^^^^
41 | requires_authentication()
|
= help: Install `apache-airflow-providers-fab>=1.0.0` and use `init_app` from `airflow.providers.fab.auth_manager.api.auth.backend.kerberos_auth` instead.
help: Install `apache-airflow-providers-fab>=1.0.0` and use `init_app` from `airflow.providers.fab.auth_manager.api.auth.backend.kerberos_auth` instead.
Unsafe fix
29 29 | from airflow.auth.managers.fab.api.auth.backend.kerberos_auth import (
@@ -307,16 +320,17 @@ AIR302_fab.py:40:1: AIR302 [*] `airflow.auth.managers.fab.api.auth.backend.kerbe
37 37 | log()
38 38 | CLIENT_AUTH
AIR302_fab.py:41:1: AIR302 [*] `airflow.auth.managers.fab.api.auth.backend.kerberos_auth.requires_authentication` is moved into `fab` provider in Airflow 3.0;
AIR302 [*] `airflow.auth.managers.fab.api.auth.backend.kerberos_auth.requires_authentication` is moved into `fab` provider in Airflow 3.0;
--> AIR302_fab.py:41:1
|
39 | find_user()
40 | init_app()
41 | requires_authentication()
| ^^^^^^^^^^^^^^^^^^^^^^^ AIR302
| ^^^^^^^^^^^^^^^^^^^^^^^
42 |
43 | from airflow.auth.managers.fab.fab_auth_manager import FabAuthManager
|
= help: Install `apache-airflow-providers-fab>=1.0.0` and use `requires_authentication` from `airflow.providers.fab.auth_manager.api.auth.backend.kerberos_auth` instead.
help: Install `apache-airflow-providers-fab>=1.0.0` and use `requires_authentication` from `airflow.providers.fab.auth_manager.api.auth.backend.kerberos_auth` instead.
Unsafe fix
31 31 | find_user,
@@ -329,16 +343,17 @@ AIR302_fab.py:41:1: AIR302 [*] `airflow.auth.managers.fab.api.auth.backend.kerbe
37 37 | log()
38 38 | CLIENT_AUTH
AIR302_fab.py:49:1: AIR302 [*] `airflow.auth.managers.fab.fab_auth_manager.FabAuthManager` is moved into `fab` provider in Airflow 3.0;
AIR302 [*] `airflow.auth.managers.fab.fab_auth_manager.FabAuthManager` is moved into `fab` provider in Airflow 3.0;
--> AIR302_fab.py:49:1
|
47 | )
48 |
49 | FabAuthManager()
| ^^^^^^^^^^^^^^ AIR302
| ^^^^^^^^^^^^^^
50 | MAX_NUM_DATABASE_USER_SESSIONS
51 | FabAirflowSecurityManagerOverride()
|
= help: Install `apache-airflow-providers-fab>=1.0.0` and use `FabAuthManager` from `airflow.providers.fab.auth_manager.fab_auth_manager` instead.
help: Install `apache-airflow-providers-fab>=1.0.0` and use `FabAuthManager` from `airflow.providers.fab.auth_manager.fab_auth_manager` instead.
Unsafe fix
40 40 | init_app()
@@ -354,14 +369,15 @@ AIR302_fab.py:49:1: AIR302 [*] `airflow.auth.managers.fab.fab_auth_manager.FabAu
49 49 | FabAuthManager()
50 50 | MAX_NUM_DATABASE_USER_SESSIONS
AIR302_fab.py:50:1: AIR302 [*] `airflow.auth.managers.fab.security_manager.override.MAX_NUM_DATABASE_USER_SESSIONS` is moved into `fab` provider in Airflow 3.0;
AIR302 [*] `airflow.auth.managers.fab.security_manager.override.MAX_NUM_DATABASE_USER_SESSIONS` is moved into `fab` provider in Airflow 3.0;
--> AIR302_fab.py:50:1
|
49 | FabAuthManager()
50 | MAX_NUM_DATABASE_USER_SESSIONS
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ AIR302
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
51 | FabAirflowSecurityManagerOverride()
|
= help: Install `apache-airflow-providers-fab>=1.0.0` and use `MAX_NUM_DATABASE_USER_SESSIONS` from `airflow.providers.fab.auth_manager.security_manager.override` instead.
help: Install `apache-airflow-providers-fab>=1.0.0` and use `MAX_NUM_DATABASE_USER_SESSIONS` from `airflow.providers.fab.auth_manager.security_manager.override` instead.
Unsafe fix
42 42 |
@@ -375,16 +391,17 @@ AIR302_fab.py:50:1: AIR302 [*] `airflow.auth.managers.fab.security_manager.overr
49 49 | FabAuthManager()
50 50 | MAX_NUM_DATABASE_USER_SESSIONS
AIR302_fab.py:51:1: AIR302 [*] `airflow.auth.managers.fab.security_manager.override.FabAirflowSecurityManagerOverride` is moved into `fab` provider in Airflow 3.0;
AIR302 [*] `airflow.auth.managers.fab.security_manager.override.FabAirflowSecurityManagerOverride` is moved into `fab` provider in Airflow 3.0;
--> AIR302_fab.py:51:1
|
49 | FabAuthManager()
50 | MAX_NUM_DATABASE_USER_SESSIONS
51 | FabAirflowSecurityManagerOverride()
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ AIR302
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
52 |
53 | from airflow.www.security import FabAirflowSecurityManagerOverride
|
= help: Install `apache-airflow-providers-fab>=1.0.0` and use `FabAirflowSecurityManagerOverride` from `airflow.providers.fab.auth_manager.security_manager.override` instead.
help: Install `apache-airflow-providers-fab>=1.0.0` and use `FabAirflowSecurityManagerOverride` from `airflow.providers.fab.auth_manager.security_manager.override` instead.
Unsafe fix
43 43 | from airflow.auth.managers.fab.fab_auth_manager import FabAuthManager
@@ -397,14 +414,15 @@ AIR302_fab.py:51:1: AIR302 [*] `airflow.auth.managers.fab.security_manager.overr
49 49 | FabAuthManager()
50 50 | MAX_NUM_DATABASE_USER_SESSIONS
AIR302_fab.py:55:1: AIR302 [*] `airflow.www.security.FabAirflowSecurityManagerOverride` is moved into `fab` provider in Airflow 3.0;
AIR302 [*] `airflow.www.security.FabAirflowSecurityManagerOverride` is moved into `fab` provider in Airflow 3.0;
--> AIR302_fab.py:55:1
|
53 | from airflow.www.security import FabAirflowSecurityManagerOverride
54 |
55 | FabAirflowSecurityManagerOverride()
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ AIR302
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: Install `apache-airflow-providers-fab>=1.0.0` and use `FabAirflowSecurityManagerOverride` from `airflow.providers.fab.auth_manager.security_manager.override` instead.
help: Install `apache-airflow-providers-fab>=1.0.0` and use `FabAirflowSecurityManagerOverride` from `airflow.providers.fab.auth_manager.security_manager.override` instead.
Unsafe fix
50 50 | MAX_NUM_DATABASE_USER_SESSIONS

View File

@@ -1,15 +1,16 @@
---
source: crates/ruff_linter/src/rules/airflow/mod.rs
---
AIR302_hdfs.py:6:1: AIR302 [*] `airflow.hooks.webhdfs_hook.WebHDFSHook` is moved into `apache-hdfs` provider in Airflow 3.0;
AIR302 [*] `airflow.hooks.webhdfs_hook.WebHDFSHook` is moved into `apache-hdfs` provider in Airflow 3.0;
--> AIR302_hdfs.py:6:1
|
4 | from airflow.sensors.web_hdfs_sensor import WebHdfsSensor
5 |
6 | WebHDFSHook()
| ^^^^^^^^^^^ AIR302
| ^^^^^^^^^^^
7 | WebHdfsSensor()
|
= help: Install `apache-airflow-providers-apache-hdfs>=1.0.0` and use `WebHDFSHook` from `airflow.providers.apache.hdfs.hooks.webhdfs` instead.
help: Install `apache-airflow-providers-apache-hdfs>=1.0.0` and use `WebHDFSHook` from `airflow.providers.apache.hdfs.hooks.webhdfs` instead.
Unsafe fix
1 1 | from __future__ import annotations
@@ -21,13 +22,14 @@ AIR302_hdfs.py:6:1: AIR302 [*] `airflow.hooks.webhdfs_hook.WebHDFSHook` is moved
6 6 | WebHDFSHook()
7 7 | WebHdfsSensor()
AIR302_hdfs.py:7:1: AIR302 [*] `airflow.sensors.web_hdfs_sensor.WebHdfsSensor` is moved into `apache-hdfs` provider in Airflow 3.0;
AIR302 [*] `airflow.sensors.web_hdfs_sensor.WebHdfsSensor` is moved into `apache-hdfs` provider in Airflow 3.0;
--> AIR302_hdfs.py:7:1
|
6 | WebHDFSHook()
7 | WebHdfsSensor()
| ^^^^^^^^^^^^^ AIR302
| ^^^^^^^^^^^^^
|
= help: Install `apache-airflow-providers-apache-hdfs>=1.0.0` and use `WebHdfsSensor` from `airflow.providers.apache.hdfs.sensors.web_hdfs` instead.
help: Install `apache-airflow-providers-apache-hdfs>=1.0.0` and use `WebHdfsSensor` from `airflow.providers.apache.hdfs.sensors.web_hdfs` instead.
Unsafe fix
1 1 | from __future__ import annotations

Some files were not shown because too many files have changed in this diff Show More