Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove F401 fix for __init__ imports by default and allow opt-in to unsafe fix #10365

Merged
merged 9 commits into from Mar 13, 2024

Conversation

zanieb
Copy link
Member

@zanieb zanieb commented Mar 12, 2024

Re-implementation of #5845 but instead of deprecating the option I toggle the default. Now users can opt-in via the setting which will give them an unsafe fix to remove the import. Otherwise, we raise violations but do not offer a fix. The setting is a bit of a misnomer in either case, maybe we'll want to remove it still someday.

As discussed there, I think the safe fix should be to import it as an alias. I'm not sure. We need support for offering multiple fixes for ideal behavior though? I think we should remove the fix entirely and consider it separately.

Closes #5697
Supersedes #5845

@zanieb zanieb added the rule Implementing or modifying a lint rule label Mar 12, 2024
@zanieb zanieb changed the title Deprecate F401 ignore-init-module-imports and make default behavior Remove F401 fix for __init__ imports by default and allow opt-in to unsafe fix Mar 12, 2024
Copy link

github-actions bot commented Mar 12, 2024

ruff-ecosystem results

Linter (stable)

ℹ️ ecosystem check detected linter changes. (+52 -52 violations, +0 -0 fixes in 2 projects; 41 projects unchanged)

PostHog/HouseWatch (+2 -2 violations, +0 -0 fixes)

- housewatch/models/__init__.py:2:21: F401 [*] `.backup.ScheduledBackup` imported but unused
+ housewatch/models/__init__.py:2:21: F401 `.backup.ScheduledBackup` imported but unused; consider removing, adding to `__all__`, or using a redundant alias
- housewatch/models/__init__.py:2:38: F401 [*] `.backup.ScheduledBackupRun` imported but unused
+ housewatch/models/__init__.py:2:38: F401 `.backup.ScheduledBackupRun` imported but unused; consider removing, adding to `__all__`, or using a redundant alias

latchbio/latch (+50 -50 violations, +0 -0 fixes)

- latch/__init__.py:10:5: F401 [*] `latch.functions.operators.group_tuple` imported but unused
+ latch/__init__.py:10:5: F401 `latch.functions.operators.group_tuple` imported but unused; consider removing, adding to `__all__`, or using a redundant alias
- latch/__init__.py:11:5: F401 [*] `latch.functions.operators.inner_join` imported but unused
+ latch/__init__.py:11:5: F401 `latch.functions.operators.inner_join` imported but unused; consider removing, adding to `__all__`, or using a redundant alias
- latch/__init__.py:12:5: F401 [*] `latch.functions.operators.latch_filter` imported but unused
+ latch/__init__.py:12:5: F401 `latch.functions.operators.latch_filter` imported but unused; consider removing, adding to `__all__`, or using a redundant alias
- latch/__init__.py:13:5: F401 [*] `latch.functions.operators.left_join` imported but unused
+ latch/__init__.py:13:5: F401 `latch.functions.operators.left_join` imported but unused; consider removing, adding to `__all__`, or using a redundant alias
- latch/__init__.py:14:5: F401 [*] `latch.functions.operators.outer_join` imported but unused
+ latch/__init__.py:14:5: F401 `latch.functions.operators.outer_join` imported but unused; consider removing, adding to `__all__`, or using a redundant alias
- latch/__init__.py:15:5: F401 [*] `latch.functions.operators.right_join` imported but unused
+ latch/__init__.py:15:5: F401 `latch.functions.operators.right_join` imported but unused; consider removing, adding to `__all__`, or using a redundant alias
- latch/__init__.py:17:41: F401 [*] `latch.resources.conditional.create_conditional_section` imported but unused
+ latch/__init__.py:17:41: F401 `latch.resources.conditional.create_conditional_section` imported but unused; consider removing, adding to `__all__`, or using a redundant alias
- latch/__init__.py:18:39: F401 [*] `latch.resources.map_tasks.map_task` imported but unused
+ latch/__init__.py:18:39: F401 `latch.resources.map_tasks.map_task` imported but unused; consider removing, adding to `__all__`, or using a redundant alias
- latch/__init__.py:19:48: F401 [*] `latch.resources.reference_workflow.workflow_reference` imported but unused
+ latch/__init__.py:19:48: F401 `latch.resources.reference_workflow.workflow_reference` imported but unused; consider removing, adding to `__all__`, or using a redundant alias
- latch/__init__.py:21:5: F401 [*] `latch.resources.tasks.custom_task` imported but unused
+ latch/__init__.py:21:5: F401 `latch.resources.tasks.custom_task` imported but unused; consider removing, adding to `__all__`, or using a redundant alias
- latch/__init__.py:22:5: F401 [*] `latch.resources.tasks.custom_memory_optimized_task` imported but unused
+ latch/__init__.py:22:5: F401 `latch.resources.tasks.custom_memory_optimized_task` imported but unused; consider removing, adding to `__all__`, or using a redundant alias
- latch/__init__.py:23:5: F401 [*] `latch.resources.tasks.large_gpu_task` imported but unused
+ latch/__init__.py:23:5: F401 `latch.resources.tasks.large_gpu_task` imported but unused; consider removing, adding to `__all__`, or using a redundant alias
- latch/__init__.py:24:5: F401 [*] `latch.resources.tasks.large_task` imported but unused
+ latch/__init__.py:24:5: F401 `latch.resources.tasks.large_task` imported but unused; consider removing, adding to `__all__`, or using a redundant alias
- latch/__init__.py:25:5: F401 [*] `latch.resources.tasks.medium_task` imported but unused
+ latch/__init__.py:25:5: F401 `latch.resources.tasks.medium_task` imported but unused; consider removing, adding to `__all__`, or using a redundant alias
- latch/__init__.py:26:5: F401 [*] `latch.resources.tasks.small_gpu_task` imported but unused
+ latch/__init__.py:26:5: F401 `latch.resources.tasks.small_gpu_task` imported but unused; consider removing, adding to `__all__`, or using a redundant alias
- latch/__init__.py:27:5: F401 [*] `latch.resources.tasks.small_task` imported but unused
+ latch/__init__.py:27:5: F401 `latch.resources.tasks.small_task` imported but unused; consider removing, adding to `__all__`, or using a redundant alias
- latch/__init__.py:29:38: F401 [*] `latch.resources.workflow.workflow` imported but unused
+ latch/__init__.py:29:38: F401 `latch.resources.workflow.workflow` imported but unused; consider removing, adding to `__all__`, or using a redundant alias
- latch/__init__.py:7:38: F401 [*] `latch.functions.messages.message` imported but unused
+ latch/__init__.py:7:38: F401 `latch.functions.messages.message` imported but unused; consider removing, adding to `__all__`, or using a redundant alias
- latch/__init__.py:9:5: F401 [*] `latch.functions.operators.combine` imported but unused
+ latch/__init__.py:9:5: F401 `latch.functions.operators.combine` imported but unused; consider removing, adding to `__all__`, or using a redundant alias
- latch/types/__init__.py:10:5: F401 [*] `latch.types.metadata.LatchMetadata` imported but unused
+ latch/types/__init__.py:10:5: F401 `latch.types.metadata.LatchMetadata` imported but unused; consider removing, adding to `__all__`, or using a redundant alias
- latch/types/__init__.py:11:5: F401 [*] `latch.types.metadata.LatchParameter` imported but unused
+ latch/types/__init__.py:11:5: F401 `latch.types.metadata.LatchParameter` imported but unused; consider removing, adding to `__all__`, or using a redundant alias
- latch/types/__init__.py:12:5: F401 [*] `latch.types.metadata.LatchRule` imported but unused
+ latch/types/__init__.py:12:5: F401 `latch.types.metadata.LatchRule` imported but unused; consider removing, adding to `__all__`, or using a redundant alias
- latch/types/__init__.py:13:5: F401 [*] `latch.types.metadata.Params` imported but unused
+ latch/types/__init__.py:13:5: F401 `latch.types.metadata.Params` imported but unused; consider removing, adding to `__all__`, or using a redundant alias
- latch/types/__init__.py:14:5: F401 [*] `latch.types.metadata.Section` imported but unused
+ latch/types/__init__.py:14:5: F401 `latch.types.metadata.Section` imported but unused; consider removing, adding to `__all__`, or using a redundant alias
... 52 additional changes omitted for project

Changes by rule (1 rules affected)

code total + violation - violation + fix - fix
F401 104 52 52 0 0

Linter (preview)

ℹ️ ecosystem check detected linter changes. (+52 -52 violations, +0 -0 fixes in 2 projects; 41 projects unchanged)

PostHog/HouseWatch (+2 -2 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --ignore RUF9 --output-format concise --preview

- housewatch/models/__init__.py:2:21: F401 [*] `.backup.ScheduledBackup` imported but unused
+ housewatch/models/__init__.py:2:21: F401 `.backup.ScheduledBackup` imported but unused; consider removing, adding to `__all__`, or using a redundant alias
- housewatch/models/__init__.py:2:38: F401 [*] `.backup.ScheduledBackupRun` imported but unused
+ housewatch/models/__init__.py:2:38: F401 `.backup.ScheduledBackupRun` imported but unused; consider removing, adding to `__all__`, or using a redundant alias

latchbio/latch (+50 -50 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --ignore RUF9 --output-format concise --preview

- latch/__init__.py:10:5: F401 [*] `latch.functions.operators.group_tuple` imported but unused
+ latch/__init__.py:10:5: F401 `latch.functions.operators.group_tuple` imported but unused; consider removing, adding to `__all__`, or using a redundant alias
- latch/__init__.py:11:5: F401 [*] `latch.functions.operators.inner_join` imported but unused
+ latch/__init__.py:11:5: F401 `latch.functions.operators.inner_join` imported but unused; consider removing, adding to `__all__`, or using a redundant alias
- latch/__init__.py:12:5: F401 [*] `latch.functions.operators.latch_filter` imported but unused
+ latch/__init__.py:12:5: F401 `latch.functions.operators.latch_filter` imported but unused; consider removing, adding to `__all__`, or using a redundant alias
- latch/__init__.py:13:5: F401 [*] `latch.functions.operators.left_join` imported but unused
+ latch/__init__.py:13:5: F401 `latch.functions.operators.left_join` imported but unused; consider removing, adding to `__all__`, or using a redundant alias
- latch/__init__.py:14:5: F401 [*] `latch.functions.operators.outer_join` imported but unused
+ latch/__init__.py:14:5: F401 `latch.functions.operators.outer_join` imported but unused; consider removing, adding to `__all__`, or using a redundant alias
- latch/__init__.py:15:5: F401 [*] `latch.functions.operators.right_join` imported but unused
+ latch/__init__.py:15:5: F401 `latch.functions.operators.right_join` imported but unused; consider removing, adding to `__all__`, or using a redundant alias
- latch/__init__.py:17:41: F401 [*] `latch.resources.conditional.create_conditional_section` imported but unused
+ latch/__init__.py:17:41: F401 `latch.resources.conditional.create_conditional_section` imported but unused; consider removing, adding to `__all__`, or using a redundant alias
- latch/__init__.py:18:39: F401 [*] `latch.resources.map_tasks.map_task` imported but unused
+ latch/__init__.py:18:39: F401 `latch.resources.map_tasks.map_task` imported but unused; consider removing, adding to `__all__`, or using a redundant alias
- latch/__init__.py:19:48: F401 [*] `latch.resources.reference_workflow.workflow_reference` imported but unused
+ latch/__init__.py:19:48: F401 `latch.resources.reference_workflow.workflow_reference` imported but unused; consider removing, adding to `__all__`, or using a redundant alias
- latch/__init__.py:21:5: F401 [*] `latch.resources.tasks.custom_task` imported but unused
+ latch/__init__.py:21:5: F401 `latch.resources.tasks.custom_task` imported but unused; consider removing, adding to `__all__`, or using a redundant alias
- latch/__init__.py:22:5: F401 [*] `latch.resources.tasks.custom_memory_optimized_task` imported but unused
+ latch/__init__.py:22:5: F401 `latch.resources.tasks.custom_memory_optimized_task` imported but unused; consider removing, adding to `__all__`, or using a redundant alias
- latch/__init__.py:23:5: F401 [*] `latch.resources.tasks.large_gpu_task` imported but unused
+ latch/__init__.py:23:5: F401 `latch.resources.tasks.large_gpu_task` imported but unused; consider removing, adding to `__all__`, or using a redundant alias
- latch/__init__.py:24:5: F401 [*] `latch.resources.tasks.large_task` imported but unused
+ latch/__init__.py:24:5: F401 `latch.resources.tasks.large_task` imported but unused; consider removing, adding to `__all__`, or using a redundant alias
- latch/__init__.py:25:5: F401 [*] `latch.resources.tasks.medium_task` imported but unused
+ latch/__init__.py:25:5: F401 `latch.resources.tasks.medium_task` imported but unused; consider removing, adding to `__all__`, or using a redundant alias
- latch/__init__.py:26:5: F401 [*] `latch.resources.tasks.small_gpu_task` imported but unused
+ latch/__init__.py:26:5: F401 `latch.resources.tasks.small_gpu_task` imported but unused; consider removing, adding to `__all__`, or using a redundant alias
- latch/__init__.py:27:5: F401 [*] `latch.resources.tasks.small_task` imported but unused
+ latch/__init__.py:27:5: F401 `latch.resources.tasks.small_task` imported but unused; consider removing, adding to `__all__`, or using a redundant alias
- latch/__init__.py:29:38: F401 [*] `latch.resources.workflow.workflow` imported but unused
+ latch/__init__.py:29:38: F401 `latch.resources.workflow.workflow` imported but unused; consider removing, adding to `__all__`, or using a redundant alias
- latch/__init__.py:7:38: F401 [*] `latch.functions.messages.message` imported but unused
+ latch/__init__.py:7:38: F401 `latch.functions.messages.message` imported but unused; consider removing, adding to `__all__`, or using a redundant alias
- latch/__init__.py:9:5: F401 [*] `latch.functions.operators.combine` imported but unused
+ latch/__init__.py:9:5: F401 `latch.functions.operators.combine` imported but unused; consider removing, adding to `__all__`, or using a redundant alias
- latch/types/__init__.py:10:5: F401 [*] `latch.types.metadata.LatchMetadata` imported but unused
+ latch/types/__init__.py:10:5: F401 `latch.types.metadata.LatchMetadata` imported but unused; consider removing, adding to `__all__`, or using a redundant alias
- latch/types/__init__.py:11:5: F401 [*] `latch.types.metadata.LatchParameter` imported but unused
+ latch/types/__init__.py:11:5: F401 `latch.types.metadata.LatchParameter` imported but unused; consider removing, adding to `__all__`, or using a redundant alias
- latch/types/__init__.py:12:5: F401 [*] `latch.types.metadata.LatchRule` imported but unused
+ latch/types/__init__.py:12:5: F401 `latch.types.metadata.LatchRule` imported but unused; consider removing, adding to `__all__`, or using a redundant alias
- latch/types/__init__.py:13:5: F401 [*] `latch.types.metadata.Params` imported but unused
+ latch/types/__init__.py:13:5: F401 `latch.types.metadata.Params` imported but unused; consider removing, adding to `__all__`, or using a redundant alias
- latch/types/__init__.py:14:5: F401 [*] `latch.types.metadata.Section` imported but unused
+ latch/types/__init__.py:14:5: F401 `latch.types.metadata.Section` imported but unused; consider removing, adding to `__all__`, or using a redundant alias
... 52 additional changes omitted for project

Changes by rule (1 rules affected)

code total + violation - violation + fix - fix
F401 104 52 52 0 0

Base automatically changed from zb/f401-init-test to main March 12, 2024 18:30
@charliermarsh
Copy link
Member

What's the motivation for retaining the diagnostic but not offering the fix? I'm generally not a fan of that pattern -- it ends up being somewhat user-unfriendly.

@zanieb
Copy link
Member Author

zanieb commented Mar 12, 2024

@charliermarsh I think the fix is more than unsafe, I think it's wrong. I'd prefer to offer a different fix: inclusion in __all__ if present in the file, otherwise a redundant alias. Until then, I'd like the fix to be off but am making use of the existing setting for people to recover the current behavior.

@charliermarsh
Copy link
Member

Should we be raising the diagnostic at all then?

@zanieb
Copy link
Member Author

zanieb commented Mar 12, 2024

@charliermarsh I think having a diagnostic without an automated fix is totally normal?

@zanieb
Copy link
Member Author

zanieb commented Mar 12, 2024

To be clear: I think in the future we should remove the setting entirely and always offer a safe fix in __init__ files.

@charliermarsh
Copy link
Member

For context, this is a good example of what I want to avoid: #10238. This was really disruptive for users, we received multiple issues about it and changed it quickly: #10263.

@charliermarsh
Copy link
Member

That's obviously a different situation since we were introducing more diagnostics, but it made me generally even more cautious of the idea of gating fixes from users.

In this case, I'm mostly just wondering if we should flag this at all, since we have no idea if the import is unused.

@zanieb
Copy link
Member Author

zanieb commented Mar 12, 2024

I think that's a fair point. I think there's a great deal of value to this diagnostic but honestly it seems like it should be under a different rule code.

I think what I have here is still the best past forward for parity with the existing tool (i.e. PyCQA/pyflakes#471). It moves us towards better default behavior while allowing users to fully recover the existing behavior. I think we have a reasonable path towards additional improvements, I'm just not doing them here because I'm trying to do a small amount of work to close old pull requests and issues.

@AlexWaygood
Copy link
Member

AlexWaygood commented Mar 13, 2024

What if in __init__.py files, we did something similar to what autoflake does for unused imports? By default, autoflake only removes unused imports if they're from the standard library; you have to opt into more aggressive removals of imports.

I think it would be nice to carry on removing stdlib imports in particular, since they're really common, should be relatively easy to identify, and you're very rarely going to actually be trying to reexport a stdlib thing from some third-party package.

@zanieb
Copy link
Member Author

zanieb commented Mar 13, 2024

@AlexWaygood I'm all for that. Could we introduce it in a follow-up or do you think it's blocking?

@@ -90,7 +95,7 @@ impl Violation for UnusedImport {
}
Some(UnusedImportContext::Init) => {
format!(
"`{name}` imported but unused; consider adding to `__all__` or using a redundant alias"
"`{name}` imported but unused; consider removing, adding to `__all__`, or using a redundant alias"
Copy link
Member

Choose a reason for hiding this comment

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

I'd usually refer to this as an "explicit reexport" rather than "redundant alias", FWIW, but "redundant alias" is probably clearer for people unfamiliar with the idea

Copy link
Member Author

Choose a reason for hiding this comment

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

"redundant alias" is the term used in Pyright which is usually my reference for this

Copy link
Member

@AlexWaygood AlexWaygood left a comment

Choose a reason for hiding this comment

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

@AlexWaygood I'm all for that. Could we introduce it in a follow-up or do you think it's blocking?

I think it can be done as a followup (but let's open an issue to track it, since I agree with @charliermarsh that we should do our best to provide autofixes for errors whenever possible).

This LGTM; I agree removing imports automatically from __init__.py files seems too risky for it to be done as a safe fix. Left some minor comments above.

@charliermarsh
Copy link
Member

To clarify: does that mean we wouldn't flag F401 for non-standard library imports in __init__.py, or we'd flag in either case but would only add the fix for standard-library?

@zanieb
Copy link
Member Author

zanieb commented Mar 13, 2024

does that mean we wouldn't flag F401 for non-standard library imports in init.py, or we'd flag in either case but would only add the fix for standard-library?

I think the ideal behavior is:

  1. Always flag unused imports in __init__.py
  2. Fix standard library imports by removing
  3. Fix other imports by appending to __all__ or using a redundant alias

We could consider not doing (1) for a subset until we have the fixes e.g. (3) but I would rather have the diagnostic without a fix than no diagnostic at all.

@AlexWaygood
Copy link
Member

To clarify: does that mean we wouldn't flag F401 for non-standard library imports in __init__.py, or we'd flag in either case but would only add the fix for standard-library?

I was suggesting the latter.

I think if you want to re-export in an __init__.py file, you should be forced to either use the "redundant alias" or add it to __all__. So I think it's correct for us to emit F401 errors on __init__.py files the same way as we would with any other files. But unlike with normal files, it's impossible for us to know whether the correct fix would be to remove the import or add it to __all__/mark it as explicit. So I think our default should be to only fix F401 errors in __init__.py files if they're unused imports from the stdlib.

@charliermarsh
Copy link
Member

Okay, seems reasonable to me.

@AlexWaygood
Copy link
Member

3. Fix other imports by appending to __all__ or using a redundant alias

Unless we can reliably distinguish between local imports and third-party dependencies, I'd honestly probably just not fix non-stdlib imports at all in an __init__.py file

@zanieb
Copy link
Member Author

zanieb commented Mar 13, 2024

Unless we can reliably distinguish between local imports and third-party dependencies,

I think we have reasonable support for this

@zanieb zanieb merged commit 7b3ee2d into main Mar 13, 2024
17 checks passed
@zanieb zanieb deleted the zb/F401-init branch March 13, 2024 17:58
@zanieb
Copy link
Member Author

zanieb commented Mar 13, 2024

Tracking follow-ups in

@AlexWaygood
Copy link
Member

Tracking follow-ups in

* [Add fix to remove unused standard library imports from `__init__.py` files #10390](https://github.com/astral-sh/ruff/issues/10390)

* [Add fix to re-export unused first-party imports in `__init__.py` files #10391](https://github.com/astral-sh/ruff/issues/10391)

Thanks!

@tylerlaprade
Copy link
Contributor

FWIW, I've had both F401 and F403 disabled in all __init__ files from the start. Does it make sense to ever even apply those rules to those files?

@zanieb
Copy link
Member Author

zanieb commented Mar 25, 2024

@tylerlaprade Yeah I think so. If you're a library author and you want to define an API, you should properly export types. If you don't, F401 will warn you. The fix of "removing" the import is generally going to be wrong for internal types though, instead, we want to suggest export of the types (#10391) so downstream tooling can read your interface. See the PyRight documentation on library interfaces for more details on exporting types.

@Hnasar
Copy link
Contributor

Hnasar commented Apr 19, 2024

@zanieb note that this is not just a pyright convention, but a Python typing convention in general:
https://typing.readthedocs.io/en/latest/source/libraries.html#library-interface-public-and-private-symbols

Looking forward to the two follow up improvements though! F401 is one of my faves 😄

That said, after updating, we were surprised to see this fix wasn't being applied, so we reverted to the pre-3.3 behavior with:

[tool.ruff.lint]
# Allow F401 to be honored in __init__.py files
ignore-init-module-imports = false
# Allow applying these unsafe fixes without the `--unsafe-fixes` flag
extend-safe-fixes = ["F401"]

@zanieb
Copy link
Member Author

zanieb commented Apr 19, 2024

Thanks for the feedback!

Glad to see that's documented clearly elsewhere, pyright was just my goto resource for a while :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
rule Implementing or modifying a lint rule
Projects
None yet
Development

Successfully merging this pull request may close these issues.

F401: Improve message for attempted re-export of symbols in __init__ files
5 participants