fix(F822): add option to enable F822 in __init__.py files (#11370)

## Summary

This PR aims to close #10095 by adding an option
`init-allow-undef-export` to the `pyflakes` settings. This option is
currently set to `true` such that behavior is kept identical.
But setting this option to `false` will lead to `F822` warnings to be
shown in all files, **including** `__init__.py` files.

As I've mentioned on #10095, I think `init-allow-undef-export=false`
would be the more user-friendly default option, as it creates fewer
surprises. @charliermarsh what do you think about making that the
default?

With this option in place, it's a single line fix for people that rely
on the old behavior.

And thinking longer term, for future major releases, one could probably
consider deprecating the option and eventually having people just `noqa`
these warnings if they are not wanted.


## Test Plan

I've added a `test_init_f822_enabled` test which repeats the test that
is done in the `init` test but this time with
`init-allow-undef-export=false` and the snap file correctly shows that
ruff will then trigger the otherwise suppressed F822 warning.


closes #10095
This commit is contained in:
Christoph Hasse 2024-05-29 23:15:05 -04:00 committed by GitHub
parent 921bc15542
commit e35deee583
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 37 additions and 1 deletions

View File

@ -2301,7 +2301,9 @@ impl<'a> Checker<'a> {
} }
} else { } else {
if self.enabled(Rule::UndefinedExport) { if self.enabled(Rule::UndefinedExport) {
if !self.path.ends_with("__init__.py") { if self.settings.preview.is_enabled()
|| !self.path.ends_with("__init__.py")
{
self.diagnostics.push( self.diagnostics.push(
Diagnostic::new( Diagnostic::new(
pyflakes::rules::UndefinedExport { pyflakes::rules::UndefinedExport {

View File

@ -212,6 +212,7 @@ mod tests {
#[test_case(Rule::UnusedImport, Path::new("F401_27__all_mistyped/__init__.py"))] #[test_case(Rule::UnusedImport, Path::new("F401_27__all_mistyped/__init__.py"))]
#[test_case(Rule::UnusedImport, Path::new("F401_28__all_multiple/__init__.py"))] #[test_case(Rule::UnusedImport, Path::new("F401_28__all_multiple/__init__.py"))]
#[test_case(Rule::UnusedImport, Path::new("F401_29__all_conditional/__init__.py"))] #[test_case(Rule::UnusedImport, Path::new("F401_29__all_conditional/__init__.py"))]
#[test_case(Rule::UndefinedExport, Path::new("__init__.py"))]
fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> { fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!( let snapshot = format!(
"preview__{}_{}", "preview__{}_{}",

View File

@ -13,6 +13,11 @@ use ruff_macros::{derive_message_formats, violation};
/// Including an undefined name in `__all__` is likely to raise `NameError` at /// Including an undefined name in `__all__` is likely to raise `NameError` at
/// runtime, when the module is imported. /// runtime, when the module is imported.
/// ///
/// In [preview], this rule will flag undefined names in `__init__.py` file,
/// even if those names implicitly refer to other modules in the package. Users
/// that rely on implicit exports should disable this rule in `__init__.py`
/// files via [`lint.per-file-ignores`].
///
/// ## Example /// ## Example
/// ```python /// ```python
/// from foo import bar /// from foo import bar
@ -31,6 +36,8 @@ use ruff_macros::{derive_message_formats, violation};
/// ///
/// ## References /// ## References
/// - [Python documentation: `__all__`](https://docs.python.org/3/tutorial/modules.html#importing-from-a-package) /// - [Python documentation: `__all__`](https://docs.python.org/3/tutorial/modules.html#importing-from-a-package)
///
/// [preview]: https://docs.astral.sh/ruff/preview/
#[violation] #[violation]
pub struct UndefinedExport { pub struct UndefinedExport {
pub name: String, pub name: String,

View File

@ -0,0 +1,26 @@
---
source: crates/ruff_linter/src/rules/pyflakes/mod.rs
---
__init__.py:5:12: F822 Undefined name `a` in `__all__`
|
3 | print(__path__)
4 |
5 | __all__ = ["a", "b", "c"]
| ^^^ F822
|
__init__.py:5:17: F822 Undefined name `b` in `__all__`
|
3 | print(__path__)
4 |
5 | __all__ = ["a", "b", "c"]
| ^^^ F822
|
__init__.py:5:22: F822 Undefined name `c` in `__all__`
|
3 | print(__path__)
4 |
5 | __all__ = ["a", "b", "c"]
| ^^^ F822
|