├── flake8_pyi ├── __init__.py ├── checker.py └── errors.py ├── .github ├── ISSUE_TEMPLATE │ ├── bug.md │ ├── feature.md │ └── docs.md ├── workflows │ ├── publish.yml │ ├── typeshed_primer_comment.yml │ ├── check.yml │ └── typeshed_primer.yml └── scripts │ ├── typeshed_primer_download_errors.js │ └── typeshed_primer_post_comment.js ├── tests ├── pep646_py311.pyi ├── del.pyi ├── sysplatform.pyi ├── disabled_by_default.pyi ├── F822.pyi ├── comparisons.pyi ├── emptyclasses.pyi ├── names_requiring_values.pyi ├── single_element_tuples.pyi ├── override.pyi ├── never_vs_noreturn.pyi ├── protocol_arg.pyi ├── literals.pyi ├── emptyfunctions.pyi ├── incomplete.pyi ├── calls.pyi ├── type_comments.pyi ├── typevar.pyi ├── test_pyi_files.py ├── pep604_union_types.pyi ├── pep570.pyi ├── sysversioninfo.pyi ├── quotes.pyi ├── pep695_py312.pyi ├── unused_things.pyi ├── aliases.pyi ├── exit_methods.pyi ├── defaults.pyi ├── attribute_annotations.pyi ├── union_duplicates.pyi ├── classdefs.pyi └── imports.pyi ├── .editorconfig ├── LICENSE ├── .gitignore ├── .pre-commit-config.yaml ├── .flake8 ├── CONTRIBUTING.md ├── README.md ├── pyproject.toml ├── ERRORCODES.md └── CHANGELOG.md /flake8_pyi/__init__.py: -------------------------------------------------------------------------------- 1 | from .checker import PyiTreeChecker 2 | 3 | __all__ = ["PyiTreeChecker"] 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Report a bug 4 | labels: type-bug 5 | --- 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest a new feature 4 | labels: type-feature 5 | --- 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/docs.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Documentation 3 | about: Report an issue with our docs 4 | labels: type-documentation 5 | --- 6 | -------------------------------------------------------------------------------- /tests/pep646_py311.pyi: -------------------------------------------------------------------------------- 1 | # flags: --extend-select=Y090 2 | import typing 3 | 4 | _Ts = typing.TypeVarTuple("_Ts") 5 | 6 | e: tuple[*_Ts] 7 | f: typing.Tuple[*_Ts] # Y022 Use "tuple[Foo, Bar]" instead of "typing.Tuple[Foo, Bar]" (PEP 585 syntax) 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*.{py,pyi,rst,md,yml,yaml,toml,json}] 4 | trim_trailing_whitespace = true 5 | insert_final_newline = true 6 | indent_style = space 7 | 8 | [*.{py,pyi,toml,json}] 9 | indent_size = 4 10 | 11 | [*.{yml,yaml}] 12 | indent_size = 2 13 | -------------------------------------------------------------------------------- /tests/del.pyi: -------------------------------------------------------------------------------- 1 | # flags: --extend-ignore=Y037 2 | from typing import TypeAlias, Union 3 | 4 | ManyStr: TypeAlias = list[EitherStr] 5 | EitherStr: TypeAlias = Union[str, bytes] 6 | 7 | def function(accepts: EitherStr) -> None: ... 8 | del EitherStr # private name, not exported 9 | -------------------------------------------------------------------------------- /tests/sysplatform.pyi: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | if sys.platform > 3: ... # Y007 Unrecognized sys.platform check 4 | if sys.platform == 10.12: ... # Y007 Unrecognized sys.platform check 5 | if sys.platform == 'linus': ... # Y008 Unrecognized platform "linus" 6 | if sys.platform != 'linux': ... 7 | if sys.platform == 'win32': ... 8 | -------------------------------------------------------------------------------- /tests/disabled_by_default.pyi: -------------------------------------------------------------------------------- 1 | # This test file checks that disabled-by-default error codes aren't triggered, 2 | # unless they're explicitly enabled 3 | from typing import ( # Y022 Use "tuple[Foo, Bar]" instead of "typing.Tuple[Foo, Bar]" (PEP 585 syntax) 4 | Tuple, 5 | ) 6 | 7 | # These would trigger Y090, but it's disabled by default 8 | x: tuple[int] 9 | y: Tuple[str] 10 | -------------------------------------------------------------------------------- /tests/F822.pyi: -------------------------------------------------------------------------------- 1 | # This checks that pyflakes emits F822 errors where appropriate, 2 | # but doesn't emit false positives when checking stub files. 3 | # flake8-pyi's monkeypatching of pyflakes impacts the way this check works. 4 | 5 | __all__ = ["a", "b", "c", "Klass", "d", "e"] # F822 undefined name 'e' in __all__ 6 | 7 | a: int 8 | b: int = 42 9 | c: int = ... 10 | class Klass: ... 11 | def d(): ... 12 | -------------------------------------------------------------------------------- /tests/comparisons.pyi: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | if sys.version == 'Python 2.7.10': ... # Y002 If test must be a simple comparison against sys.platform or sys.version_info 4 | if 'linux' == sys.platform: ... # Y002 If test must be a simple comparison against sys.platform or sys.version_info 5 | if hasattr(sys, 'maxint'): ... # Y002 If test must be a simple comparison against sys.platform or sys.version_info 6 | if sys.maxsize == 42: ... # Y002 If test must be a simple comparison against sys.platform or sys.version_info 7 | -------------------------------------------------------------------------------- /tests/emptyclasses.pyi: -------------------------------------------------------------------------------- 1 | class EmptyClass: ... 2 | 3 | class PassingEmptyClass: 4 | pass # Y009 Empty body should contain "...", not "pass" 5 | 6 | class PassingNonEmptyClass: 7 | x: int 8 | pass # Y012 Class body must not contain "pass" 9 | 10 | class PassingNonEmptyClass2: 11 | pass # Y012 Class body must not contain "pass" 12 | x: int 13 | 14 | class EllipsisNonEmptyClass: 15 | x: int 16 | ... # Y013 Non-empty class body must not contain "..." 17 | 18 | class EllipsisNonEmptyClass2: 19 | ... # Y013 Non-empty class body must not contain "..." 20 | x: int 21 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | # Based on 2 | # https://packaging.python.org/guides/publishing-package-distribution-releases-using-github-actions-ci-cd-workflows/ 3 | 4 | name: Publish Python distributions to PyPI 5 | 6 | on: push 7 | 8 | permissions: 9 | contents: read 10 | 11 | jobs: 12 | build-n-publish: 13 | name: Build and publish Python distributions to PyPI 14 | runs-on: ubuntu-latest 15 | permissions: 16 | # needed for PyPI trusted publishing 17 | id-token: write 18 | steps: 19 | - uses: actions/checkout@v4 20 | - uses: astral-sh/setup-uv@v6 21 | - run: uv build 22 | - name: Publish distribution to PyPI 23 | if: startsWith(github.ref, 'refs/tags') 24 | run: uv publish 25 | -------------------------------------------------------------------------------- /.github/scripts/typeshed_primer_download_errors.js: -------------------------------------------------------------------------------- 1 | module.exports = async ({ github, context }) => { 2 | const fs = require('fs') 3 | 4 | const artifacts = await github.rest.actions.listWorkflowRunArtifacts({ 5 | owner: context.repo.owner, 6 | repo: context.repo.repo, 7 | run_id: context.payload.workflow_run.id, 8 | }) 9 | const [matchArtifact] = artifacts.data.artifacts.filter((artifact) => 10 | artifact.name == "typeshed_primer_errors") 11 | const download = await github.rest.actions.downloadArtifact({ 12 | owner: context.repo.owner, 13 | repo: context.repo.repo, 14 | artifact_id: matchArtifact.id, 15 | archive_format: "zip", 16 | }) 17 | 18 | fs.writeFileSync("errors.zip", Buffer.from(download.data)); 19 | } 20 | -------------------------------------------------------------------------------- /tests/names_requiring_values.pyi: -------------------------------------------------------------------------------- 1 | __all__: list[str] # Y035 "__all__" in a stub file must have a value, as it has the same semantics as "__all__" at runtime. 2 | __all__: list[str] = ["foo", "bar", "baz"] 3 | __all__ = ["foo", "bar", "baz"] 4 | __match_args__: list[int] 5 | __slots__: set[int] 6 | 7 | foo: int = ... 8 | bar: str = ... 9 | baz: list[set[bytes]] = ... 10 | 11 | class Foo: 12 | __all__: list[str] 13 | __match_args__: tuple[str, ...] # Y035 "__match_args__" in a stub file must have a value, as it has the same semantics as "__match_args__" at runtime. 14 | __slots__: tuple[str, ...] # Y035 "__slots__" in a stub file must have a value, as it has the same semantics as "__slots__" at runtime. 15 | 16 | class Bar: 17 | __match_args__ = ('x', 'y') 18 | x: int 19 | y: str 20 | -------------------------------------------------------------------------------- /tests/single_element_tuples.pyi: -------------------------------------------------------------------------------- 1 | # flags: --extend-select=Y090 2 | import builtins 3 | import typing 4 | 5 | _Ts = typing.TypeVarTuple("_Ts") 6 | 7 | a: tuple[int] # Y090 "tuple[int]" means "a tuple of length 1, in which the sole element is of type 'int'". Perhaps you meant "tuple[int, ...]"? 8 | b: typing.Tuple[builtins.str] # Y022 Use "tuple[Foo, Bar]" instead of "typing.Tuple[Foo, Bar]" (PEP 585 syntax) # Y090 "typing.Tuple[builtins.str]" means "a tuple of length 1, in which the sole element is of type 'builtins.str'". Perhaps you meant "typing.Tuple[builtins.str, ...]"? 9 | c: tuple[int, ...] 10 | d: typing.Tuple[builtins.str, builtins.complex] # Y022 Use "tuple[Foo, Bar]" instead of "typing.Tuple[Foo, Bar]" (PEP 585 syntax) 11 | e: tuple[typing.Unpack[_Ts]] 12 | f: typing.Tuple[typing.Unpack[_Ts]] # Y022 Use "tuple[Foo, Bar]" instead of "typing.Tuple[Foo, Bar]" (PEP 585 syntax) 13 | -------------------------------------------------------------------------------- /tests/override.pyi: -------------------------------------------------------------------------------- 1 | import typing 2 | import typing as t 3 | from typing import override, override as over 4 | 5 | import typing_extensions 6 | 7 | class Foo: 8 | def f(self) -> None: ... 9 | def g(self) -> None: ... 10 | def h(self) -> None: ... 11 | def j(self) -> None: ... 12 | def k(self) -> None: ... 13 | 14 | class Bar(Foo): 15 | @override # Y068 Do not use "@override" in stub files. 16 | def f(self) -> None: ... 17 | @typing.override # Y068 Do not use "@override" in stub files. 18 | def g(self) -> None: ... 19 | @t.override # Ideally we'd catch this too, but the infrastructure is not in place. 20 | def h(self) -> None: ... 21 | @over # Ideally we'd catch this too, but the infrastructure is not in place. 22 | def j(self) -> None: ... 23 | @typing_extensions.override # Y068 Do not use "@override" in stub files. 24 | def k(self) -> None: ... 25 | -------------------------------------------------------------------------------- /tests/never_vs_noreturn.pyi: -------------------------------------------------------------------------------- 1 | import typing 2 | from typing import NoReturn 3 | 4 | import typing_extensions 5 | from typing_extensions import Never 6 | 7 | def badfunc0(arg: NoReturn) -> None: ... # Y050 Use "typing_extensions.Never" instead of "NoReturn" for argument annotations 8 | def badfunc1(*args: typing.NoReturn) -> None: ... # Y050 Use "typing_extensions.Never" instead of "NoReturn" for argument annotations 9 | def badfunc2(**kwargs: typing_extensions.NoReturn) -> None: ... # Y050 Use "typing_extensions.Never" instead of "NoReturn" for argument annotations # Y023 Use "typing.NoReturn" instead of "typing_extensions.NoReturn" 10 | def badfunc3(*, arg: NoReturn) -> None: ... # Y050 Use "typing_extensions.Never" instead of "NoReturn" for argument annotations 11 | 12 | def goodfunc0(arg: Never) -> None: ... 13 | def goodfunc1(*args: typing.Never) -> None: ... 14 | def goodfunc2(**kwargs: typing_extensions.Never) -> None: ... 15 | def goodfunc3(*, arg: Never) -> None: ... 16 | -------------------------------------------------------------------------------- /tests/protocol_arg.pyi: -------------------------------------------------------------------------------- 1 | # flags: --extend-select=Y091 2 | from typing import Protocol 3 | 4 | class P(Protocol): 5 | def method1(self, arg: int) -> None: ... # Y091 Argument "arg" to protocol method "method1" should probably not be positional-or-keyword. Make it positional-only, since usually you don't want to mandate a specific argument name 6 | def method2(self, arg: str, /) -> None: ... 7 | def method3(self, *, arg: str) -> None: ... 8 | def method4(self, arg: int, /) -> None: ... 9 | def method5(self, arg: int, /, *, foo: str) -> None: ... 10 | # Ensure Y091 recognizes this as pos-only for the benefit of users still 11 | # using the old syntax. 12 | def method6(self, __arg: int) -> None: ... # Y063 Use PEP-570 syntax to indicate positional-only arguments 13 | @staticmethod 14 | def smethod1(arg: int) -> None: ... # Y091 Argument "arg" to protocol method "smethod1" should probably not be positional-or-keyword. Make it positional-only, since usually you don't want to mandate a specific argument name 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Łukasz Langa 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/scripts/typeshed_primer_post_comment.js: -------------------------------------------------------------------------------- 1 | module.exports = async ({ github, context }) => { 2 | const fs = require('fs') 3 | const DIFF_LINE = { ">": true, "<": true } 4 | 5 | let data = fs.readFileSync('errors_diff.txt', { encoding: 'utf8' }) 6 | // Only keep diff lines 7 | data = data 8 | .split("\n") 9 | .filter(line => line[0] in DIFF_LINE) 10 | .join("\n") 11 | // posting comment fails if too long, so truncate 12 | if (data.length > 30000) { 13 | let truncated_data = data.substring(0, 30000) 14 | let lines_truncated = data.split('\n').length - truncated_data.split('\n').length 15 | data = truncated_data + `\n\n... (truncated ${lines_truncated} lines) ...\n` 16 | } 17 | 18 | const body = data.trim() 19 | ? '⚠ Flake8 diff showing the effect of this PR on typeshed: \n```diff\n' + data + '\n```' 20 | : 'This change has no effect on typeshed. 🤖🎉' 21 | const issue_number = parseInt(fs.readFileSync("pr_number.txt", { encoding: "utf8" })) 22 | await github.rest.issues.createComment({ 23 | issue_number, 24 | owner: context.repo.owner, 25 | repo: context.repo.repo, 26 | body 27 | }) 28 | 29 | return issue_number 30 | } 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | *.egg-info/ 23 | .installed.cfg 24 | *.egg 25 | 26 | # PyInstaller 27 | # Usually these files are written by a python script from a template 28 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 29 | *.manifest 30 | *.spec 31 | 32 | # Installer logs 33 | pip-log.txt 34 | pip-delete-this-directory.txt 35 | 36 | # Unit test / coverage reports 37 | htmlcov/ 38 | .tox/ 39 | .coverage 40 | .coverage.* 41 | .cache 42 | nosetests.xml 43 | coverage.xml 44 | *,cover 45 | .hypothesis/ 46 | 47 | # Translations 48 | *.mo 49 | *.pot 50 | 51 | # Django stuff: 52 | *.log 53 | 54 | # Sphinx documentation 55 | docs/_build/ 56 | 57 | # PyBuilder 58 | target/ 59 | 60 | #Ipython Notebook 61 | .ipynb_checkpoints 62 | 63 | # virtualenvs 64 | /env*/ 65 | /venv*/ 66 | /.env*/ 67 | /.venv*/ 68 | 69 | # IDEs 70 | .vscode/ 71 | 72 | # Currently more complexity than it's worth for this repo. 73 | uv.lock 74 | -------------------------------------------------------------------------------- /.github/workflows/typeshed_primer_comment.yml: -------------------------------------------------------------------------------- 1 | name: Post typeshed_primer comment 2 | 3 | on: 4 | workflow_run: 5 | workflows: 6 | - typeshed_primer 7 | types: 8 | - completed 9 | 10 | permissions: 11 | contents: read 12 | pull-requests: write 13 | 14 | jobs: 15 | comment: 16 | name: Comment PR from typeshed_primer 17 | if: ${{ github.event.workflow_run.conclusion == 'success' }} 18 | runs-on: ubuntu-latest 19 | steps: 20 | - uses: actions/checkout@v4 21 | - name: Download errors 22 | uses: actions/github-script@v7 23 | with: 24 | script: await require('.github/scripts/typeshed_primer_download_errors.js')({github, context}) 25 | - run: unzip errors.zip 26 | - name: Post comment 27 | id: post-comment 28 | uses: actions/github-script@v7 29 | with: 30 | github-token: ${{secrets.GITHUB_TOKEN}} 31 | script: return await require('.github/scripts/typeshed_primer_post_comment.js')({github, context}) 32 | - name: Hide old comments 33 | # v0.4.0 34 | uses: kanga333/comment-hider@c12bb20b48aeb8fc098e35967de8d4f8018fffdf 35 | with: 36 | github_token: ${{ secrets.GITHUB_TOKEN }} 37 | leave_visible: 1 38 | issue_number: ${{ steps.post-comment.outputs.result }} 39 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v6.0.0 # must match pyproject.toml 4 | hooks: 5 | - id: trailing-whitespace 6 | - id: end-of-file-fixer 7 | - id: check-yaml 8 | - id: check-toml 9 | - id: check-merge-conflict 10 | - id: check-case-conflict 11 | - id: mixed-line-ending 12 | - id: name-tests-test 13 | args: [--pytest-test-first] 14 | - repo: https://github.com/psf/black-pre-commit-mirror 15 | rev: 25.9.0 # must match pyproject.toml 16 | hooks: 17 | - id: black 18 | language_version: python3.9 19 | - repo: https://github.com/pycqa/isort 20 | rev: 6.1.0 # must match pyproject.toml 21 | hooks: 22 | - id: isort 23 | name: isort (python) 24 | - repo: https://github.com/abravalheri/validate-pyproject 25 | rev: v0.24.1 26 | hooks: 27 | - id: validate-pyproject 28 | - repo: https://github.com/python-jsonschema/check-jsonschema 29 | rev: 0.34.0 30 | hooks: 31 | - id: check-github-workflows 32 | - repo: https://github.com/rhysd/actionlint 33 | rev: v1.7.7 34 | hooks: 35 | - id: actionlint 36 | 37 | ci: 38 | autofix_commit_msg: "[pre-commit.ci] auto fixes from pre-commit.com hooks" 39 | autofix_prs: true 40 | autoupdate_commit_msg: "[pre-commit.ci] pre-commit autoupdate" 41 | autoupdate_schedule: quarterly 42 | submodules: false 43 | -------------------------------------------------------------------------------- /tests/literals.pyi: -------------------------------------------------------------------------------- 1 | from typing import Final, Literal 2 | 3 | Literal[None] # Y061 None inside "Literal[]" expression. Replace with "None" 4 | Literal[True, None] # Y061 None inside "Literal[]" expression. Replace with "Literal[True] | None" 5 | 6 | Literal[True, True] # Y062 Duplicate "Literal[]" member "True" 7 | Literal[True, True, True] # Y062 Duplicate "Literal[]" member "True" 8 | Literal[True, False, True, False] # Y062 Duplicate "Literal[]" member "True" # Y062 Duplicate "Literal[]" member "False" 9 | 10 | ### 11 | # The following rules here are slightly subtle, 12 | # but make sense when it comes to giving the best suggestions to users of flake8-pyi. 13 | ### 14 | 15 | # If Y061 and Y062 both apply, but all the duplicate members are None, 16 | # only emit Y061... 17 | Literal[None, None] # Y061 None inside "Literal[]" expression. Replace with "None" 18 | Literal[1, None, "foo", None] # Y061 None inside "Literal[]" expression. Replace with "Literal[1, 'foo'] | None" 19 | 20 | # ... but if Y061 and Y062 both apply 21 | # and there are no None members in the Literal[] slice, 22 | # only emit Y062: 23 | Literal[None, True, None, True] # Y062 Duplicate "Literal[]" member "True" 24 | 25 | x: Final[Literal[True]] # Y064 Use "x: Final = True" instead of "x: Final[Literal[True]]" 26 | # If Y061 and Y064 both apply, only emit Y064 27 | y: Final[Literal[None]] # Y064 Use "y: Final = None" instead of "y: Final[Literal[None]]" 28 | z: Final[Literal[True, False]] 29 | -------------------------------------------------------------------------------- /tests/emptyfunctions.pyi: -------------------------------------------------------------------------------- 1 | def empty(x: int) -> float: ... 2 | 3 | def passing(x: int) -> float: 4 | pass # Y009 Empty body should contain "...", not "pass" 5 | 6 | def raising(x: int) -> float: 7 | raise TypeError # Y010 Function body must contain only "..." 8 | 9 | class BadClass: 10 | def __init__(self, x: int) -> None: 11 | self.x = x # Y010 Function body must contain only "..." 12 | 13 | async def async_assign(self, x: int) -> None: 14 | self.x = x # Y010 Function body must contain only "..." 15 | 16 | class WorseClass: 17 | def __init__(self, b: BadClass, x: int) -> None: 18 | b.x = x # Y010 Function body must contain only "..." 19 | 20 | def returning(x: int) -> float: 21 | return x / 2 # Y010 Function body must contain only "..." 22 | 23 | def multiple_ellipses(x: int) -> float: 24 | ... 25 | ... # Y048 Function body should contain exactly one statement 26 | 27 | async def empty_async(x: int) -> float: ... 28 | 29 | async def passing_async(x: int) -> float: 30 | pass # Y009 Empty body should contain "...", not "pass" 31 | 32 | async def raising_async(x: int) -> float: 33 | raise TypeError # Y010 Function body must contain only "..." 34 | 35 | async def returning_async(x: int) -> float: 36 | return x / 2 # Y010 Function body must contain only "..." 37 | 38 | async def multiple_ellipses_async(x: int) -> float: 39 | ... 40 | ... # Y048 Function body should contain exactly one statement 41 | -------------------------------------------------------------------------------- /.flake8: -------------------------------------------------------------------------------- 1 | # This is an example .flake8 config, used when developing flake8-pyi itself. 2 | # 3 | # The following rule now goes against PEP8: 4 | # W503 line break before binary operator 5 | # 6 | # The following rules are incompatible with or largely enforced by black: 7 | # B950 Line too long (flake8-bugbear equivalent of E501) 8 | # E203 whitespace before ':' -- .py files only 9 | # E501 line too long 10 | # W291 trailing whitespace -- .py files only 11 | # W293 blank line contains whitespace -- .py files only 12 | # 13 | # Some rules are considered irrelevant to stub files: 14 | # E301 expected 1 blank line 15 | # E302 expected 2 blank lines 16 | # E305 expected 2 blank lines 17 | # E701 multiple statements on one line (colon) -- disallows "..." on the same line 18 | # E704 multiple statements on one line (def) -- disallows function body on the same line as the def 19 | # 20 | # flake8-bugbear rules that cause too many false positives: 21 | # B905 "`zip()` without an explicit `strict=True` parameter -- 22 | # the `strict` parameter was introduced in Python 3.10; we support Python 3.9 23 | # B907 "Use !r inside f-strings instead of manual quotes" -- 24 | # produces false positives if you're surrounding things with double quotes 25 | 26 | [flake8] 27 | extend-select = B9 28 | max-line-length = 80 29 | max-complexity = 12 30 | noqa-require-code = true 31 | per-file-ignores = 32 | *.py: B905, B907, B950, E203, E501, W503, W291, W293 33 | *.pyi: B, E301, E302, E305, E501, E701, E704, F821, W503 34 | -------------------------------------------------------------------------------- /flake8_pyi/checker.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import ast 4 | import logging 5 | import re 6 | from dataclasses import dataclass 7 | from typing import ClassVar, Iterator 8 | 9 | from flake8.options.manager import OptionManager 10 | 11 | from . import errors, visitor 12 | 13 | LOG = logging.getLogger("flake8.pyi") 14 | 15 | 16 | _TYPE_COMMENT_REGEX = re.compile(r"#\s*type:\s*(?!\s?ignore)([^#]+)(\s*#.*?)?$") 17 | 18 | 19 | def _check_for_type_comments(lines: list[str]) -> Iterator[errors.Error]: 20 | for lineno, line in enumerate(lines, start=1): 21 | cleaned_line = line.strip() 22 | 23 | if cleaned_line.startswith("#"): 24 | continue 25 | 26 | if match := _TYPE_COMMENT_REGEX.search(cleaned_line): 27 | type_comment = match.group(1).strip() 28 | 29 | try: 30 | ast.parse(type_comment) 31 | except SyntaxError: 32 | continue 33 | 34 | yield errors.Error(lineno, 0, errors.Y033, PyiTreeChecker) 35 | 36 | 37 | @dataclass 38 | class PyiTreeChecker: 39 | name: ClassVar[str] = "flake8-pyi" 40 | tree: ast.Module 41 | lines: list[str] 42 | filename: str = "(none)" 43 | 44 | def run(self) -> Iterator[errors.Error]: 45 | if self.filename.endswith(".pyi"): 46 | yield from _check_for_type_comments(self.lines) 47 | yield from visitor.PyiVisitor(filename=self.filename).run(self.tree) 48 | 49 | @staticmethod 50 | def add_options(parser: OptionManager) -> None: 51 | parser.parser.set_defaults(filename="*.py,*.pyi") 52 | parser.extend_default_ignore(errors.DISABLED_BY_DEFAULT) 53 | -------------------------------------------------------------------------------- /.github/workflows/check.yml: -------------------------------------------------------------------------------- 1 | name: tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | paths-ignore: 9 | - "**/*.md" 10 | workflow_dispatch: 11 | 12 | permissions: 13 | contents: read 14 | 15 | env: 16 | PIP_DISABLE_PIP_VERSION_CHECK: 1 17 | FORCE_COLOR: 1 18 | PY_COLORS: 1 # Recognized by the `py` package, dependency of `pytest` 19 | TERM: xterm-256color # needed for FORCE_COLOR to work on mypy on Ubuntu, see https://github.com/python/mypy/issues/13817 20 | 21 | concurrency: 22 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 23 | cancel-in-progress: true 24 | 25 | jobs: 26 | mypy: 27 | name: mypy 28 | runs-on: ubuntu-latest 29 | timeout-minutes: 5 30 | strategy: 31 | matrix: 32 | python-version: ["3.9", "3.14"] 33 | fail-fast: false 34 | steps: 35 | - uses: actions/checkout@v4 36 | - uses: astral-sh/setup-uv@v6 37 | - run: | 38 | uv run --python=3.13 --group=dev mypy --python-version=${{ matrix.python-version }} 39 | 40 | flake8: 41 | name: flake8 42 | runs-on: ubuntu-latest 43 | timeout-minutes: 5 44 | steps: 45 | - uses: actions/checkout@v4 46 | - uses: astral-sh/setup-uv@v6 47 | - run: | 48 | uv run --python=3.13 --group=dev flake8 $(git ls-files | grep 'py$') --color=always 49 | 50 | tests: 51 | name: pytest suite 52 | timeout-minutes: 10 53 | runs-on: ${{ matrix.os }} 54 | strategy: 55 | matrix: 56 | os: ["ubuntu-latest", "macos-latest", "windows-latest"] 57 | python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] 58 | fail-fast: false 59 | steps: 60 | - uses: actions/checkout@v4 61 | - uses: astral-sh/setup-uv@v6 62 | - run: | 63 | uv run --python=${{ matrix.python-version }} --group=dev pytest -vv 64 | -------------------------------------------------------------------------------- /tests/incomplete.pyi: -------------------------------------------------------------------------------- 1 | from _typeshed import Incomplete 2 | from typing_extensions import TypeAlias 3 | 4 | IncompleteAlias: TypeAlias = Incomplete # ok 5 | 6 | att: Incomplete # ok 7 | 8 | def ok(x: Incomplete | None) -> list[Incomplete]: ... 9 | def aliased(x: IncompleteAlias) -> IncompleteAlias: ... # ok 10 | def err1( 11 | x: Incomplete, # Y065 Leave parameter "x" unannotated rather than using "Incomplete" 12 | ) -> None: ... 13 | def err2() -> ( 14 | Incomplete # Y065 Leave return type unannotated rather than using "Incomplete" 15 | ): ... 16 | 17 | class Foo1: 18 | att: Incomplete 19 | def ok(self, x: Incomplete | None) -> list[Incomplete]: ... 20 | def err1( 21 | self, 22 | x: Incomplete, # Y065 Leave parameter "x" unannotated rather than using "Incomplete" 23 | ) -> None: ... 24 | def err2( 25 | self, 26 | ) -> ( 27 | Incomplete # Y065 Leave return type unannotated rather than using "Incomplete" 28 | ): ... 29 | def __getattr__( 30 | self, name: str 31 | ) -> Incomplete: ... # allowed in __getattr__ return type 32 | 33 | class Bar: 34 | def __getattr__( 35 | self, 36 | name: Incomplete, # Y065 Leave parameter "name" unannotated rather than using "Incomplete" 37 | ) -> Incomplete: ... 38 | 39 | def __getattr__(name: str) -> Incomplete: ... # allowed in __getattr__ return type 40 | 41 | def f1(x: Incomplete | None = None): ... # Y067 Use "=None" instead of "Incomplete | None = None" 42 | def f2(x: Incomplete | None): ... 43 | def f3(x: Incomplete | int = None): ... 44 | def f4(x: None = None): ... 45 | 46 | class Foo2: 47 | def f1(self, x: Incomplete | None = None): ... # Y067 Use "=None" instead of "Incomplete | None = None" 48 | def f2(self, x: Incomplete | None): ... 49 | def f3(self, x: Incomplete | int = None): ... 50 | def f4(self, x: None = None): ... 51 | 52 | a1: Incomplete | None = None 53 | a2: Incomplete | None 54 | a3: Incomplete | int 55 | a4: None = None 56 | -------------------------------------------------------------------------------- /tests/calls.pyi: -------------------------------------------------------------------------------- 1 | import typing 2 | from typing import NamedTuple, TypedDict 3 | 4 | import mypy_extensions 5 | import typing_extensions 6 | 7 | T = NamedTuple("T", [('foo', int), ('bar', str)]) # Y028 Use class-based syntax for NamedTuples 8 | U = typing.NamedTuple("U", [('baz', bytes)]) # Y028 Use class-based syntax for NamedTuples 9 | S = typing_extensions.NamedTuple("S", [('baz', bytes)]) # Y028 Use class-based syntax for NamedTuples 10 | 11 | class V(NamedTuple): 12 | foo: int 13 | bar: str 14 | 15 | # BAD TYPEDDICTS 16 | W = TypedDict("W", {'foo': str, 'bar': int}) # Y031 Use class-based syntax for TypedDicts where possible 17 | B = typing.TypedDict("B", {'foo': str, 'bar': int}) # Y031 Use class-based syntax for TypedDicts where possible 18 | WithTotal = typing_extensions.TypedDict("WithTotal", {'foo': str, 'bar': int}, total=False) # Y023 Use "typing.TypedDict" instead of "typing_extensions.TypedDict" # Y031 Use class-based syntax for TypedDicts where possible 19 | BB = mypy_extensions.TypedDict("BB", {'foo': str, 'bar': int}) # Y031 Use class-based syntax for TypedDicts where possible 20 | 21 | # we don't want these two to raise errors (type-checkers already do that for us), 22 | # we just want to make sure the plugin doesn't crash if it comes across an invalid TypedDict 23 | InvalidTypedDict = TypedDict("InvalidTypedDict", {7: 9, b"wot": [8, 3]}) 24 | WeirdThirdArg = TypedDict("WeirdThirdArg", {'foo': int, "wot": str}, "who knows?") 25 | 26 | # GOOD TYPEDDICTS 27 | C = typing.TypedDict("B", {'field has a space': list[int]}) 28 | D = typing_extensions.TypedDict("C", {'while': bytes, 'for': int}) # Y023 Use "typing.TypedDict" instead of "typing_extensions.TypedDict" 29 | E = TypedDict("D", {'[][]': dict[str, int]}) 30 | F = TypedDict("E", {'1': list[str], '2': str}) 31 | 32 | class ClassBased(TypedDict): 33 | foo: str 34 | bar: int 35 | 36 | __all__ = ["T", "U", "S"] 37 | __all__.append("W") # Y056 Calling ".append()" on "__all__" may not be supported by all type checkers (use += instead) 38 | __all__.extend(["B", "WithTotal"]) # Y056 Calling ".extend()" on "__all__" may not be supported by all type checkers (use += instead) 39 | __all__.remove("U") # Y056 Calling ".remove()" on "__all__" may not be supported by all type checkers (use += instead) 40 | -------------------------------------------------------------------------------- /tests/type_comments.pyi: -------------------------------------------------------------------------------- 1 | # flags: --extend-ignore=F401,F723,E261,E262 2 | # 3 | # F401: 'foo' imported but unused (collections.abc.Sequence is only used in a type comment -- flake8>=6 has dropped support for type comments) 4 | # F723: syntax error in type comment 5 | # E261: at least two spaces before inline comment 6 | # E262: inline comment should start with '# ' 7 | 8 | from collections.abc import Sequence 9 | from typing import TypeAlias 10 | 11 | A: TypeAlias = None # type: int # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int") 12 | B: TypeAlias = None # type: str # And here's an extra comment about why it's that type # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int") 13 | C: TypeAlias = None #type: int # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int") 14 | D: TypeAlias = None # type: int # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int") 15 | E: TypeAlias = None# type: int # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int") 16 | F: TypeAlias = None#type:int # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int") 17 | 18 | def func( 19 | arg1, # type: dict[str, int] # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int") 20 | arg2 # type: Sequence[bytes] # And here's some more info about this arg # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int") 21 | ): ... 22 | 23 | class Foo: 24 | Attr: TypeAlias = None # type: set[str] # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int") 25 | 26 | G: TypeAlias = None # type: ignore 27 | H: TypeAlias = None # type: ignore[attr-defined] 28 | I: TypeAlias = None #type: ignore 29 | J: TypeAlias = None # type: ignore 30 | K: TypeAlias = None# type: ignore 31 | L: TypeAlias = None#type:ignore 32 | 33 | # Whole line commented out # type: int 34 | M: TypeAlias = None # type: can't parse me! 35 | 36 | class Bar: 37 | N: TypeAlias = None # type: can't parse me either! 38 | # This whole line is commented out and indented # type: str 39 | -------------------------------------------------------------------------------- /.github/workflows/typeshed_primer.yml: -------------------------------------------------------------------------------- 1 | name: typeshed_primer 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - "flake8_pyi/**/*" 7 | - ".github/**/*" 8 | workflow_dispatch: 9 | 10 | permissions: 11 | contents: read 12 | 13 | env: 14 | PIP_DISABLE_PIP_VERSION_CHECK: 1 15 | 16 | concurrency: 17 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 18 | cancel-in-progress: true 19 | 20 | jobs: 21 | typeshed_primer: 22 | timeout-minutes: 5 23 | runs-on: ubuntu-latest 24 | steps: 25 | - name: Checkout flake8-pyi on target branch 26 | uses: actions/checkout@v4 27 | with: 28 | ref: ${{ github.base_ref }} 29 | path: old_plugin 30 | - name: Checkout flake8-pyi on PR branch 31 | uses: actions/checkout@v4 32 | with: 33 | path: new_plugin 34 | - name: Checkout typeshed 35 | uses: actions/checkout@v4 36 | with: 37 | repository: python/typeshed 38 | path: typeshed 39 | - name: Setup Python 40 | uses: actions/setup-python@v5 41 | with: 42 | python-version: "3.13" 43 | - name: Install uv 44 | run: curl -LsSf https://astral.sh/uv/install.sh | sh 45 | - run: uv pip install flake8-noqa --system 46 | # We cd so that "old_plugin"/"new_plugin"/typeshed" don't appear in the error path 47 | - name: flake8 typeshed using target branch 48 | run: | 49 | cd old_plugin 50 | uv pip install -e . --system 51 | cd ../typeshed 52 | flake8 --exit-zero --color never --output-file ../old_errors.txt 53 | - name: flake8 typeshed using PR branch 54 | run: | 55 | cd new_plugin 56 | uv pip install -e . --system --reinstall 57 | cd ../typeshed 58 | flake8 --exit-zero --color never --output-file ../new_errors.txt 59 | - name: Get diff between the two runs 60 | run: | 61 | echo ${{ github.event.pull_request.number }} | tee pr_number.txt 62 | diff old_errors.txt new_errors.txt | tee errors_diff.txt 63 | - name: Upload diff and PR number 64 | uses: actions/upload-artifact@v4 65 | with: 66 | name: typeshed_primer_errors 67 | path: | 68 | errors_diff.txt 69 | pr_number.txt 70 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to flake8-pyi 2 | 3 | Welcome! flake8-pyi started off as a project to help lint typeshed stubs, but aims to 4 | be a package that can be utilised by any user of Python stubs. Any PRs towards that 5 | end will be warmly received. 6 | 7 | 8 | ## Guide to the codebase 9 | 10 | The plugin consists of a single package: `flake8_pyi`. Most of the logic lives in the 11 | `flake8_pyi/visitor.py` file. Tests are run using `pytest`, and can be found in the `tests` 12 | folder. 13 | 14 | PRs that make user-visible changes should generally add a short description of the change 15 | to the `CHANGELOG.md` file in the repository root. 16 | 17 | 18 | ## Tests and formatting 19 | 20 | When you make a pull request, [pre-commit.ci](https://pre-commit.ci/) bots will 21 | automatically reformat your code using `black` and `isort`. GitHub Actions will 22 | also run the full test suite on your proposed changes. 23 | 24 | If you wish to (optionally) run the tests or format your code prior to submitting your PR, 25 | however, we advise setting up a virtual environment first: 26 | 27 | $ python3 -m venv env 28 | $ source env/bin/activate 29 | $ pip install -e . --group=dev 30 | 31 | To format your code with `isort` and `black`, run: 32 | 33 | $ isort flake8_pyi 34 | $ black flake8_pyi 35 | 36 | If you want, you can also run locally the commands that GitHub Actions runs. 37 | Look in `.github/workflows/` to find the commands. 38 | For example, to run tests: 39 | 40 | $ python3 -m pytest -vv 41 | 42 | To run the tests in a single test file, use the `-k` option. For example, to 43 | run all tests in `tests/quotes.pyi`: 44 | 45 | $ python3 -m pytest -vv -k quotes.pyi 46 | 47 | 48 | ## Making a release 49 | 50 | `flake8-pyi` uses calendar-based versioning. For example, the first 51 | release in January 2022 should be called 22.1.0, followed by 22.1.1. 52 | 53 | Releasing a new version is easy: 54 | 55 | - Make a PR that updates the version header in `CHANGELOG.md`. 56 | - Merge the PR and wait for tests to complete. 57 | - Draft a release using the GitHub UI. The tag name should be 58 | identical to the version (e.g., `22.1.0`) and the release notes 59 | should be copied from `CHANGELOG.md`. 60 | - A workflow will run and automatically upload the release to PyPI. 61 | If it doesn't work, check the Actions tab to see what went wrong. 62 | -------------------------------------------------------------------------------- /tests/typevar.pyi: -------------------------------------------------------------------------------- 1 | # flags: --extend-ignore=Y037 2 | import typing 3 | from typing import Annotated, ParamSpec, TypeVar, TypeVarTuple, Union 4 | 5 | import typing_extensions 6 | from _typeshed import Self 7 | 8 | T = TypeVar("T") # Y001 Name of private TypeVar must start with _ 9 | _T = typing.TypeVar("_T") # Y018 TypeVar "_T" is not used 10 | P = ParamSpec("P") # Y001 Name of private ParamSpec must start with _ 11 | _P = typing_extensions.ParamSpec("_P") # Y018 ParamSpec "_P" is not used 12 | Ts = TypeVarTuple("Ts") # Y001 Name of private TypeVarTuple must start with _ 13 | _Ts = TypeVarTuple("_Ts") # Y018 TypeVarTuple "_Ts" is not used 14 | 15 | _UsedTypeVar = TypeVar("_UsedTypeVar") 16 | def func(arg: _UsedTypeVar) -> _UsedTypeVar: ... 17 | 18 | _UsedInBinOp = TypeVar("_UsedInBinOp", bound=str) 19 | def func2(arg: _UsedInBinOp | int) -> _UsedInBinOp | int: ... 20 | 21 | _UsedInSubscriptUnion = TypeVar("_UsedInSubscriptUnion", str, int) 22 | class UsesATypeVar1: 23 | def foo(self, arg: Union[str, _UsedInSubscriptUnion]) -> Union[str, _UsedInSubscriptUnion]: ... 24 | 25 | _UsedInClassDef = TypeVar("_UsedInClassDef", bound=bytes) 26 | class UsesATypeVar2(list[_UsedInClassDef]): ... 27 | 28 | _UsedInAnnotatedSubscript = TypeVar("_UsedInAnnotatedSubscript", bound=list[int]) 29 | def func3(arg: Annotated[_UsedInAnnotatedSubscript, "Important metadata"]) -> Annotated[_UsedInAnnotatedSubscript, "More important metadata"]: ... 30 | 31 | # Make sure TypeVars constrained and bound to forward references are permitted by pyflakes 32 | _S = TypeVar("_S", bound=BadClass) 33 | _S2 = TypeVar("_S2", BadClass, GoodClass) 34 | 35 | class BadClass: 36 | def __new__(cls: type[_S], *args: str, **kwargs: int) -> _S: ... # Y019 Use "typing_extensions.Self" instead of "_S", e.g. "def __new__(cls, *args: str, **kwargs: int) -> Self: ..." 37 | def bad_instance_method(self: _S, arg: bytes) -> _S: ... # Y019 Use "typing_extensions.Self" instead of "_S", e.g. "def bad_instance_method(self, arg: bytes) -> Self: ..." 38 | @classmethod 39 | def bad_class_method(cls: type[_S], arg: int) -> _S: ... # Y019 Use "typing_extensions.Self" instead of "_S", e.g. "def bad_class_method(cls, arg: int) -> Self: ..." 40 | 41 | class GoodClass: 42 | def __new__(cls: type[Self], *args: list[int], **kwargs: set[str]) -> Self: ... 43 | def good_instance_method_1(self: Self, arg: bytes) -> Self: ... 44 | def good_instance_method_2(self, arg1: _S2, arg2: _S2) -> _S2: ... 45 | def good_instance_method_3(self, arg1: _S2, /, arg2: _S2) -> _S2: ... 46 | @classmethod 47 | def good_cls_method_1(cls: type[Self], arg: int) -> Self: ... 48 | @classmethod 49 | def good_cls_method_2(cls, arg1: _S, arg2: _S) -> _S: ... 50 | @staticmethod 51 | def static_method(arg1: _S) -> _S: ... 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # flake8-pyi 2 | 3 | A plugin for Flake8 that provides specializations for 4 | [type hinting stub files](https://www.python.org/dev/peps/pep-0484/#stub-files), 5 | especially interesting for linting 6 | [typeshed](https://github.com/python/typeshed/). 7 | 8 | Refer to [this documentation](https://typing.readthedocs.io/en/latest/source/stubs.html) for more 9 | details on stub files. 10 | 11 | ## Functionality 12 | 13 | 1. Adds the `.pyi` extension to the default value of the `--filename` 14 | command-line argument to Flake8. This means stubs are linted by default with 15 | this plugin enabled, without needing to explicitly list every file. 16 | 17 | 2. Modifies PyFlakes runs for `.pyi` files to defer checking type annotation 18 | expressions after the entire file has been read. This enables support for 19 | first-class forward references that stub files use. 20 | 21 | 3. Provides a number of `.pyi`-specific warnings that enforce typeshed's 22 | style guide. 23 | 24 | Note: Be careful when using this plugin in the same environment as other flake8 25 | plugins, as they might generate errors that are inappropriate for 26 | `.pyi` files (e.g., about missing docstrings). We recommend running 27 | `flake8-pyi` in a dedicated environment in your CI. 28 | 29 | ## Lints provided 30 | 31 | This plugin reserves codes starting with **Y0**. For a full list of lints 32 | currently provided by flake8-pyi, see the 33 | [list of error codes](https://github.com/PyCQA/flake8-pyi/blob/main/ERRORCODES.md). 34 | 35 | Note that several error codes recommend using types from `typing_extensions` or 36 | `_typeshed`. Strictly speaking, these packages are not part of the standard 37 | library. However, these packages are included in typeshed's `stdlib/` 38 | directory, meaning that type checkers believe them to be part of the standard 39 | library even if this does not reflect the reality at runtime. As such, since 40 | stubs are never executed at runtime, types from `typing_extensions` and 41 | `_typeshed` can be used freely in a stubs package, even if the package does not 42 | have an explicit dependency on either `typing_extensions` or typeshed. 43 | 44 | Flake8-pyi's checks may produce false positives on stubs that aim to support Python 2. 45 | 46 | ## License 47 | 48 | MIT 49 | 50 | ## Authors 51 | 52 | Originally created by [Łukasz Langa](mailto:lukasz@langa.pl) and 53 | now maintained by 54 | [Jelle Zijlstra](mailto:jelle.zijlstra@gmail.com), 55 | [Alex Waygood](mailto:alex.waygood@gmail.com), 56 | Sebastian Rittau, Akuli, and Shantanu. 57 | 58 | ## See also 59 | 60 | * [List of error codes](https://github.com/PyCQA/flake8-pyi/blob/main/ERRORCODES.md) 61 | * [Changelog](https://github.com/PyCQA/flake8-pyi/blob/main/CHANGELOG.md) 62 | * [Information for contributors](https://github.com/PyCQA/flake8-pyi/blob/main/CONTRIBUTING.md) 63 | -------------------------------------------------------------------------------- /tests/test_pyi_files.py: -------------------------------------------------------------------------------- 1 | import glob 2 | import os 3 | import re 4 | import subprocess 5 | import sys 6 | from itertools import zip_longest 7 | 8 | import pytest 9 | 10 | 11 | @pytest.mark.parametrize("path", glob.glob("tests/*.pyi")) 12 | def test_pyi_file(path: str) -> None: 13 | flags = [] 14 | expected_output = "" 15 | 16 | if match := re.search(r"_py3(\d+)\.pyi$", path): 17 | if sys.version_info < (3, int(match.group(1))): 18 | pytest.skip(f"Python {sys.version_info} is too old for {path}") 19 | 20 | with open(path, encoding="UTF-8") as file: 21 | file_contents = file.read() 22 | 23 | for lineno, line in enumerate(file_contents.splitlines(), start=1): 24 | if line.startswith("# flags: "): 25 | flags.extend(line.split()[2:]) 26 | continue 27 | if line.startswith("#"): 28 | continue 29 | 30 | error_codes = list(re.finditer(r"# ([A-Z]\d\d\d )", line)) 31 | 32 | for match, next_match in zip_longest(error_codes, error_codes[1:]): 33 | end_pos = len(line) if next_match is None else next_match.start() 34 | message = line[match.end() : end_pos].strip() 35 | expected_output += f"{path}:{lineno}: {match.group(1)}{message}\n" 36 | 37 | bad_flag_msg = ( 38 | "--{flag} flags in test files override the .flake8 config file. " 39 | "Use --extend-{flag} instead." 40 | ).format 41 | 42 | for flag in flags: 43 | option = flag.split("=")[0] 44 | assert option not in {"--ignore", "--select"}, bad_flag_msg(option[2:]) 45 | 46 | # Silence DeprecationWarnings from our dependencies (pyflakes, flake8-bugbear, etc.) 47 | # 48 | # For DeprecationWarnings coming from flake8-pyi itself, 49 | # print the first occurence of each warning to stderr. 50 | # This will fail CI the same as `-Werror:::flake8_pyi`, 51 | # but the test failure report that pytest gives is much easier to read 52 | # if we use `-Wdefault:::flake8_pyi` 53 | flake8_invocation = [ 54 | sys.executable, 55 | "-Wignore", 56 | "-Wdefault:::flake8_pyi", 57 | "-m", 58 | "flake8", 59 | "-j0", 60 | ] 61 | 62 | run_results = [ 63 | # Passing a file on command line 64 | subprocess.run( 65 | [*flake8_invocation, *flags, path], 66 | env={**os.environ, "PYTHONPATH": "."}, 67 | capture_output=True, 68 | text=True, 69 | ), 70 | # Passing "-" as the file, and reading from stdin instead 71 | subprocess.run( 72 | [*flake8_invocation, "--stdin-display-name", path, *flags, "-"], 73 | env={**os.environ, "PYTHONPATH": "."}, 74 | input=file_contents, 75 | capture_output=True, 76 | text=True, 77 | ), 78 | ] 79 | 80 | for run_result in run_results: 81 | output = re.sub(":[0-9]+: ", ": ", run_result.stdout) # ignore column numbers 82 | if run_result.stderr: 83 | output += "\n" + run_result.stderr 84 | assert output == expected_output 85 | -------------------------------------------------------------------------------- /tests/pep604_union_types.pyi: -------------------------------------------------------------------------------- 1 | # flags: --extend-ignore=F401,F811 2 | # 3 | # Note: DO NOT RUN ISORT ON THIS FILE. 4 | # It's excluded in our pyproject.toml. 5 | 6 | import typing 7 | import typing_extensions 8 | from typing import Optional # Y037 Use PEP 604 union types instead of typing.Optional (e.g. "int | None" instead of "Optional[int]"). 9 | from typing import Union # Y037 Use PEP 604 union types instead of typing.Union (e.g. "int | str" instead of "Union[int, str]"). 10 | from typing_extensions import Optional # Y037 Use PEP 604 union types instead of typing_extensions.Optional (e.g. "int | None" instead of "Optional[int]"). 11 | from typing_extensions import Union # Y037 Use PEP 604 union types instead of typing_extensions.Union (e.g. "int | str" instead of "Union[int, str]"). 12 | 13 | x1: Optional[str] 14 | x2: Optional 15 | x3: Union[str, int] 16 | x4: Union 17 | 18 | 19 | y1: typing.Optional[str] # Y037 Use PEP 604 union types instead of typing.Optional (e.g. "int | None" instead of "Optional[int]"). 20 | y2: typing.Optional # Y037 Use PEP 604 union types instead of typing.Optional (e.g. "int | None" instead of "Optional[int]"). 21 | y3: typing.Union[str, int] # Y037 Use PEP 604 union types instead of typing.Union (e.g. "int | str" instead of "Union[int, str]"). 22 | y4: typing.Union # Y037 Use PEP 604 union types instead of typing.Union (e.g. "int | str" instead of "Union[int, str]"). 23 | y5: typing_extensions.Optional[str] # Y037 Use PEP 604 union types instead of typing_extensions.Optional (e.g. "int | None" instead of "Optional[int]"). 24 | y6: typing_extensions.Optional # Y037 Use PEP 604 union types instead of typing_extensions.Optional (e.g. "int | None" instead of "Optional[int]"). 25 | y7: typing_extensions.Union[str, int] # Y037 Use PEP 604 union types instead of typing_extensions.Union (e.g. "int | str" instead of "Union[int, str]"). 26 | y8: typing_extensions.Union # Y037 Use PEP 604 union types instead of typing_extensions.Union (e.g. "int | str" instead of "Union[int, str]"). 27 | 28 | 29 | def f1(x: Optional[str] = ...) -> None: ... 30 | def f2() -> Optional[str]: ... 31 | def f3() -> Union[str, int]: ... 32 | def f4(x: Union[str, int] = ...) -> None: ... 33 | def f5(x: Optional) -> None: ... 34 | def f6(x: Union) -> None: ... 35 | 36 | 37 | def g1(x: typing.Optional[str] = ...) -> None: ... # Y037 Use PEP 604 union types instead of typing.Optional (e.g. "int | None" instead of "Optional[int]"). 38 | def g2() -> typing.Optional[str]: ... # Y037 Use PEP 604 union types instead of typing.Optional (e.g. "int | None" instead of "Optional[int]"). 39 | def g3() -> typing.Union[str, int]: ... # Y037 Use PEP 604 union types instead of typing.Union (e.g. "int | str" instead of "Union[int, str]"). 40 | def g4(x: typing.Union[str, int] = ...) -> None: ... # Y037 Use PEP 604 union types instead of typing.Union (e.g. "int | str" instead of "Union[int, str]"). 41 | def g5(x: typing.Optional) -> None: ... # Y037 Use PEP 604 union types instead of typing.Optional (e.g. "int | None" instead of "Optional[int]"). 42 | def g6(x: typing.Union) -> None: ... # Y037 Use PEP 604 union types instead of typing.Union (e.g. "int | str" instead of "Union[int, str]"). 43 | -------------------------------------------------------------------------------- /tests/pep570.pyi: -------------------------------------------------------------------------------- 1 | # See https://peps.python.org/pep-0484/#positional-only-arguments 2 | # for the full details on which arguments using the older syntax should/shouldn't 3 | # be considered positional-only arguments by type checkers. 4 | from typing import Self 5 | 6 | def bad(__x: int) -> None: ... # Y063 Use PEP-570 syntax to indicate positional-only arguments 7 | def also_bad(__x: int, __y: str) -> None: ... # Y063 Use PEP-570 syntax to indicate positional-only arguments 8 | def still_bad(__x_: int) -> None: ... # Y063 Use PEP-570 syntax to indicate positional-only arguments 9 | 10 | def no_args() -> None: ... 11 | def okay(__x__: int) -> None: ... 12 | # The first argument isn't positional-only, so logically the second can't be either: 13 | def also_okay(x: int, __y: str) -> None: ... 14 | def fine(x: bytes, /) -> None: ... 15 | def no_idea_why_youd_do_this(__x: int, /, __y: str) -> None: ... 16 | def cool(_x__: int) -> None: ... 17 | def also_cool(x__: int) -> None: ... 18 | def unclear_from_pep_484_if_this_is_positional_or_not(__: str) -> None: ... 19 | def _(_: int) -> None: ... 20 | 21 | class Foo: 22 | def bad(__self) -> None: ... # Y063 Use PEP-570 syntax to indicate positional-only arguments 23 | @staticmethod 24 | def bad2(__self) -> None: ... # Y063 Use PEP-570 syntax to indicate positional-only arguments 25 | def bad3(__self, __x: int) -> None: ... # Y063 Use PEP-570 syntax to indicate positional-only arguments 26 | def still_bad(self, __x_: int) -> None: ... # Y063 Use PEP-570 syntax to indicate positional-only arguments 27 | @staticmethod 28 | def this_is_bad_too(__x: int) -> None: ... # Y063 Use PEP-570 syntax to indicate positional-only arguments 29 | @classmethod 30 | def not_good(cls, __foo: int) -> None: ... # Y063 Use PEP-570 syntax to indicate positional-only arguments 31 | 32 | # The first non-self argument isn't positional-only, so logically the second can't be either: 33 | def okay1(self, x: int, __y: int) -> None: ... 34 | # Same here: 35 | @staticmethod 36 | def okay2(x: int, __y_: int) -> None: ... 37 | @staticmethod 38 | def no_args() -> int: ... 39 | def okay3(__self__, __x__: int, __y: str) -> None: ... 40 | def okay4(self, /) -> None: ... 41 | def okay5(self, x: int, /) -> None: ... 42 | def okay6(__self, /) -> None: ... 43 | def cool(_self__: int) -> None: ... 44 | def also_cool(self__: int) -> None: ... 45 | def unclear_from_pep_484_if_this_is_positional_or_not(__: str) -> None: ... 46 | def _(_: int) -> None: ... 47 | @classmethod 48 | def fine(cls, foo: int, /) -> None: ... 49 | 50 | class Metaclass(type): 51 | @classmethod 52 | def __new__(mcls, __name: str, __bases: tuple[type, ...], __namespace: dict, **kwds) -> Self: ... # Y063 Use PEP-570 syntax to indicate positional-only arguments 53 | 54 | class Metaclass2(type): 55 | @classmethod 56 | def __new__(metacls, __name: str, __bases: tuple[type, ...], __namespace: dict, **kwds) -> Self: ... # Y063 Use PEP-570 syntax to indicate positional-only arguments 57 | 58 | class GoodMetaclass(type): 59 | @classmethod 60 | def __new__(mcls, name: str, bases: tuple[type, ...], namespace: dict, /, **kwds) -> Self: ... 61 | 62 | class GoodMetaclass2(type): 63 | @classmethod 64 | def __new__(metacls, name: str, bases: tuple[type, ...], namespace: dict, /, **kwds) -> Self: ... 65 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["hatchling", "hatch-vcs"] 3 | build-backend = "hatchling.build" 4 | 5 | [project] 6 | name = "flake8-pyi" 7 | dynamic = ["version"] 8 | authors = [ 9 | { name="Łukasz Langa", email="=lukasz@langa.pl" }, 10 | ] 11 | maintainers = [ 12 | { name="Jelle Zijlstra", email="jelle.zijlstra@gmail.com" }, 13 | { name="Alex Waygood", email="alex.waygood@gmail.com" }, 14 | { name="Sebastian Rittau", email="sebastian@rittau.biz" }, 15 | { name="Akuli" }, 16 | { name="Shantanu" }, 17 | ] 18 | description = "A plugin for flake8 to enable linting .pyi stub files." 19 | license = "MIT" 20 | readme = "README.md" 21 | requires-python = ">=3.9" 22 | keywords = [ 23 | "flake8", 24 | "pyi", 25 | "bugs", 26 | "pyflakes", 27 | "linter", 28 | "qa", 29 | "stubs", 30 | "typing", 31 | ] 32 | classifiers = [ 33 | "Development Status :: 5 - Production/Stable", 34 | "Environment :: Console", 35 | "Framework :: Flake8", 36 | "Intended Audience :: Developers", 37 | "Operating System :: OS Independent", 38 | "Programming Language :: Python", 39 | "Programming Language :: Python :: 3 :: Only", 40 | "Programming Language :: Python :: 3.9", 41 | "Programming Language :: Python :: 3.10", 42 | "Programming Language :: Python :: 3.11", 43 | "Programming Language :: Python :: 3.12", 44 | "Programming Language :: Python :: 3.13", 45 | "Programming Language :: Python :: 3.14", 46 | "Topic :: Software Development :: Libraries :: Python Modules", 47 | "Topic :: Software Development :: Quality Assurance", 48 | ] 49 | dependencies = [ 50 | "flake8 >= 6.0.0, < 8.0.0", 51 | "pyflakes >= 2.1.1", 52 | ] 53 | 54 | [dependency-groups] 55 | dev = [ 56 | "black==25.1.0", # Must match .pre-commit-config.yaml 57 | "flake8-bugbear==24.12.12", 58 | "flake8-noqa==1.4.0", 59 | "isort==6.0.1", # Must match .pre-commit-config.yaml 60 | "mypy==1.15.0", 61 | "pre-commit-hooks==5.0.0", # Must match .pre-commit-config.yaml 62 | "pytest==8.3.5", 63 | "pytest-xdist==3.6.1", 64 | "types-pyflakes<4", 65 | ] 66 | 67 | [project.urls] 68 | "Homepage" = "https://github.com/PyCQA/flake8-pyi" 69 | "Source" = "https://github.com/PyCQA/flake8-pyi" 70 | "Bug Tracker" = "https://github.com/PyCQA/flake8-pyi/issues" 71 | "Changelog" = "https://github.com/PyCQA/flake8-pyi/blob/main/CHANGELOG.md" 72 | 73 | [project.entry-points] 74 | "flake8.extension" = {Y0 = "flake8_pyi:PyiTreeChecker"} 75 | 76 | [tool.hatch.version] 77 | source = "vcs" 78 | 79 | [tool.hatch.version.raw-options] 80 | local_scheme = "no-local-version" 81 | 82 | [tool.isort] 83 | profile = "black" 84 | combine_as_imports = true 85 | skip = ["tests/imports.pyi", "tests/pep604_union_types.pyi"] 86 | skip_gitignore = true 87 | 88 | [tool.black] 89 | target-version = ['py39'] 90 | skip-magic-trailing-comma = true 91 | force-exclude = ".*\\.pyi" 92 | 93 | [tool.mypy] 94 | files = ["flake8_pyi", "tests/test_pyi_files.py"] 95 | show_traceback = true 96 | pretty = true 97 | strict = true 98 | enable_error_code = "ignore-without-code,redundant-expr,possibly-undefined" 99 | warn_unreachable = true 100 | allow_subclassing_any = true 101 | 102 | [[tool.mypy.overrides]] 103 | module = 'flake8.*' 104 | ignore_missing_imports = true 105 | 106 | [tool.pytest.ini_options] 107 | addopts = "--doctest-modules -nauto" 108 | filterwarnings = ["error"] 109 | -------------------------------------------------------------------------------- /tests/sysversioninfo.pyi: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | if sys.version_info[0] == 2: ... 4 | if sys.version_info[0] == True: ... # Y003 Unrecognized sys.version_info check # E712 comparison to True should be 'if cond is True:' or 'if cond:' 5 | if sys.version_info[0.0] == 2: ... # Y003 Unrecognized sys.version_info check 6 | if sys.version_info[False] == 2: ... # Y003 Unrecognized sys.version_info check 7 | if sys.version_info[0j] == 2: ... # Y003 Unrecognized sys.version_info check 8 | if sys.version_info[0] == (2, 7): ... # Y003 Unrecognized sys.version_info check 9 | if sys.version_info[0] == '2': ... # Y003 Unrecognized sys.version_info check 10 | if sys.version_info[1:] >= (7, 11): ... # Y003 Unrecognized sys.version_info check 11 | if sys.version_info[::-1] < (11, 7): ... # Y003 Unrecognized sys.version_info check 12 | if sys.version_info[:3] >= (2, 7): ... # Y003 Unrecognized sys.version_info check 13 | if sys.version_info[:True] >= (2, 7): ... # Y003 Unrecognized sys.version_info check 14 | if sys.version_info[:1] == (2,): ... 15 | if sys.version_info[:1] == (True,): ... # Y003 Unrecognized sys.version_info check 16 | if sys.version_info[:1] == (2, 7): ... # Y005 Version comparison must be against a length-1 tuple 17 | if sys.version_info[:2] == (2, 7): ... 18 | if sys.version_info[:2] == (2,): ... # Y005 Version comparison must be against a length-2 tuple 19 | if sys.version_info[:2] == "lol": ... # Y003 Unrecognized sys.version_info check 20 | if sys.version_info[:2.0] >= (3, 9): ... # Y003 Unrecognized sys.version_info check 21 | if sys.version_info[:2j] >= (3, 9): ... # Y003 Unrecognized sys.version_info check 22 | if sys.version_info[:, :] >= (2, 7): ... # Y003 Unrecognized sys.version_info check 23 | if sys.version_info < [3, 0]: ... # Y003 Unrecognized sys.version_info check 24 | if sys.version_info < ('3', '0'): ... # Y003 Unrecognized sys.version_info check 25 | if sys.version_info >= (3, 4, 3): ... # Y004 Version comparison must use only major and minor version 26 | if sys.version_info == (3, 4): ... # Y006 Use only < and >= for version comparisons 27 | if sys.version_info > (3, 0): ... # Y006 Use only < and >= for version comparisons 28 | if sys.version_info <= (3, 0): ... # Y006 Use only < and >= for version comparisons 29 | if sys.version_info < (3, 5): ... 30 | if sys.version_info >= (3, 5): ... 31 | if (2, 7) <= sys.version_info < (3, 5): ... # Y002 If test must be a simple comparison against sys.platform or sys.version_info 32 | 33 | if sys.version_info >= (3, 10): 34 | def foo1(x, *, bar=True, baz=False): ... 35 | elif sys.version_info >= (3, 9): 36 | def foo1(x, *, bar=True): ... 37 | else: 38 | def foo1(x): ... 39 | 40 | if sys.version_info < (3, 9): 41 | def foo2(x): ... 42 | elif sys.version_info < (3, 10): 43 | def foo2(x, *, bar=True): ... 44 | 45 | if sys.version_info < (3, 10): # Y066 When using if/else with sys.version_info, put the code for new Python versions first, e.g. "if sys.version_info >= (3, 10)" 46 | def foo3(x): ... 47 | else: 48 | def foo3(x, *, bar=True): ... 49 | 50 | if sys.version_info < (3, 8): # Y066 When using if/else with sys.version_info, put the code for new Python versions first, e.g. "if sys.version_info >= (3, 8)" 51 | def foo4(x): ... 52 | elif sys.version_info < (3, 9): # Y066 When using if/else with sys.version_info, put the code for new Python versions first, e.g. "if sys.version_info >= (3, 9)" 53 | def foo4(x, *, bar=True): ... 54 | elif sys.version_info < (3, 10): # Y066 When using if/else with sys.version_info, put the code for new Python versions first, e.g. "if sys.version_info >= (3, 10)" 55 | def foo4(x, *, bar=True, baz=False): ... 56 | else: 57 | def foo4(x, *, bar=True, baz=False, qux=1): ... 58 | -------------------------------------------------------------------------------- /tests/quotes.pyi: -------------------------------------------------------------------------------- 1 | import sys 2 | import typing 3 | from typing import Annotated, Literal, NewType, TypeAlias, TypeVar 4 | 5 | import typing_extensions 6 | 7 | __all__ = ["f", "g"] 8 | __all__ += ["h"] 9 | __all__.extend(["i"]) # Y056 Calling ".extend()" on "__all__" may not be supported by all type checkers (use += instead) 10 | __all__.append("j") # Y056 Calling ".append()" on "__all__" may not be supported by all type checkers (use += instead) 11 | __all__.remove("j") # Y056 Calling ".remove()" on "__all__" may not be supported by all type checkers (use += instead) 12 | __match_args__ = ('foo',) # Y052 Need type annotation for "__match_args__" 13 | __slots__ = ('foo',) # Y052 Need type annotation for "__slots__" 14 | 15 | def f(x: "int"): ... # Y020 Quoted annotations should never be used in stubs 16 | def g(x: list["int"]): ... # Y020 Quoted annotations should never be used in stubs 17 | _T = TypeVar("_T", bound="int") # Y020 Quoted annotations should never be used in stubs 18 | _T2 = TypeVar("_T", bound=int) 19 | _S = TypeVar("_S") 20 | _U = TypeVar("_U", "int", "str") # Y020 Quoted annotations should never be used in stubs # Y020 Quoted annotations should never be used in stubs 21 | _U2 = TypeVar("_U", int, str) 22 | 23 | # This is invalid, but type checkers will flag it, so we don't need to 24 | _V = TypeVar() 25 | 26 | def make_sure_those_typevars_arent_flagged_as_unused(a: _T, b: _T2, c: _S, d: _U, e: _U2, f: _V) -> tuple[_T, _T2, _S, _U, _U2, _V]: ... 27 | 28 | def h(w: Literal["a", "b"], x: typing.Literal["c"], y: typing_extensions.Literal["d"], z: _T) -> _T: ... # Y023 Use "typing.Literal" instead of "typing_extensions.Literal" 29 | 30 | def i(x: Annotated[int, "lots", "of", "strings"], b: typing.Annotated[str, "more", "strings"]) -> None: 31 | """Documented and guaranteed useful.""" # Y021 Docstrings should not be included in stubs 32 | 33 | def j() -> "int": ... # Y020 Quoted annotations should never be used in stubs 34 | Alias: TypeAlias = list["int"] # Y020 Quoted annotations should never be used in stubs 35 | 36 | class Child(list["int"]): # Y020 Quoted annotations should never be used in stubs 37 | """Documented and guaranteed useful.""" # Y021 Docstrings should not be included in stubs 38 | 39 | __all__ = ('foo',) # Y052 Need type annotation for "__all__" 40 | __match_args__ = ('foo', 'bar') 41 | __slots__ = ('foo', 'bar') 42 | 43 | if sys.platform == "linux": 44 | f: "int" # Y020 Quoted annotations should never be used in stubs 45 | elif sys.platform == "win32": 46 | f: "str" # Y020 Quoted annotations should never be used in stubs 47 | else: 48 | f: "bytes" # Y020 Quoted annotations should never be used in stubs 49 | 50 | class Empty: 51 | """Empty""" # Y021 Docstrings should not be included in stubs 52 | 53 | def docstring_and_ellipsis() -> None: 54 | """Docstring""" # Y021 Docstrings should not be included in stubs 55 | ... # Y048 Function body should contain exactly one statement 56 | 57 | def docstring_and_pass() -> None: 58 | """Docstring""" # Y021 Docstrings should not be included in stubs 59 | pass # Y048 Function body should contain exactly one statement 60 | 61 | class DocstringAndEllipsis: 62 | """Docstring""" # Y021 Docstrings should not be included in stubs 63 | ... # Y013 Non-empty class body must not contain "..." 64 | 65 | class DocstringAndPass: 66 | """Docstring""" # Y021 Docstrings should not be included in stubs 67 | pass # Y012 Class body must not contain "pass" 68 | 69 | # These three shouldn't trigger Y020 -- empty strings can't be "quoted annotations" 70 | k = "" # Y052 Need type annotation for "k" 71 | el = r"" # Y052 Need type annotation for "el" 72 | m = u"" # Y052 Need type annotation for "m" 73 | 74 | _N = NewType("_N", int) 75 | _NBad = NewType("_N", "int") # Y020 Quoted annotations should never be used in stubs 76 | -------------------------------------------------------------------------------- /tests/pep695_py312.pyi: -------------------------------------------------------------------------------- 1 | import typing 2 | from collections.abc import Iterator 3 | from typing import ( 4 | Annotated, 5 | Any, 6 | Literal, 7 | NamedTuple, 8 | NoReturn, 9 | Protocol, 10 | Self, 11 | TypedDict, 12 | ) 13 | 14 | type lowercase_alias = str | int # Y042 Type aliases should use the CamelCase naming convention 15 | type _LooksLikeATypeVarT = str | int # Y043 Bad name for a type alias (the "T" suffix implies a TypeVar) 16 | type _Unused = str | int # Y047 Type alias "_Unused" is not used 17 | type _List[T] = list[T] 18 | 19 | y: _List[int] 20 | x: _LooksLikeATypeVarT 21 | 22 | class GenericPEP695Class[T]: 23 | def __init__(self, x: int) -> None: 24 | self.x = x # Y010 Function body must contain only "..." 25 | def __new__(cls, *args: Any, **kwargs: Any) -> GenericPEP695Class: ... # Y034 "__new__" methods usually return "self" at runtime. Consider using "typing_extensions.Self" in "GenericPEP695Class.__new__", e.g. "def __new__(cls, *args: Any, **kwargs: Any) -> Self: ..." 26 | def __repr__(self) -> str: ... # Y029 Defining __repr__ or __str__ in a stub is almost always redundant 27 | def __eq__(self, other: Any) -> bool: ... # Y032 Prefer "object" to "Any" for the second parameter in "__eq__" methods 28 | def method(self) -> T: ... 29 | ... # Y013 Non-empty class body must not contain "..." 30 | pass # Y012 Class body must not contain "pass" 31 | def __exit__(self, *args: Any) -> None: ... # Y036 Badly defined __exit__ method: Star-args in an __exit__ method should be annotated with "object", not "Any" 32 | async def __aexit__(self) -> None: ... # Y036 Badly defined __aexit__ method: If there are no star-args, there should be at least 3 non-keyword-only args in an __aexit__ method (excluding "self") 33 | def never_call_me(self, arg: NoReturn) -> None: ... # Y050 Use "typing_extensions.Never" instead of "NoReturn" for argument annotations 34 | 35 | class GenericPEP695InheritingFromObject[T](object): # Y040 Do not inherit from "object" explicitly, as it is redundant in Python 3 36 | x: T 37 | 38 | class GenericPEP695InheritingFromIterator[T](Iterator[T]): 39 | def __iter__(self) -> Iterator[T]: ... # Y034 "__iter__" methods in classes like "GenericPEP695InheritingFromIterator" usually return "self" at runtime. Consider using "typing_extensions.Self" in "GenericPEP695InheritingFromIterator.__iter__", e.g. "def __iter__(self) -> Self: ..." 40 | 41 | class PEP695BadBody[T]: 42 | pass # Y009 Empty body should contain "...", not "pass" 43 | 44 | class PEP695Docstring[T]: 45 | """Docstring""" # Y021 Docstrings should not be included in stubs 46 | ... # Y013 Non-empty class body must not contain "..." 47 | 48 | class PEP695BadDunderNew[T]: 49 | def __new__[S](cls: type[S], *args: Any, **kwargs: Any) -> S: ... # Y019 Use "typing_extensions.Self" instead of "S", e.g. "def __new__(cls, *args: Any, **kwargs: Any) -> Self: ..." 50 | def generic_instance_method[S](self: S) -> S: ... # Y019 Use "typing_extensions.Self" instead of "S", e.g. "def generic_instance_method(self) -> Self: ..." 51 | 52 | class PEP695GoodDunderNew[T]: 53 | def __new__(cls, *args: Any, **kwargs: Any) -> Self: ... 54 | 55 | class GenericNamedTuple[T](NamedTuple): 56 | foo: T 57 | 58 | class GenericTypedDict[T](TypedDict): 59 | foo: T 60 | 61 | class GenericTypingDotNamedTuple(typing.NamedTuple): 62 | foo: T 63 | 64 | class GenericTypingDotTypedDict(typing.TypedDict): 65 | foo: T 66 | 67 | type NoDuplicatesInThisUnion = GenericPEP695Class[str] | GenericPEP695Class[int] 68 | type ThisHasDuplicates = GenericPEP695Class[str] | GenericPEP695Class[str] # Y016 Duplicate union member "GenericPEP695Class[str]" 69 | 70 | class _UnusedPEP695Protocol[T](Protocol): # Y046 Protocol "_UnusedPEP695Protocol" is not used 71 | x: T 72 | 73 | class _UnusedPEP695TypedDict[T](TypedDict): # Y049 TypedDict "_UnusedPEP695TypedDict" is not used 74 | x: T 75 | 76 | type X = Literal["Y053 will not be emitted here despite it being a very long string literal, because it is inside a `Literal` slice"] 77 | type Y = Annotated[int, "look at me, very long string literal, but it's okay because it's an `Annotated` metadata string"] 78 | -------------------------------------------------------------------------------- /tests/unused_things.pyi: -------------------------------------------------------------------------------- 1 | import sys 2 | import typing 3 | from typing import Literal, Protocol, TypedDict, TypeVar 4 | 5 | import mypy_extensions 6 | import typing_extensions 7 | from typing_extensions import TypeAlias 8 | 9 | _T = TypeVar("_T") 10 | 11 | class _Foo(Protocol): # Y046 Protocol "_Foo" is not used 12 | bar: int 13 | 14 | class _GenericFoo(Protocol[_T]): # Y046 Protocol "_GenericFoo" is not used 15 | bar: _T 16 | 17 | class _Bar(typing.Protocol): # Y046 Protocol "_Bar" is not used 18 | bar: int 19 | 20 | class _Baz(typing_extensions.Protocol): # Y046 Protocol "_Baz" is not used # Y023 Use "typing.Protocol" instead of "typing_extensions.Protocol" 21 | bar: int 22 | 23 | class UnusedButPublicProtocol(Protocol): 24 | bar: int 25 | 26 | class _UsedPrivateProtocol(Protocol): 27 | bar: int 28 | 29 | class _UsedPrivateGenericProtocol(Protocol[_T]): 30 | bar: _T 31 | 32 | def uses__UsedPrivateProtocol(arg: _UsedPrivateProtocol) -> None: ... 33 | def uses__UsedPrivateGenericProtocol(arg: _UsedPrivateGenericProtocol) -> None: ... 34 | 35 | _UnusedPrivateAlias: TypeAlias = str | int # Y047 Type alias "_UnusedPrivateAlias" is not used 36 | PublicAlias: TypeAlias = str | int 37 | _UsedAlias: TypeAlias = Literal["Look, this is used!"] 38 | 39 | def uses__UsedAlias(arg: _UsedAlias) -> None: ... 40 | 41 | class _UnusedTypedDict(TypedDict): # Y049 TypedDict "_UnusedTypedDict" is not used 42 | foo: str 43 | 44 | class _UnusedTypedDict2(typing_extensions.TypedDict): # Y049 TypedDict "_UnusedTypedDict2" is not used # Y023 Use "typing.TypedDict" instead of "typing_extensions.TypedDict" 45 | bar: int 46 | 47 | class _UnusedTypedDict3(mypy_extensions.TypedDict): # Y049 TypedDict "_UnusedTypedDict3" is not used 48 | baz: dict[str, str] 49 | 50 | class _UsedTypedDict(TypedDict): 51 | baz: bytes 52 | 53 | class _Uses_UsedTypedDict(_UsedTypedDict): 54 | spam: list[int] 55 | 56 | class _UsedTypedDict2(TypedDict): 57 | ham: set[int] 58 | 59 | def uses__UsedTypeDict2(arg: _UsedTypedDict2) -> None: ... 60 | 61 | _UnusedTypedDict4 = TypedDict("_UnusedTypedDict4", {"-": int, "def": str}) # Y049 TypedDict "_UnusedTypedDict4" is not used 62 | _UnusedTypedDict5 = typing_extensions.TypedDict("_UnusedTypedDict5", {"foo": bytes, "bar": str}) # Y049 TypedDict "_UnusedTypedDict5" is not used # Y023 Use "typing.TypedDict" instead of "typing_extensions.TypedDict" # Y031 Use class-based syntax for TypedDicts where possible 63 | _UsedTypedDict3 = mypy_extensions.TypedDict("_UsedTypedDict3", {".": list[int]}) 64 | 65 | uses__UsedTypedDict3: _UsedTypedDict3 66 | 67 | class UnusedButPublicTypedDict(TypedDict): 68 | foo: str 69 | 70 | UnusedButPublicTypedDict2 = TypedDict("UnusedButPublicTypedDict", {"99999": frozenset[str]}) 71 | 72 | if sys.version_info >= (3, 10): 73 | _ConditionallyDefinedUnusedAlias: TypeAlias = int # Y047 Type alias "_ConditionallyDefinedUnusedAlias" is not used 74 | _ConditionallyDefinedUnusedTypeVar = TypeVar("_ConditionallyDefinedUnusedTypeVar") # Y018 TypeVar "_ConditionallyDefinedUnusedTypeVar" is not used 75 | _ConditionallyDefinedUnusedAssignmentBasedTypedDict = TypedDict("_ConditionallyDefinedUnusedTypedDict", {"42": int}) # Y049 TypedDict "_ConditionallyDefinedUnusedAssignmentBasedTypedDict" is not used 76 | 77 | class _ConditionallyDefinedUnusedClassBasedTypedDict(TypedDict): # Y049 TypedDict "_ConditionallyDefinedUnusedClassBasedTypedDict" is not used 78 | foo: str 79 | 80 | class _ConditionallyDefinedUnusedProtocol(Protocol): # Y046 Protocol "_ConditionallyDefinedUnusedProtocol" is not used 81 | foo: int 82 | 83 | else: 84 | _ConditionallyDefinedUnusedAlias: TypeAlias = str 85 | _ConditionallyDefinedUnusedTypeVar = TypeVar("_ConditionallyDefinedUnusedTypeVar", bound=str) 86 | _ConditionallyDefinedUnusedAssignmentBasedTypedDict = TypedDict("_ConditionallyDefinedUnusedTypedDict", {"4222": int}) 87 | 88 | class _ConditionallyDefinedUnusedClassBasedTypedDict(TypedDict): 89 | foo: str 90 | bar: int 91 | 92 | class _ConditionallyDefinedUnusedProtocol(Protocol): 93 | foo: int 94 | bar: str 95 | 96 | # Tests to make sure we handle edge-cases in the AST correctly: 97 | class Strange: ... 98 | class _NotAProtocol(Strange[Strange][Strange].Protocol): ... 99 | class _AlsoNotAProtocol(Protocol[Protocol][Protocol][Protocol]): ... 100 | -------------------------------------------------------------------------------- /tests/aliases.pyi: -------------------------------------------------------------------------------- 1 | # flags: --extend-ignore=Y037,Y047 2 | import array 3 | import builtins 4 | import collections.abc 5 | import enum 6 | import typing 7 | from collections.abc import Mapping 8 | from typing import ( 9 | Annotated, 10 | Any, 11 | Literal, 12 | Optional, 13 | ParamSpec as _ParamSpec, 14 | TypeAlias, 15 | TypedDict, 16 | Union, 17 | _Alias, 18 | ) 19 | from weakref import WeakValueDictionary 20 | 21 | import typing_extensions 22 | 23 | class Foo: 24 | def baz(self) -> None: ... 25 | 26 | StringMapping = Mapping[str, str] # Y026 Use typing_extensions.TypeAlias for type aliases, e.g. "StringMapping: TypeAlias = Mapping[str, str]" 27 | IntSequence = collections.abc.Sequence[int] # Y026 Use typing_extensions.TypeAlias for type aliases, e.g. "IntSequence: TypeAlias = collections.abc.Sequence[int]" 28 | IntArray = array.array[int] # Y026 Use typing_extensions.TypeAlias for type aliases, e.g. "IntArray: TypeAlias = array.array[int]" 29 | FooWeakDict = WeakValueDictionary[str, Foo] # Y026 Use typing_extensions.TypeAlias for type aliases, e.g. "FooWeakDict: TypeAlias = WeakValueDictionary[str, Foo]" 30 | P = builtins.tuple[int, int] # Y026 Use typing_extensions.TypeAlias for type aliases, e.g. "P: TypeAlias = builtins.tuple[int, int]" 31 | Q = tuple[int, int] # Y026 Use typing_extensions.TypeAlias for type aliases, e.g. "Q: TypeAlias = tuple[int, int]" 32 | R = Any # Y026 Use typing_extensions.TypeAlias for type aliases, e.g. "R: TypeAlias = Any" 33 | S = Optional[str] # Y026 Use typing_extensions.TypeAlias for type aliases, e.g. "S: TypeAlias = Optional[str]" 34 | T = Annotated[int, "some very useful metadata"] # Y026 Use typing_extensions.TypeAlias for type aliases, e.g. "T: TypeAlias = Annotated[int, 'some very useful metadata']" 35 | U = typing.Literal["ham", "bacon"] # Y026 Use typing_extensions.TypeAlias for type aliases, e.g. "U: TypeAlias = typing.Literal['ham', 'bacon']" 36 | V = Literal["[(", ")]"] # Y026 Use typing_extensions.TypeAlias for type aliases, e.g. "V: TypeAlias = Literal['[(', ')]']" 37 | X = typing_extensions.Literal["foo", "bar"] # Y026 Use typing_extensions.TypeAlias for type aliases, e.g. "X: TypeAlias = typing_extensions.Literal['foo', 'bar']" # Y023 Use "typing.Literal" instead of "typing_extensions.Literal" 38 | Y = int | str # Y026 Use typing_extensions.TypeAlias for type aliases, e.g. "Y: TypeAlias = int | str" 39 | Z = Union[str, bytes] # Y026 Use typing_extensions.TypeAlias for type aliases, e.g. "Z: TypeAlias = Union[str, bytes]" 40 | ZZ = None # Y026 Use typing_extensions.TypeAlias for type aliases, e.g. "ZZ: TypeAlias = None" 41 | 42 | StringMapping: TypeAlias = Mapping[str, str] 43 | IntSequence: TypeAlias = collections.abc.Sequence[int] 44 | IntArray: TypeAlias = array.array[int] 45 | FooWeakDict: TypeAlias = WeakValueDictionary[str, Foo] 46 | A: typing.TypeAlias = typing.Literal["ham", "bacon"] 47 | B: typing_extensions.TypeAlias = Literal["spam", "eggs"] 48 | C: TypeAlias = typing_extensions.Literal["foo", "bar"] # Y023 Use "typing.Literal" instead of "typing_extensions.Literal" 49 | D: TypeAlias = int | str 50 | E: TypeAlias = Union[str, bytes] 51 | F: TypeAlias = int 52 | G: typing.TypeAlias = int 53 | H: typing_extensions.TypeAlias = int 54 | I: TypeAlias = Annotated[int, "some very useful metadata"] 55 | J: TypeAlias = Optional[str] 56 | K: TypeAlias = Any 57 | L: TypeAlias = tuple[int, int] 58 | P: TypeAlias = builtins.tuple[int, int] 59 | 60 | a = b = int # Y017 Only simple assignments allowed 61 | a.b = int # Y017 Only simple assignments allowed 62 | 63 | _P = _ParamSpec("_P") 64 | List = _Alias() 65 | 66 | TD = TypedDict("TD", {"in": bool}) 67 | 68 | def foo() -> None: ... 69 | alias_for_foo_but_not_type_alias = foo 70 | 71 | alias_for_function_from_builtins = dir 72 | 73 | f: Foo = ... 74 | baz = f.baz 75 | 76 | _PrivateAliasT: TypeAlias = str | int # Y043 Bad name for a type alias (the "T" suffix implies a TypeVar) 77 | _PrivateAliasT2: TypeAlias = typing.Any # Y043 Bad name for a type alias (the "T" suffix implies a TypeVar) 78 | _PrivateAliasT3: TypeAlias = Literal["not", "a", "chance"] # Y043 Bad name for a type alias (the "T" suffix implies a TypeVar) 79 | PublicAliasT: TypeAlias = str | int 80 | PublicAliasT2: TypeAlias = Union[str, bytes] 81 | _ABCDEFGHIJKLMNOPQRST: TypeAlias = typing.Any 82 | _PrivateAliasS: TypeAlias = Literal["I", "guess", "this", "is", "okay"] 83 | _PrivateAliasS2: TypeAlias = Annotated[str, "also okay"] 84 | 85 | snake_case_alias1: TypeAlias = str | int # Y042 Type aliases should use the CamelCase naming convention 86 | _snake_case_alias2: TypeAlias = Literal["whatever"] # Y042 Type aliases should use the CamelCase naming convention 87 | 88 | # check that this edge case doesn't crash the plugin 89 | _: TypeAlias = str | int 90 | 91 | class FooEnum(enum.Enum): 92 | BAR = None # shouldn't emit Y026 because it's an assignment in an enum class 93 | -------------------------------------------------------------------------------- /tests/exit_methods.pyi: -------------------------------------------------------------------------------- 1 | # flags: --extend-ignore=Y022 2 | 3 | import builtins 4 | import types 5 | import typing 6 | from collections.abc import Awaitable 7 | from types import TracebackType 8 | from typing import Any, Type 9 | 10 | import _typeshed 11 | import typing_extensions 12 | from _typeshed import Unused 13 | 14 | # Good __(a)exit__ methods 15 | class GoodOne: 16 | def __exit__(self, *args: object) -> None: ... 17 | async def __aexit__(self, *args) -> str: ... 18 | 19 | class GoodTwo: 20 | def __exit__(self, typ: type[builtins.BaseException] | None, *args: builtins.object) -> bool | None: ... 21 | async def __aexit__(self, typ: Type[BaseException] | None, *args: object) -> bool: ... 22 | 23 | class GoodThree: 24 | def __exit__(self, typ: typing.Type[BaseException] | None, /, exc: BaseException | None, *args: object) -> None: ... 25 | async def __aexit__(self, typ: typing_extensions.Type[BaseException] | None, __exc: BaseException | None, *args: object) -> None: ... 26 | 27 | class GoodFour: 28 | def __exit__(self, typ: type[BaseException] | None, exc: BaseException | None, tb: TracebackType | None) -> None: ... 29 | async def __aexit__(self, typ: type[BaseException] | None, exc: BaseException | None, tb: types.TracebackType | None, *args: list[None]) -> None: ... 30 | 31 | class GoodFive: 32 | def __exit__(self, typ: type[BaseException] | None, exc: BaseException | None, tb: TracebackType | None, weird_extra_arg: int = ..., *args: int, **kwargs: str) -> None: ... 33 | def __aexit__(self, typ: type[BaseException] | None, exc: BaseException | None, tb: TracebackType | None) -> Awaitable[None]: ... 34 | 35 | class GoodFiveAndAHalf: 36 | def __exit__(self, typ: object, exc: builtins.object, tb: object) -> None: ... 37 | async def __aexit__(self, typ: object, exc: object, tb: builtins.object) -> None: ... 38 | 39 | class GoodSix: 40 | def __exit__(self, *args: Unused) -> bool: ... 41 | def __aexit__(self, typ: Type[BaseException] | None, *args: _typeshed.Unused) -> Awaitable[None]: ... 42 | 43 | class GoodSeven: 44 | def __exit__(self, typ: typing.Type[BaseException] | None, /, exc: BaseException | None, *args: _typeshed.Unused) -> bool: ... 45 | def __aexit__(self, typ: type[BaseException] | None, exc: BaseException | None, tb: TracebackType | None, weird_extra_arg: int = ..., *args: Unused, **kwargs: Unused) -> Awaitable[None]: ... 46 | 47 | 48 | # Bad __(a)exit__ methods 49 | class BadOne: 50 | def __exit__(self, *args: Any) -> None: ... # Y036 Badly defined __exit__ method: Star-args in an __exit__ method should be annotated with "object", not "Any" 51 | async def __aexit__(self) -> None: ... # Y036 Badly defined __aexit__ method: If there are no star-args, there should be at least 3 non-keyword-only args in an __aexit__ method (excluding "self") 52 | 53 | class BadTwo: 54 | def __exit__(self, typ, exc, tb, weird_extra_arg) -> None: ... # Y036 Badly defined __exit__ method: All arguments after the first 4 in an __exit__ method must have a default value 55 | async def __aexit__(self, typ, exc, tb, *, weird_extra_arg) -> None: ... # Y036 Badly defined __aexit__ method: All keyword-only arguments in an __aexit__ method must have a default value 56 | 57 | class BadThree: 58 | def __exit__(self, typ: type[BaseException], exc: BaseException | None, tb: TracebackType | None) -> None: ... # Y036 Badly defined __exit__ method: The first arg in an __exit__ method should be annotated with "type[BaseException] | None" or "object", not "type[BaseException]" 59 | async def __aexit__(self, typ: type[BaseException] | None, exc: BaseException, tb: TracebackType, /) -> bool | None: ... # Y036 Badly defined __aexit__ method: The second arg in an __aexit__ method should be annotated with "BaseException | None" or "object", not "BaseException" # Y036 Badly defined __aexit__ method: The third arg in an __aexit__ method should be annotated with "types.TracebackType | None" or "object", not "TracebackType" 60 | 61 | class BadFour: 62 | def __exit__(self, typ: BaseException | None, *args: list[str]) -> bool: ... # Y036 Badly defined __exit__ method: Star-args in an __exit__ method should be annotated with "object", not "list[str]" # Y036 Badly defined __exit__ method: The first arg in an __exit__ method should be annotated with "type[BaseException] | None" or "object", not "BaseException | None" 63 | def __aexit__(self, *args: Any) -> Awaitable[None]: ... # Y036 Badly defined __aexit__ method: Star-args in an __aexit__ method should be annotated with "object", not "Any" 64 | 65 | class ThisExistsToTestInteractionBetweenY036AndY063: 66 | def __exit__(self, __typ, exc, tb, weird_extra_arg) -> None: ... # Y036 Badly defined __exit__ method: All arguments after the first 4 in an __exit__ method must have a default value # Y063 Use PEP-570 syntax to indicate positional-only arguments 67 | async def __aexit__(self, __typ: type[BaseException] | None, __exc: BaseException, __tb: TracebackType) -> bool | None: ... # Y036 Badly defined __aexit__ method: The second arg in an __aexit__ method should be annotated with "BaseException | None" or "object", not "BaseException" # Y036 Badly defined __aexit__ method: The third arg in an __aexit__ method should be annotated with "types.TracebackType | None" or "object", not "TracebackType" # Y063 Use PEP-570 syntax to indicate positional-only arguments 68 | -------------------------------------------------------------------------------- /flake8_pyi/errors.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING, NamedTuple 4 | 5 | if TYPE_CHECKING: 6 | # Import is only needed for type annotations, 7 | # and causes a circular import if it's imported at runtime. 8 | from .checker import PyiTreeChecker 9 | 10 | 11 | class Error(NamedTuple): 12 | lineno: int 13 | col: int 14 | message: str 15 | type: type[PyiTreeChecker] 16 | 17 | 18 | # Please keep error code lists in ERRORCODES and CHANGELOG up to date 19 | Y001 = "Y001 Name of private {} must start with _" 20 | Y002 = ( 21 | "Y002 If test must be a simple comparison against sys.platform or sys.version_info" 22 | ) 23 | Y003 = "Y003 Unrecognized sys.version_info check" 24 | Y004 = "Y004 Version comparison must use only major and minor version" 25 | Y005 = "Y005 Version comparison must be against a length-{n} tuple" 26 | Y006 = "Y006 Use only < and >= for version comparisons" 27 | Y007 = "Y007 Unrecognized sys.platform check" 28 | Y008 = 'Y008 Unrecognized platform "{platform}"' 29 | Y009 = 'Y009 Empty body should contain "...", not "pass"' 30 | Y010 = 'Y010 Function body must contain only "..."' 31 | Y011 = "Y011 Only simple default values allowed for typed arguments" 32 | Y012 = 'Y012 Class body must not contain "pass"' 33 | Y013 = 'Y013 Non-empty class body must not contain "..."' 34 | Y014 = "Y014 Only simple default values allowed for arguments" 35 | Y015 = "Y015 Only simple default values are allowed for assignments" 36 | Y016 = 'Y016 Duplicate union member "{}"' 37 | Y017 = "Y017 Only simple assignments allowed" 38 | Y018 = 'Y018 {typevarlike_cls} "{typevar_name}" is not used' 39 | Y019 = ( 40 | 'Y019 Use "typing_extensions.Self" instead of "{typevar_name}", e.g. "{new_syntax}"' 41 | ) 42 | Y020 = "Y020 Quoted annotations should never be used in stubs" 43 | Y021 = "Y021 Docstrings should not be included in stubs" 44 | Y022 = "Y022 Use {good_syntax} instead of {bad_syntax} (PEP 585 syntax)" 45 | Y023 = "Y023 Use {good_syntax} instead of {bad_syntax}" 46 | Y024 = 'Y024 Use "typing.NamedTuple" instead of "collections.namedtuple"' 47 | Y025 = ( 48 | 'Y025 Use "from collections.abc import Set as AbstractSet" ' 49 | 'to avoid confusion with "builtins.set"' 50 | ) 51 | Y026 = 'Y026 Use typing_extensions.TypeAlias for type aliases, e.g. "{suggestion}"' 52 | Y028 = "Y028 Use class-based syntax for NamedTuples" 53 | Y029 = "Y029 Defining __repr__ or __str__ in a stub is almost always redundant" 54 | Y030 = "Y030 Multiple Literal members in a union. {suggestion}" 55 | Y031 = "Y031 Use class-based syntax for TypedDicts where possible" 56 | Y032 = ( 57 | 'Y032 Prefer "object" to "Any" for the second parameter in "{method_name}" methods' 58 | ) 59 | Y033 = ( 60 | "Y033 Do not use type comments in stubs " 61 | '(e.g. use "x: int" instead of "x = ... # type: int")' 62 | ) 63 | Y034 = ( 64 | 'Y034 {methods} usually return "self" at runtime. ' 65 | 'Consider using "typing_extensions.Self" in "{method_name}", ' 66 | 'e.g. "{suggested_syntax}"' 67 | ) 68 | Y035 = ( 69 | 'Y035 "{var}" in a stub file must have a value, ' 70 | 'as it has the same semantics as "{var}" at runtime.' 71 | ) 72 | Y036 = "Y036 Badly defined {method_name} method: {details}" 73 | Y037 = "Y037 Use PEP 604 union types instead of {old_syntax} (e.g. {example})." 74 | Y038 = ( 75 | 'Y038 Use "from collections.abc import Set as AbstractSet" ' 76 | 'instead of "from {module} import AbstractSet" (PEP 585 syntax)' 77 | ) 78 | Y039 = 'Y039 Use "str" instead of "{module}.Text"' 79 | Y040 = 'Y040 Do not inherit from "object" explicitly, as it is redundant in Python 3' 80 | Y041 = ( 81 | 'Y041 Use "{implicit_supertype}" ' 82 | 'instead of "{implicit_subtype} | {implicit_supertype}" ' 83 | '(see "The numeric tower" in PEP 484)' 84 | ) 85 | Y042 = "Y042 Type aliases should use the CamelCase naming convention" 86 | Y043 = 'Y043 Bad name for a type alias (the "T" suffix implies a TypeVar)' 87 | Y044 = 'Y044 "from __future__ import annotations" has no effect in stub files.' 88 | Y045 = 'Y045 "{iter_method}" methods should return an {good_cls}, not an {bad_cls}' 89 | Y046 = 'Y046 Protocol "{protocol_name}" is not used' 90 | Y047 = 'Y047 Type alias "{alias_name}" is not used' 91 | Y048 = "Y048 Function body should contain exactly one statement" 92 | Y049 = 'Y049 TypedDict "{typeddict_name}" is not used' 93 | Y050 = ( 94 | 'Y050 Use "typing_extensions.Never" instead of "NoReturn" for argument annotations' 95 | ) 96 | Y051 = 'Y051 "{literal_subtype}" is redundant in a union with "{builtin_supertype}"' 97 | Y052 = 'Y052 Need type annotation for "{variable}"' 98 | Y053 = "Y053 String and bytes literals >50 characters long are not permitted" 99 | Y054 = ( 100 | "Y054 Numeric literals with a string representation " 101 | ">10 characters long are not permitted" 102 | ) 103 | Y055 = 'Y055 Multiple "type[Foo]" members in a union. {suggestion}' 104 | Y056 = ( 105 | 'Y056 Calling "{method}" on "__all__" may not be supported by all type checkers ' 106 | "(use += instead)" 107 | ) 108 | Y057 = ( 109 | "Y057 Do not use {module}.ByteString, which has unclear semantics and is deprecated" 110 | ) 111 | Y058 = ( 112 | 'Y058 Use "{good_cls}" as the return value for simple "{iter_method}" methods, ' 113 | 'e.g. "{example}"' 114 | ) 115 | Y059 = 'Y059 "Generic[]" should always be the last base class' 116 | Y060 = ( 117 | 'Y060 Redundant inheritance from "{redundant_base}"; ' 118 | "class would be inferred as generic anyway" 119 | ) 120 | Y061 = 'Y061 None inside "Literal[]" expression. Replace with "{suggestion}"' 121 | Y062 = 'Y062 Duplicate "Literal[]" member "{}"' 122 | Y063 = "Y063 Use PEP-570 syntax to indicate positional-only arguments" 123 | Y064 = 'Y064 Use "{suggestion}" instead of "{original}"' 124 | Y065 = 'Y065 Leave {what} unannotated rather than using "Incomplete"' 125 | Y066 = ( 126 | "Y066 When using if/else with sys.version_info, " 127 | 'put the code for new Python versions first, e.g. "{new_syntax}"' 128 | ) 129 | Y067 = 'Y067 Use "=None" instead of "Incomplete | None = None"' 130 | Y068 = 'Y068 Do not use "@override" in stub files.' 131 | 132 | Y090 = ( 133 | 'Y090 "{original}" means ' 134 | '"a tuple of length 1, in which the sole element is of type {typ!r}". ' 135 | 'Perhaps you meant "{new}"?' 136 | ) 137 | Y091 = ( 138 | 'Y091 Argument "{arg}" to protocol method "{method}" should probably not be positional-or-keyword. ' 139 | "Make it positional-only, since usually you don't want to mandate a specific argument name" 140 | ) 141 | 142 | DISABLED_BY_DEFAULT = ["Y090", "Y091"] 143 | -------------------------------------------------------------------------------- /tests/defaults.pyi: -------------------------------------------------------------------------------- 1 | import math 2 | import os 3 | import sys 4 | from typing import Annotated, Literal, TypeAlias 5 | 6 | import _typeshed 7 | from _typeshed import SupportsRead, SupportsWrite, sentinel 8 | 9 | class MyEnum: ... 10 | class NS: 11 | class MyEnum: ... 12 | 13 | def f1(x: int = ...) -> None: ... 14 | def f2(x: int = 3) -> None: ... 15 | def f201(x: int = -3) -> None: ... 16 | def f202(x: float = 3.14) -> None: ... 17 | def f203(x: type = int) -> None: ... # Y011 Only simple default values allowed for typed arguments 18 | def f204(x: float = math.inf) -> None: ... 19 | def f205(x: float = math.pi) -> None: ... 20 | def f206(x: float = -math.inf) -> None: ... 21 | def f207(x: float = -math.e) -> None: ... 22 | def f208(x: float = math.nan) -> None: ... 23 | def f209(x: float = -math.nan) -> None: ... # Y011 Only simple default values allowed for typed arguments 24 | def f210(x: float = float("inf")) -> None: ... # Y011 Only simple default values allowed for typed arguments 25 | def f3(*, x: int = ...) -> None: ... 26 | def f4(*, x: complex = 3j) -> None: ... 27 | def f401(*, x: complex = 5 + 3j) -> None: ... 28 | def f402(*, x: complex = -42 - 42j) -> None: ... 29 | def f403(*, x: complex = -42.37 + 8.35j) -> None: ... 30 | def f405(*, x: int = 3 * 3) -> None: ... # Y011 Only simple default values allowed for typed arguments 31 | def f406(*, x: int = -3 >> 3) -> None: ... # Y011 Only simple default values allowed for typed arguments 32 | def f407(*, x: complex = math.inf + 1j) -> None: ... # Y011 Only simple default values allowed for typed arguments 33 | def f408(*, x: complex = math.tau * 1j) -> None: ... # Y011 Only simple default values allowed for typed arguments 34 | def f409(*, x: complex = math.inf + math.inf * 1j) -> None: ... # Y011 Only simple default values allowed for typed arguments 35 | def f5(x=3) -> None: ... 36 | def f6(*, x: int) -> None: ... 37 | def f7(x, y: int = 3) -> None: ... 38 | def f8(x, y: int = ...) -> None: ... 39 | def f9(x, y: str = "x") -> None: ... 40 | def f901(x, y: str = os.pathsep) -> None: ... 41 | def f10(x, y: str = ..., *args: "int") -> None: ... # Y020 Quoted annotations should never be used in stubs 42 | def f11(*, x: str = "x") -> None: ... 43 | def f12(*, x: str = ..., **kwargs: "int") -> None: ... # Y020 Quoted annotations should never be used in stubs 44 | def f13(x: list[str] = ["foo", "bar", "baz"]) -> None: ... 45 | def f14(x: tuple[str, ...] = ("foo", "bar", "baz")) -> None: ... 46 | def f15(x: set[str] = {"foo", "bar", "baz"}) -> None: ... 47 | def f151(x: dict[int, int] = {1: 2}) -> None: ... 48 | # When parsed, this case results in `None` being placed in the `.keys` list for the `ast.Dict` node 49 | def f152(x: dict[int, int] = {1: 2, **{3: 4}}) -> None: ... # Y011 Only simple default values allowed for typed arguments 50 | def f153(x: list[int] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]) -> None: ... # Y011 Only simple default values allowed for typed arguments 51 | def f154(x: tuple[str, ...] = ("foo", ("bar", "baz"))) -> None: ... # Y011 Only simple default values allowed for typed arguments 52 | def f141(x: list[int] = [*range(10)]) -> None: ... # Y011 Only simple default values allowed for typed arguments 53 | def f142(x: list[int] = list(range(10))) -> None: ... # Y011 Only simple default values allowed for typed arguments 54 | def f16(x: frozenset[bytes] = frozenset({b"foo", b"bar", b"baz"})) -> None: ... # Y011 Only simple default values allowed for typed arguments 55 | def f17(x: str = "foo" + "bar") -> None: ... # Y011 Only simple default values allowed for typed arguments 56 | def f18(x: str = b"foo" + b"bar") -> None: ... # Y011 Only simple default values allowed for typed arguments 57 | def f19(x: object = "foo" + 4) -> None: ... # Y011 Only simple default values allowed for typed arguments 58 | def f20(x: int = 5 + 5) -> None: ... # Y011 Only simple default values allowed for typed arguments 59 | def f21(x: complex = 3j - 3j) -> None: ... # Y011 Only simple default values allowed for typed arguments 60 | def f22(x: complex = -42.5j + 4.3j) -> None: ... # Y011 Only simple default values allowed for typed arguments 61 | def f220(x=MyEnum.VAL) -> None: ... 62 | def f221(x: MyEnum = MyEnum.VAL) -> None: ... 63 | def f222(x: MyEnum = MyEnum.val) -> None: ... 64 | def f223(x: MyEnum | int = MyEnum.VAL) -> None: ... 65 | def f224(x: int | MyEnum = MyEnum.VAL) -> None: ... 66 | def f225(x: int = MyEnum.VAL) -> None: ... 67 | def f226(x: MyEnum = MyEnum.foo.bar) -> None: ... 68 | def f227(x: NS.MyEnum = NS.MyEnum.VAL) -> None: ... 69 | def f228(x: NS.MyEnum = {"x": MyEnum.VAL}) -> None: ... 70 | 71 | F220 = MyEnum.VAL 72 | F221: MyEnum = MyEnum.VAL 73 | F225: NS = MyEnum.VAL 74 | F228: NS.MyEnum = {"x": MyEnum.VAL} 75 | F901: float = os.pathsep 76 | 77 | # Special-cased attributes 78 | def f23(x: str = sys.base_prefix) -> None: ... 79 | def f24(x: str = sys.byteorder) -> None: ... 80 | def f25(x: str = sys.exec_prefix) -> None: ... 81 | def f26(x: str = sys.executable) -> None: ... 82 | def f27(x: int = sys.hexversion) -> None: ... 83 | def f28(x: int = sys.maxsize) -> None: ... 84 | def f29(x: str = sys.platform) -> None: ... 85 | def f30(x: str = sys.prefix) -> None: ... 86 | def f31(x: SupportsRead[str] = sys.stdin) -> None: ... 87 | def f32(x: SupportsWrite[str] = sys.stdout) -> None: ... 88 | def f33(x: SupportsWrite[str] = sys.stderr) -> None: ... 89 | def f34(x: str = sys.version) -> None: ... 90 | def f35(x: tuple[int, ...] = sys.version_info) -> None: ... 91 | def f36(x: str = sys.winver) -> None: ... 92 | def f361(x: str | None = sentinel) -> None: ... 93 | def f362(x: str | None = _typeshed.sentinel) -> None: ... 94 | 95 | def f37(x: str = "a_very_long_stringgggggggggggggggggggggggggggggggggggggggggggggg") -> None: ... # Y053 String and bytes literals >50 characters long are not permitted 96 | def f38(x: bytes = b"a_very_long_byte_stringggggggggggggggggggggggggggggggggggggg") -> None: ... # Y053 String and bytes literals >50 characters long are not permitted 97 | 98 | foo: str = "a_very_long_stringgggggggggggggggggggggggggggggggggggggggggggggg" # Y053 String and bytes literals >50 characters long are not permitted 99 | bar: bytes = b"a_very_long_byte_stringggggggggggggggggggggggggggggggggggggg" # Y053 String and bytes literals >50 characters long are not permitted 100 | 101 | # Tests for PEP-570 syntax 102 | def f39(x: "int", /) -> None: ... # Y020 Quoted annotations should never be used in stubs 103 | def f40(x: int, /) -> None: ... 104 | def f41(x: int, /, y: "int") -> None: ... # Y020 Quoted annotations should never be used in stubs 105 | def f42(x: str = "y", /) -> None: ... 106 | def f43(x: str = os.pathsep, /) -> None: ... 107 | 108 | # Long strings inside `Literal` or `Annotated` slices are okay 109 | def f44(x: Literal["loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong"]) -> None: ... 110 | def f55(x: Annotated[str, "metadataaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"]) -> None: ... 111 | 112 | X: TypeAlias = Literal["llllllllllllllllllllllllllonggggggggggggggggggggggggggggggggggggggggg"] 113 | Y: TypeAlias = Annotated[int, "verrrrrrrrrrrrrrrrrrrry looooooooooooooongggggggggggggggggggggggggggggggggggg"] 114 | -------------------------------------------------------------------------------- /tests/attribute_annotations.pyi: -------------------------------------------------------------------------------- 1 | import builtins 2 | import enum 3 | import os 4 | import sys 5 | import typing 6 | from enum import Enum, Flag, ReprEnum, StrEnum 7 | from typing import Final, Final as _Final, TypeAlias 8 | 9 | import typing_extensions 10 | 11 | # We shouldn't emit Y015 for simple default values 12 | field1: int 13 | field2: int = ... 14 | field3 = ... # type: int # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int") 15 | field4: int = 0 16 | field41: int = 0xFFFFFFFF 17 | field42: int = 1234567890 18 | field43: int = -0xFFFFFFFF 19 | field44: int = -1234567890 20 | field5 = 0 # type: int # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int") # Y052 Need type annotation for "field5" 21 | field6 = 0 # Y052 Need type annotation for "field6" 22 | field7 = b"" # Y052 Need type annotation for "field7" 23 | field71 = "foo" # Y052 Need type annotation for "field71" 24 | field72: str = "foo" 25 | field8 = False # Y052 Need type annotation for "field8" 26 | field81 = -1 # Y052 Need type annotation for "field81" 27 | field82: float = -98.43 28 | field83 = -42j # Y052 Need type annotation for "field83" 29 | field84 = 5 + 42j # Y052 Need type annotation for "field84" 30 | field85 = -5 - 42j # Y052 Need type annotation for "field85" 31 | field9 = None # Y026 Use typing_extensions.TypeAlias for type aliases, e.g. "field9: TypeAlias = None" 32 | Field95: TypeAlias = None 33 | Field96: TypeAlias = int | None 34 | Field97: TypeAlias = None | typing.SupportsInt | builtins.str | float | bool 35 | field19 = [1, 2, 3] # Y052 Need type annotation for "field19" 36 | field191: list[int] = [1, 2, 3] 37 | field20 = (1, 2, 3) # Y052 Need type annotation for "field20" 38 | field201: tuple[int, ...] = (1, 2, 3) 39 | field21 = {1, 2, 3} # Y052 Need type annotation for "field21" 40 | field211: set[int] = {1, 2, 3} 41 | field212 = {"foo": "bar"} # Y052 Need type annotation for "field212" 42 | field213: dict[str, str] = {"foo": "bar"} 43 | field22: Final = {"foo": 5} 44 | 45 | # Tests for Final 46 | field11: Final = 1 47 | field12: Final = "foo" 48 | field13: Final = b"foo" 49 | field14: Final = True 50 | field15: _Final = True 51 | field16: typing.Final = "foo" 52 | field17: typing_extensions.Final = "foo" # Y023 Use "typing.Final" instead of "typing_extensions.Final" 53 | field18: Final = -24j 54 | field181: Final = field18 55 | field182: Final = os.pathsep 56 | field183: Final = None 57 | 58 | # We *should* emit Y015 for more complex default values 59 | field221: list[int] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] # Y015 Only simple default values are allowed for assignments 60 | field222: list[int] = [100000000000000000000000000000] # Y054 Numeric literals with a string representation >10 characters long are not permitted 61 | field223: list[int] = [*range(10)] # Y015 Only simple default values are allowed for assignments 62 | field224: list[int] = list(range(10)) # Y015 Only simple default values are allowed for assignments 63 | field225: list[object] = [{}, 1, 2] # Y015 Only simple default values are allowed for assignments 64 | field226: tuple[str | tuple[str, ...], ...] = ("foo", ("foo", "bar")) # Y015 Only simple default values are allowed for assignments 65 | field227: dict[str, object] = {"foo": {"foo": "bar"}} # Y015 Only simple default values are allowed for assignments 66 | field228: dict[str, list[object]] = {"foo": []} # Y015 Only simple default values are allowed for assignments 67 | # When parsed, this case results in `None` being placed in the `.keys` list for the `ast.Dict` node 68 | field229: dict[int, int] = {1: 2, **{3: 4}} # Y015 Only simple default values are allowed for assignments 69 | field23 = "foo" + "bar" # Y015 Only simple default values are allowed for assignments 70 | field24 = b"foo" + b"bar" # Y015 Only simple default values are allowed for assignments 71 | field25 = 5 * 5 # Y015 Only simple default values are allowed for assignments 72 | field26: int = 0xFFFFFFFFF # Y054 Numeric literals with a string representation >10 characters long are not permitted 73 | field27: int = 12345678901 # Y054 Numeric literals with a string representation >10 characters long are not permitted 74 | field28: int = -0xFFFFFFFFF # Y054 Numeric literals with a string representation >10 characters long are not permitted 75 | field29: int = -12345678901 # Y054 Numeric literals with a string representation >10 characters long are not permitted 76 | 77 | class Foo: 78 | field1: int 79 | field2: int = ... 80 | field3 = ... # type: int # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int") 81 | field4: int = 0 82 | field41: int = 0xFFFFFFFF 83 | field42: int = 1234567890 84 | field43: int = -0xFFFFFFFF 85 | field44: int = -1234567890 86 | field5 = 0 # type: int # Y033 Do not use type comments in stubs (e.g. use "x: int" instead of "x = ... # type: int") # Y052 Need type annotation for "field5" 87 | field6 = 0 # Y052 Need type annotation for "field6" 88 | field7 = b"" # Y052 Need type annotation for "field7" 89 | field71 = "foo" # Y052 Need type annotation for "field71" 90 | field72: str = "foo" 91 | field8 = False # Y052 Need type annotation for "field8" 92 | # Tests for Final 93 | field9: Final = 1 94 | field10: Final = "foo" 95 | field11: Final = b"foo" 96 | field12: Final = True 97 | field13: _Final = True 98 | field14: typing.Final = "foo" 99 | field15: typing_extensions.Final = "foo" # Y023 Use "typing.Final" instead of "typing_extensions.Final" 100 | # Standalone strings used to cause issues 101 | field16 = "x" # Y052 Need type annotation for "field16" 102 | if sys.platform == "linux": 103 | field17 = "y" # Y052 Need type annotation for "field17" 104 | elif sys.platform == "win32": 105 | field18 = "z" # Y052 Need type annotation for "field18" 106 | else: 107 | field19 = "w" # Y052 Need type annotation for "field19" 108 | 109 | field20 = [1, 2, 3] # Y052 Need type annotation for "field20" 110 | field201: list[int] = [1, 2, 3] 111 | field21 = (1, 2, 3) # Y052 Need type annotation for "field21" 112 | field211: tuple[int, ...] = (1, 2, 3) 113 | field22 = {1, 2, 3} # Y052 Need type annotation for "field22" 114 | field221: set[int] = {1, 2, 3} 115 | field23: Final = {"foo": 5} 116 | field24 = "foo" + "bar" # Y015 Only simple default values are allowed for assignments 117 | field25 = b"foo" + b"bar" # Y015 Only simple default values are allowed for assignments 118 | field26 = 5 * 5 # Y015 Only simple default values are allowed for assignments 119 | field27 = 0xFFFFFFFFF # Y052 Need type annotation for "field27" # Y054 Numeric literals with a string representation >10 characters long are not permitted 120 | field28 = 12345678901 # Y052 Need type annotation for "field28" # Y054 Numeric literals with a string representation >10 characters long are not permitted 121 | field29 = -0xFFFFFFFFF # Y052 Need type annotation for "field29" # Y054 Numeric literals with a string representation >10 characters long are not permitted 122 | field30 = -12345678901 # Y052 Need type annotation for "field30" # Y054 Numeric literals with a string representation >10 characters long are not permitted 123 | 124 | Field95: TypeAlias = None 125 | Field96: TypeAlias = int | None 126 | Field97: TypeAlias = None | typing.SupportsInt | builtins.str 127 | 128 | # Enums are excluded from Y052 129 | class Enum1(Enum): 130 | FOO = "foo" 131 | 132 | class Enum2(enum.IntEnum): 133 | FOO = 1 134 | 135 | class Enum3(Flag): 136 | FOO = 1 137 | 138 | class Enum4(enum.IntFlag): 139 | FOO = 1 140 | 141 | class Enum5(StrEnum): 142 | FOO = "foo" 143 | 144 | class Enum6(ReprEnum): 145 | FOO = "foo" 146 | 147 | class Enum7(enum.Enum): 148 | FOO = "foo" 149 | 150 | class SpecialEnum(enum.Enum): ... 151 | 152 | class SubclassOfSpecialEnum(SpecialEnum): 153 | STILL_OKAY = "foo" 154 | -------------------------------------------------------------------------------- /tests/union_duplicates.pyi: -------------------------------------------------------------------------------- 1 | # flags: --extend-ignore=Y037 2 | import builtins 3 | import typing 4 | from collections.abc import Mapping 5 | from typing import ( # Y022 Use "type[MyClass]" instead of "typing.Type[MyClass]" (PEP 585 syntax) 6 | Literal, 7 | Type, 8 | Union, 9 | ) 10 | 11 | import typing_extensions 12 | from typing_extensions import ( # Y022 Use "type[MyClass]" instead of "typing_extensions.Type[MyClass]" (PEP 585 syntax) 13 | Type as Type_, 14 | TypeAlias, 15 | ) 16 | 17 | def f1_pipe(x: int | str) -> None: ... 18 | def f2_pipe(x: int | int) -> None: ... # Y016 Duplicate union member "int" 19 | def f3_pipe(x: None | int | int) -> None: ... # Y016 Duplicate union member "int" 20 | def f4_pipe(x: int | None | int) -> None: ... # Y016 Duplicate union member "int" 21 | def f5_pipe(x: int | int | None) -> None: ... # Y016 Duplicate union member "int" 22 | def f6_pipe(x: type[int] | type[str] | type[float]) -> None: ... # Y055 Multiple "type[Foo]" members in a union. Combine them into one, e.g. "type[int | str | float]". 23 | def f7_pipe(x: type[int] | str | type[float]) -> None: ... # Y055 Multiple "type[Foo]" members in a union. Combine them into one, e.g. "type[int | float]". 24 | def f8_pipe(x: builtins.type[int] | builtins.type[str] | builtins.type[float]) -> None: ... # Y055 Multiple "type[Foo]" members in a union. Combine them into one, e.g. "type[int | str | float]". 25 | def f9_pipe(x: builtins.type[int] | str | builtins.type[float]) -> None: ... # Y055 Multiple "type[Foo]" members in a union. Combine them into one, e.g. "type[int | float]". 26 | def f10_pipe(x: type[int] | builtins.type[float]) -> None: ... # Y055 Multiple "type[Foo]" members in a union. Combine them into one, e.g. "type[int | float]". 27 | # typing.Type and typing_extensions.Type are intentionally excluded from Y055 28 | # The following type annotations should not generate any Y055 errors 29 | def f11_pipe(x: Type[int] | Type[str]) -> None: ... 30 | def f12_pipe(x: typing.Type[int] | typing.Type[str]) -> None: ... # Y022 Use "type[MyClass]" instead of "typing.Type[MyClass]" (PEP 585 syntax) # Y022 Use "type[MyClass]" instead of "typing.Type[MyClass]" (PEP 585 syntax) 31 | def f13_pipe(x: Type_[int] | Type_[str]) -> None: ... 32 | def f14_pipe(x: typing_extensions.Type[int] | typing_extensions.Type[str]) -> None: ... # Y022 Use "type[MyClass]" instead of "typing_extensions.Type[MyClass]" (PEP 585 syntax) # Y022 Use "type[MyClass]" instead of "typing_extensions.Type[MyClass]" (PEP 585 syntax) 33 | 34 | def f1_union(x: Union[int, str]) -> None: ... 35 | def f2_union(x: Union[int, int]) -> None: ... # Y016 Duplicate union member "int" 36 | def f3_union(x: Union[None, int, int]) -> None: ... # Y016 Duplicate union member "int" 37 | def f4_union(x: typing.Union[int, None, int]) -> None: ... # Y016 Duplicate union member "int" 38 | def f5_union(x: typing.Union[int, int, None]) -> None: ... # Y016 Duplicate union member "int" 39 | def f6_union(x: Union[type[int], type[str], type[float]]) -> None: ... # Y055 Multiple "type[Foo]" members in a union. Combine them into one, e.g. "type[Union[int, str, float]]". 40 | def f7_union(x: Union[type[int], str, type[float]]) -> None: ... # Y055 Multiple "type[Foo]" members in a union. Combine them into one, e.g. "type[Union[int, float]]". 41 | def f8_union(x: Union[builtins.type[int], builtins.type[str], builtins.type[float]]) -> None: ... # Y055 Multiple "type[Foo]" members in a union. Combine them into one, e.g. "type[Union[int, str, float]]". 42 | def f9_union(x: Union[builtins.type[int], str, builtins.type[float]]) -> None: ... # Y055 Multiple "type[Foo]" members in a union. Combine them into one, e.g. "type[Union[int, float]]". 43 | def f10_union(x: Union[type[int], builtins.type[float]]) -> None: ... # Y055 Multiple "type[Foo]" members in a union. Combine them into one, e.g. "type[Union[int, float]]". 44 | # typing.Type and typing_extensions.Type are intentionally excluded from Y055 45 | # The following type annotations should not generate any Y055 errors 46 | def f11_union(x: Union[Type[int], Type[str]]) -> None: ... 47 | def f12_union(x: Union[typing.Type[int], typing.Type[str]]) -> None: ... # Y022 Use "type[MyClass]" instead of "typing.Type[MyClass]" (PEP 585 syntax) # Y022 Use "type[MyClass]" instead of "typing.Type[MyClass]" (PEP 585 syntax) 48 | def f13_union(x: Union[Type_[int], Type_[str]]) -> None: ... 49 | def f14_union(x: Union[typing_extensions.Type[int], typing_extensions.Type[str]]) -> None: ... # Y022 Use "type[MyClass]" instead of "typing_extensions.Type[MyClass]" (PEP 585 syntax) # Y022 Use "type[MyClass]" instead of "typing_extensions.Type[MyClass]" (PEP 585 syntax) 50 | 51 | just_literals_subscript_union: Union[Literal[1], typing.Literal[2]] # Y030 Multiple Literal members in a union. Use a single Literal, e.g. "Literal[1, 2]". 52 | mixed_subscript_union: Union[bytes, Literal['foo'], typing_extensions.Literal['bar']] # Y030 Multiple Literal members in a union. Combine them into one, e.g. "Literal['foo', 'bar']". # Y023 Use "typing.Literal" instead of "typing_extensions.Literal" 53 | just_literals_pipe_union: TypeAlias = Literal[True] | Literal['idk'] # Y042 Type aliases should use the CamelCase naming convention # Y030 Multiple Literal members in a union. Use a single Literal, e.g. "Literal[True, 'idk']". 54 | _mixed_pipe_union: TypeAlias = Union[Literal[966], bytes, Literal['baz']] # Y042 Type aliases should use the CamelCase naming convention # Y047 Type alias "_mixed_pipe_union" is not used # Y030 Multiple Literal members in a union. Combine them into one, e.g. "Literal[966, 'baz']". 55 | ManyLiteralMembersButNeedsCombining: TypeAlias = int | Literal['a', 'b'] | Literal['baz'] # Y030 Multiple Literal members in a union. Combine them into one, e.g. "Literal['a', 'b', 'baz']". 56 | 57 | a: int | float # No error here, Y041 only applies to argument annotations 58 | b: Union[builtins.float, str, bytes, builtins.int] # No error here, Y041 only applies to argument annotations 59 | def func(arg: float | list[str] | type[bool] | complex) -> None: ... # Y041 Use "complex" instead of "float | complex" (see "The numeric tower" in PEP 484) 60 | 61 | class Foo: 62 | def method(self, arg: int | builtins.float | complex) -> None: ... # Y041 Use "complex" instead of "float | complex" (see "The numeric tower" in PEP 484) # Y041 Use "complex" instead of "int | complex" (see "The numeric tower" in PEP 484) 63 | 64 | c: Union[builtins.complex, memoryview, slice, int] # No error here, Y041 only applies to argument annotations 65 | 66 | # Don't error with Y041 here, the two error messages combined are quite confusing 67 | def foo(d: int | int | float) -> None: ... # Y016 Duplicate union member "int" 68 | # Don't error with Y055 here either 69 | def baz(d: type[int] | type[int]) -> None: ... # Y016 Duplicate union member "type[int]" 70 | 71 | def bar(f: Literal["foo"] | Literal["bar"] | int | float | builtins.bool) -> None: ... # Y030 Multiple Literal members in a union. Combine them into one, e.g. "Literal['foo', 'bar']". # Y041 Use "float" instead of "int | float" (see "The numeric tower" in PEP 484) 72 | 73 | # Type aliases are special-cased to be excluded from Y041 74 | MyTypeAlias: TypeAlias = int | float | bool 75 | MySecondTypeAlias: TypeAlias = Union[builtins.int, str, complex, bool] 76 | MyThirdTypeAlias: TypeAlias = Mapping[str, int | builtins.float | builtins.bool] 77 | 78 | one: str | Literal["foo"] # Y051 "Literal['foo']" is redundant in a union with "str" 79 | Two: TypeAlias = Union[Literal[b"bar", b"baz"], bytes] # Y051 "Literal[b'bar']" is redundant in a union with "bytes" 80 | def three(arg: Literal["foo", 5] | builtins.int | Literal[9, "bar"]) -> None: ... # Y030 Multiple Literal members in a union. Combine them into one, e.g. "Literal['foo', 5, 9, 'bar']". # Y051 "Literal[5]" is redundant in a union with "int" 81 | 82 | class Four: 83 | var: builtins.bool | Literal[True] # Y051 "Literal[True]" is redundant in a union with "bool" 84 | 85 | DupesHereSoNoY051: TypeAlias = int | int | Literal[42] # Y016 Duplicate union member "int" 86 | NightmareAlias1 = int | float | Literal[4, b"bar"] | Literal["foo"] # Y026 Use typing_extensions.TypeAlias for type aliases, e.g. "NightmareAlias1: TypeAlias = int | float | Literal[4, b'bar'] | Literal['foo']" # Y030 Multiple Literal members in a union. Combine them into one, e.g. "Literal[4, b'bar', 'foo']". # Y051 "Literal[4]" is redundant in a union with "int" 87 | nightmare_alias2: TypeAlias = int | float | Literal[True, 4] | Literal["foo"] # Y042 Type aliases should use the CamelCase naming convention # Y030 Multiple Literal members in a union. Combine them into one, e.g. "Literal[True, 4, 'foo']". # Y051 "Literal[4]" is redundant in a union with "int" 88 | DoublyNestedAlias: TypeAlias = Union[type[str], type[float] | type[bytes]] # Y055 Multiple "type[Foo]" members in a union. Combine them into one, e.g. "type[float | bytes]". 89 | # typing.Type and typing_extensions.Type are intentionally excluded from Y055 90 | DoublyNestedAlias2: TypeAlias = Union[Type[str], typing.Type[float], Type_[bytes], typing_extensions.Type[complex]] # Y022 Use "type[MyClass]" instead of "typing.Type[MyClass]" (PEP 585 syntax) # Y022 Use "type[MyClass]" instead of "typing_extensions.Type[MyClass]" (PEP 585 syntax) 91 | -------------------------------------------------------------------------------- /tests/classdefs.pyi: -------------------------------------------------------------------------------- 1 | # flags: --extend-ignore=Y023 2 | 3 | import abc 4 | import builtins 5 | import collections.abc 6 | import enum 7 | import typing 8 | from abc import ABCMeta, abstractmethod 9 | from collections.abc import ( 10 | AsyncGenerator, 11 | AsyncIterable, 12 | AsyncIterator, 13 | Container, 14 | Generator, 15 | Iterable, 16 | Iterator, 17 | Mapping, 18 | ) 19 | from enum import EnumMeta 20 | from typing import Any, Generic, TypeVar, overload 21 | 22 | import typing_extensions 23 | from _typeshed import Self 24 | from typing_extensions import final 25 | 26 | class Bad(object): # Y040 Do not inherit from "object" explicitly, as it is redundant in Python 3 27 | def __new__(cls, *args: Any, **kwargs: Any) -> Bad: ... # Y034 "__new__" methods usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__new__", e.g. "def __new__(cls, *args: Any, **kwargs: Any) -> Self: ..." 28 | def __repr__(self) -> str: ... # Y029 Defining __repr__ or __str__ in a stub is almost always redundant 29 | def __str__(self) -> builtins.str: ... # Y029 Defining __repr__ or __str__ in a stub is almost always redundant 30 | def __eq__(self, other: Any) -> bool: ... # Y032 Prefer "object" to "Any" for the second parameter in "__eq__" methods 31 | def __ne__(self, other: typing.Any) -> typing.Any: ... # Y032 Prefer "object" to "Any" for the second parameter in "__ne__" methods 32 | def __enter__(self) -> Bad: ... # Y034 "__enter__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__enter__", e.g. "def __enter__(self) -> Self: ..." 33 | async def __aenter__(self) -> Bad: ... # Y034 "__aenter__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__aenter__", e.g. "async def __aenter__(self) -> Self: ..." 34 | def __iadd__(self, other: Bad) -> Bad: ... # Y034 "__iadd__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__iadd__", e.g. "def __iadd__(self, other: Bad) -> Self: ..." 35 | 36 | class AlsoBad(int, builtins.object): ... # Y040 Do not inherit from "object" explicitly, as it is redundant in Python 3 37 | 38 | class Good: 39 | def __new__(cls: type[Self], *args: Any, **kwargs: Any) -> Self: ... 40 | @abstractmethod 41 | def __str__(self) -> str: ... 42 | @abc.abstractmethod 43 | def __repr__(self) -> str: ... 44 | def __eq__(self, other: object) -> bool: ... 45 | def __ne__(self, obj: object) -> int: ... 46 | def __enter__(self: Self) -> Self: ... 47 | async def __aenter__(self: Self) -> Self: ... 48 | def __ior__(self: Self, other: Self) -> Self: ... 49 | 50 | class Fine: 51 | @overload 52 | def __new__(cls, foo: int) -> FineSubclass: ... 53 | @overload 54 | def __new__(cls, *args: Any, **kwargs: Any) -> Fine: ... 55 | @abc.abstractmethod 56 | def __str__(self) -> str: ... 57 | @abc.abstractmethod 58 | def __repr__(self) -> str: ... 59 | def __eq__(self, other: Any, strange_extra_arg: list[str]) -> Any: ... 60 | def __ne__(self, *, kw_only_other: Any) -> bool: ... 61 | def __enter__(self) -> None: ... 62 | async def __aenter__(self) -> bool: ... 63 | 64 | class FineSubclass(Fine): ... 65 | 66 | class StrangeButAcceptable(str): 67 | @typing_extensions.overload 68 | def __new__(cls, foo: int) -> StrangeButAcceptableSubclass: ... 69 | @typing_extensions.overload 70 | def __new__(cls, *args: Any, **kwargs: Any) -> StrangeButAcceptable: ... 71 | def __str__(self) -> StrangeButAcceptable: ... 72 | def __repr__(self) -> StrangeButAcceptable: ... 73 | 74 | class StrangeButAcceptableSubclass(StrangeButAcceptable): ... 75 | 76 | class FineAndDandy: 77 | def __str__(self, weird_extra_arg) -> str: ... 78 | def __repr__(self, weird_extra_arg_with_default=...) -> str: ... 79 | 80 | @final 81 | class WillNotBeSubclassed: 82 | def __new__(cls, *args: Any, **kwargs: Any) -> WillNotBeSubclassed: ... 83 | def __enter__(self) -> WillNotBeSubclassed: ... 84 | async def __aenter__(self) -> WillNotBeSubclassed: ... 85 | 86 | # we don't emit an error for these; out of scope for a linter 87 | class InvalidButPluginDoesNotCrash: 88 | def __new__() -> InvalidButPluginDoesNotCrash: ... 89 | def __enter__() -> InvalidButPluginDoesNotCrash: ... 90 | async def __aenter__() -> InvalidButPluginDoesNotCrash: ... 91 | 92 | class BadIterator1(Iterator[int]): 93 | def __iter__(self) -> Iterator[int]: ... # Y034 "__iter__" methods in classes like "BadIterator1" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadIterator1.__iter__", e.g. "def __iter__(self) -> Self: ..." 94 | 95 | class BadIterator2(typing.Iterator[int]): # Y022 Use "collections.abc.Iterator[T]" instead of "typing.Iterator[T]" (PEP 585 syntax) 96 | def __iter__(self) -> Iterator[int]: ... # Y034 "__iter__" methods in classes like "BadIterator2" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadIterator2.__iter__", e.g. "def __iter__(self) -> Self: ..." 97 | 98 | class BadIterator3(typing.Iterator[int]): # Y022 Use "collections.abc.Iterator[T]" instead of "typing.Iterator[T]" (PEP 585 syntax) 99 | def __iter__(self) -> collections.abc.Iterator[int]: ... # Y034 "__iter__" methods in classes like "BadIterator3" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadIterator3.__iter__", e.g. "def __iter__(self) -> Self: ..." 100 | 101 | class BadIterator4(Iterator[int]): 102 | # Note: *Iterable*, not *Iterator*, returned! 103 | def __iter__(self) -> Iterable[int]: ... # Y034 "__iter__" methods in classes like "BadIterator4" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadIterator4.__iter__", e.g. "def __iter__(self) -> Self: ..." 104 | 105 | class IteratorReturningIterable: 106 | def __iter__(self) -> Iterable[str]: ... # Y045 "__iter__" methods should return an Iterator, not an Iterable 107 | 108 | class IteratorReturningSimpleGenerator1: 109 | def __iter__(self) -> Generator: ... # Y058 Use "Iterator" as the return value for simple "__iter__" methods, e.g. "def __iter__(self) -> Iterator: ..." 110 | 111 | class IteratorReturningSimpleGenerator2: 112 | def __iter__(self) -> collections.abc.Generator[str, Any, None]: ... # Y058 Use "Iterator" as the return value for simple "__iter__" methods, e.g. "def __iter__(self) -> Iterator[str]: ..." 113 | 114 | class IteratorReturningComplexGenerator: 115 | def __iter__(self) -> Generator[str, int, bytes]: ... 116 | 117 | class BadAsyncIterator(collections.abc.AsyncIterator[str]): 118 | def __aiter__(self) -> typing.AsyncIterator[str]: ... # Y034 "__aiter__" methods in classes like "BadAsyncIterator" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadAsyncIterator.__aiter__", e.g. "def __aiter__(self) -> Self: ..." # Y022 Use "collections.abc.AsyncIterator[T]" instead of "typing.AsyncIterator[T]" (PEP 585 syntax) 119 | 120 | class AsyncIteratorReturningAsyncIterable: 121 | def __aiter__(self) -> AsyncIterable[str]: ... # Y045 "__aiter__" methods should return an AsyncIterator, not an AsyncIterable 122 | 123 | class AsyncIteratorReturningSimpleAsyncGenerator1: 124 | def __aiter__(self) -> AsyncGenerator: ... # Y058 Use "AsyncIterator" as the return value for simple "__aiter__" methods, e.g. "def __aiter__(self) -> AsyncIterator: ..." 125 | 126 | class AsyncIteratorReturningSimpleAsyncGenerator2: 127 | def __aiter__(self) -> collections.abc.AsyncGenerator[str, Any]: ... # Y058 Use "AsyncIterator" as the return value for simple "__aiter__" methods, e.g. "def __aiter__(self) -> AsyncIterator[str]: ..." 128 | 129 | class AsyncIteratorReturningComplexAsyncGenerator: 130 | def __aiter__(self) -> AsyncGenerator[str, int]: ... 131 | 132 | class MetaclassInWhichSelfCannotBeUsed(type): 133 | def __new__(cls) -> MetaclassInWhichSelfCannotBeUsed: ... 134 | def __enter__(self) -> MetaclassInWhichSelfCannotBeUsed: ... 135 | async def __aenter__(self) -> MetaclassInWhichSelfCannotBeUsed: ... 136 | def __isub__(self, other: MetaclassInWhichSelfCannotBeUsed) -> MetaclassInWhichSelfCannotBeUsed: ... 137 | 138 | class MetaclassInWhichSelfCannotBeUsed2(EnumMeta): 139 | def __new__(cls) -> MetaclassInWhichSelfCannotBeUsed2: ... 140 | def __enter__(self) -> MetaclassInWhichSelfCannotBeUsed2: ... 141 | async def __aenter__(self) -> MetaclassInWhichSelfCannotBeUsed2: ... 142 | def __isub__(self, other: MetaclassInWhichSelfCannotBeUsed2) -> MetaclassInWhichSelfCannotBeUsed2: ... 143 | 144 | class MetaclassInWhichSelfCannotBeUsed3(enum.EnumType): 145 | def __new__(cls) -> MetaclassInWhichSelfCannotBeUsed3: ... 146 | def __enter__(self) -> MetaclassInWhichSelfCannotBeUsed3: ... 147 | async def __aenter__(self) -> MetaclassInWhichSelfCannotBeUsed3: ... 148 | def __isub__(self, other: MetaclassInWhichSelfCannotBeUsed3) -> MetaclassInWhichSelfCannotBeUsed3: ... 149 | 150 | class MetaclassInWhichSelfCannotBeUsed4(ABCMeta): 151 | def __new__(cls) -> MetaclassInWhichSelfCannotBeUsed4: ... 152 | def __enter__(self) -> MetaclassInWhichSelfCannotBeUsed4: ... 153 | async def __aenter__(self) -> MetaclassInWhichSelfCannotBeUsed4: ... 154 | def __isub__(self, other: MetaclassInWhichSelfCannotBeUsed4) -> MetaclassInWhichSelfCannotBeUsed4: ... 155 | 156 | class Abstract(Iterator[str]): 157 | @abstractmethod 158 | def __iter__(self) -> Iterator[str]: ... 159 | @abstractmethod 160 | def __enter__(self) -> Abstract: ... 161 | @abstractmethod 162 | async def __aenter__(self) -> Abstract: ... 163 | 164 | class GoodIterator(Iterator[str]): 165 | def __iter__(self: Self) -> Self: ... 166 | 167 | class GoodAsyncIterator(AsyncIterator[int]): 168 | def __aiter__(self: Self) -> Self: ... 169 | 170 | class DoesNotInheritFromIterator: 171 | def __iter__(self) -> DoesNotInheritFromIterator: ... 172 | 173 | class Unannotated: 174 | def __new__(cls, *args, **kwargs): ... 175 | def __iter__(self): ... 176 | def __aiter__(self): ... 177 | async def __aenter__(self): ... 178 | def __repr__(self): ... 179 | def __str__(self): ... 180 | def __eq__(self): ... 181 | def __ne__(self): ... 182 | def __iadd__(self): ... 183 | def __ior__(self): ... 184 | 185 | def __repr__(self) -> str: ... 186 | def __str__(self) -> str: ... 187 | def __eq__(self, other: Any) -> bool: ... 188 | def __ne__(self, other: Any) -> bool: ... 189 | def __imul__(self, other: Any) -> list[str]: ... 190 | 191 | _S = TypeVar("_S") 192 | _T = TypeVar("_T") 193 | 194 | class BadGeneric(Generic[_T], int): ... # Y059 "Generic[]" should always be the last base class 195 | class GoodGeneric(Generic[_T]): ... 196 | 197 | class BadGeneric2(int, typing.Generic[_T], str): ... # Y059 "Generic[]" should always be the last base class 198 | class GoodGeneric2(int, typing.Generic[_T]): ... 199 | 200 | class BadGeneric3(typing_extensions.Generic[_T], int, str): ... # Y059 "Generic[]" should always be the last base class 201 | class GoodGeneric3(int, str, typing_extensions.Generic[_T]): ... 202 | 203 | class BadGeneric4(Generic[_T], Iterable[int], str): ... # Y059 "Generic[]" should always be the last base class 204 | class GoodGeneric4(Iterable[int], str, Generic[_T]): ... 205 | 206 | class RedundantGeneric1(Iterable[_T], Generic[_T]): ... # Y060 Redundant inheritance from "Generic[_T]"; class would be inferred as generic anyway 207 | class Corrected1(Iterable[_T]): ... 208 | 209 | class RedundantGeneric2(Generic[_S], GoodGeneric[_S]): ... # Y059 "Generic[]" should always be the last base class # Y060 Redundant inheritance from "Generic[_S]"; class would be inferred as generic anyway 210 | class Corrected2(GoodGeneric[_S]): ... 211 | 212 | class RedundantGeneric3(int, Iterator[_T], str, float, memoryview, bytes, Generic[_T]): ... # Y060 Redundant inheritance from "Generic[_T]"; class would be inferred as generic anyway 213 | class Corrected3(int, Iterator[_T], str, float, memoryview, bytes): ... 214 | 215 | class RedundantGeneric4(Iterable[_T], Iterator[_T], Generic[_T]): ... # Y060 Redundant inheritance from "Generic[_T]"; class would be inferred as generic anyway 216 | class Corrected4(Iterable[_T], Iterator[_T]): ... 217 | 218 | class BadAndRedundantGeneric(object, Generic[_T], Container[_T]): ... # Y040 Do not inherit from "object" explicitly, as it is redundant in Python 3 # Y059 "Generic[]" should always be the last base class # Y060 Redundant inheritance from "Generic[_T]"; class would be inferred as generic anyway 219 | class Corrected5(Container[_T]): ... 220 | 221 | # Strictly speaking this inheritance from Generic is "redundant", 222 | # but people may consider it more readable to explicitly inherit from Generic, 223 | # so we deliberately don't flag it with Y060 224 | class GoodGeneric5(Container[_S], Iterator[_T], Generic[_S, _T]): ... 225 | 226 | # And these definitely arent't redundant, 227 | # since the order of the type variables is changed via the inheritance from Generic: 228 | class GoodGeneric6(Container[_S], Iterator[_T], Generic[_T, _S]): ... 229 | class GoodGeneric7(Mapping[_S, _T], Generic[_T, _S]): ... 230 | -------------------------------------------------------------------------------- /tests/imports.pyi: -------------------------------------------------------------------------------- 1 | # flags: --extend-ignore=F401,F811 2 | # 3 | # Note: DO NOT RUN ISORT ON THIS FILE. 4 | # It's excluded in our pyproject.toml. 5 | 6 | # BAD IMPORTS (Y044) 7 | from __future__ import annotations # Y044 "from __future__ import annotations" has no effect in stub files. 8 | 9 | # GOOD IMPORTS 10 | import re 11 | import typing 12 | import typing_extensions 13 | import collections 14 | import collections.abc 15 | from collections import ChainMap, Counter, OrderedDict, UserDict, UserList, UserString, defaultdict, deque 16 | from collections.abc import ( 17 | Awaitable, 18 | Coroutine, 19 | AsyncIterable, 20 | AsyncIterator, 21 | AsyncGenerator, 22 | Hashable, 23 | Iterable, 24 | Iterator, 25 | Generator, 26 | Reversible, 27 | Set as AbstractSet, 28 | Sized, 29 | Container, 30 | Callable, 31 | Collection, 32 | MutableSet, 33 | MutableMapping, 34 | MappingView, 35 | KeysView, 36 | ItemsView, 37 | ValuesView, 38 | Sequence, 39 | MutableSequence, 40 | ) 41 | from re import Match, Pattern 42 | # Things that are of no use for stub files are intentionally omitted. 43 | from typing import ( 44 | Any, 45 | ClassVar, 46 | Final, 47 | Generic, 48 | Protocol, 49 | TypeVar, 50 | SupportsAbs, 51 | SupportsBytes, 52 | SupportsComplex, 53 | SupportsFloat, 54 | SupportsIndex, 55 | SupportsInt, 56 | SupportsRound, 57 | BinaryIO, 58 | IO, 59 | Literal, 60 | NamedTuple, 61 | TextIO, 62 | TypedDict, 63 | AnyStr, 64 | NewType, 65 | NoReturn, 66 | final, 67 | overload, 68 | ) 69 | from typing_extensions import ( 70 | Concatenate, 71 | ParamSpec, 72 | TypeAlias, 73 | TypeGuard, 74 | Annotated, 75 | ) 76 | 77 | # BAD IMPORTS (Y022 code) 78 | from typing import Dict # Y022 Use "dict[KeyType, ValueType]" instead of "typing.Dict[KeyType, ValueType]" (PEP 585 syntax) 79 | from typing import Counter # Y022 Use "collections.Counter[KeyType]" instead of "typing.Counter[KeyType]" (PEP 585 syntax) 80 | from typing import AsyncContextManager # Y022 Use "contextlib.AbstractAsyncContextManager[T]" instead of "typing.AsyncContextManager[T]" (PEP 585 syntax) 81 | from typing import ChainMap # Y022 Use "collections.ChainMap[KeyType, ValueType]" instead of "typing.ChainMap[KeyType, ValueType]" (PEP 585 syntax) 82 | from typing import ContextManager # Y022 Use "contextlib.AbstractContextManager[T]" instead of "typing.ContextManager[T]" (PEP 585 syntax) 83 | from typing import OrderedDict # Y022 Use "collections.OrderedDict[KeyType, ValueType]" instead of "typing.OrderedDict[KeyType, ValueType]" (PEP 585 syntax) 84 | from typing import Callable # Y022 Use "collections.abc.Callable" instead of "typing.Callable" (PEP 585 syntax) 85 | from typing import Container # Y022 Use "collections.abc.Container[T]" instead of "typing.Container[T]" (PEP 585 syntax) 86 | from typing import Hashable # Y022 Use "collections.abc.Hashable" instead of "typing.Hashable" (PEP 585 syntax) 87 | from typing import ItemsView # Y022 Use "collections.abc.ItemsView[KeyType, ValueType]" instead of "typing.ItemsView[KeyType, ValueType]" (PEP 585 syntax) 88 | from typing import Iterable # Y022 Use "collections.abc.Iterable[T]" instead of "typing.Iterable[T]" (PEP 585 syntax) 89 | from typing import Iterator # Y022 Use "collections.abc.Iterator[T]" instead of "typing.Iterator[T]" (PEP 585 syntax) 90 | from typing import KeysView # Y022 Use "collections.abc.KeysView[KeyType]" instead of "typing.KeysView[KeyType]" (PEP 585 syntax) 91 | from typing import Mapping # Y022 Use "collections.abc.Mapping[KeyType, ValueType]" instead of "typing.Mapping[KeyType, ValueType]" (PEP 585 syntax) 92 | from typing import MappingView # Y022 Use "collections.abc.MappingView" instead of "typing.MappingView" (PEP 585 syntax) 93 | from typing import MutableMapping # Y022 Use "collections.abc.MutableMapping[KeyType, ValueType]" instead of "typing.MutableMapping[KeyType, ValueType]" (PEP 585 syntax) 94 | from typing import MutableSequence # Y022 Use "collections.abc.MutableSequence[T]" instead of "typing.MutableSequence[T]" (PEP 585 syntax) 95 | from typing import MutableSet # Y022 Use "collections.abc.MutableSet[T]" instead of "typing.MutableSet[T]" (PEP 585 syntax) 96 | from typing import Sequence # Y022 Use "collections.abc.Sequence[T]" instead of "typing.Sequence[T]" (PEP 585 syntax) 97 | from typing import Sized # Y022 Use "collections.abc.Sized" instead of "typing.Sized" (PEP 585 syntax) 98 | from typing import ValuesView # Y022 Use "collections.abc.ValuesView[ValueType]" instead of "typing.ValuesView[ValueType]" (PEP 585 syntax) 99 | from typing import Awaitable # Y022 Use "collections.abc.Awaitable[T]" instead of "typing.Awaitable[T]" (PEP 585 syntax) 100 | from typing import AsyncIterator # Y022 Use "collections.abc.AsyncIterator[T]" instead of "typing.AsyncIterator[T]" (PEP 585 syntax) 101 | from typing import AsyncIterable # Y022 Use "collections.abc.AsyncIterable[T]" instead of "typing.AsyncIterable[T]" (PEP 585 syntax) 102 | from typing import Coroutine # Y022 Use "collections.abc.Coroutine[YieldType, SendType, ReturnType]" instead of "typing.Coroutine[YieldType, SendType, ReturnType]" (PEP 585 syntax) 103 | from typing import Collection # Y022 Use "collections.abc.Collection[T]" instead of "typing.Collection[T]" (PEP 585 syntax) 104 | from typing import AsyncGenerator # Y022 Use "collections.abc.AsyncGenerator[YieldType, SendType]" instead of "typing.AsyncGenerator[YieldType, SendType]" (PEP 585 syntax) 105 | from typing import Reversible # Y022 Use "collections.abc.Reversible[T]" instead of "typing.Reversible[T]" (PEP 585 syntax) 106 | from typing import Generator # Y022 Use "collections.abc.Generator[YieldType, SendType, ReturnType]" instead of "typing.Generator[YieldType, SendType, ReturnType]" (PEP 585 syntax) 107 | from typing import Match # Y022 Use "re.Match[T]" instead of "typing.Match[T]" (PEP 585 syntax) 108 | from typing import Pattern # Y022 Use "re.Pattern[T]" instead of "typing.Pattern[T]" (PEP 585 syntax) 109 | from typing_extensions import Dict # Y022 Use "dict[KeyType, ValueType]" instead of "typing_extensions.Dict[KeyType, ValueType]" (PEP 585 syntax) 110 | from typing_extensions import Counter # Y022 Use "collections.Counter[KeyType]" instead of "typing_extensions.Counter[KeyType]" (PEP 585 syntax) 111 | from typing_extensions import AsyncContextManager # Y022 Use "contextlib.AbstractAsyncContextManager[T]" instead of "typing_extensions.AsyncContextManager[T]" (PEP 585 syntax) 112 | from typing_extensions import ChainMap # Y022 Use "collections.ChainMap[KeyType, ValueType]" instead of "typing_extensions.ChainMap[KeyType, ValueType]" (PEP 585 syntax) 113 | from typing_extensions import ContextManager # Y022 Use "contextlib.AbstractContextManager[T]" instead of "typing_extensions.ContextManager[T]" (PEP 585 syntax) 114 | from typing_extensions import OrderedDict # Y022 Use "collections.OrderedDict[KeyType, ValueType]" instead of "typing_extensions.OrderedDict[KeyType, ValueType]" (PEP 585 syntax) 115 | from typing_extensions import Callable # Y022 Use "collections.abc.Callable" instead of "typing_extensions.Callable" (PEP 585 syntax) 116 | from typing_extensions import Container # Y022 Use "collections.abc.Container[T]" instead of "typing_extensions.Container[T]" (PEP 585 syntax) 117 | from typing_extensions import Hashable # Y022 Use "collections.abc.Hashable" instead of "typing_extensions.Hashable" (PEP 585 syntax) 118 | from typing_extensions import ItemsView # Y022 Use "collections.abc.ItemsView[KeyType, ValueType]" instead of "typing_extensions.ItemsView[KeyType, ValueType]" (PEP 585 syntax) 119 | from typing_extensions import Iterable # Y022 Use "collections.abc.Iterable[T]" instead of "typing_extensions.Iterable[T]" (PEP 585 syntax) 120 | from typing_extensions import Iterator # Y022 Use "collections.abc.Iterator[T]" instead of "typing_extensions.Iterator[T]" (PEP 585 syntax) 121 | from typing_extensions import KeysView # Y022 Use "collections.abc.KeysView[KeyType]" instead of "typing_extensions.KeysView[KeyType]" (PEP 585 syntax) 122 | from typing_extensions import Mapping # Y022 Use "collections.abc.Mapping[KeyType, ValueType]" instead of "typing_extensions.Mapping[KeyType, ValueType]" (PEP 585 syntax) 123 | from typing_extensions import MappingView # Y022 Use "collections.abc.MappingView" instead of "typing_extensions.MappingView" (PEP 585 syntax) 124 | from typing_extensions import MutableMapping # Y022 Use "collections.abc.MutableMapping[KeyType, ValueType]" instead of "typing_extensions.MutableMapping[KeyType, ValueType]" (PEP 585 syntax) 125 | from typing_extensions import MutableSequence # Y022 Use "collections.abc.MutableSequence[T]" instead of "typing_extensions.MutableSequence[T]" (PEP 585 syntax) 126 | from typing_extensions import MutableSet # Y022 Use "collections.abc.MutableSet[T]" instead of "typing_extensions.MutableSet[T]" (PEP 585 syntax) 127 | from typing_extensions import Sequence # Y022 Use "collections.abc.Sequence[T]" instead of "typing_extensions.Sequence[T]" (PEP 585 syntax) 128 | from typing_extensions import Sized # Y022 Use "collections.abc.Sized" instead of "typing_extensions.Sized" (PEP 585 syntax) 129 | from typing_extensions import ValuesView # Y022 Use "collections.abc.ValuesView[ValueType]" instead of "typing_extensions.ValuesView[ValueType]" (PEP 585 syntax) 130 | from typing_extensions import Awaitable # Y022 Use "collections.abc.Awaitable[T]" instead of "typing_extensions.Awaitable[T]" (PEP 585 syntax) 131 | from typing_extensions import AsyncIterator # Y022 Use "collections.abc.AsyncIterator[T]" instead of "typing_extensions.AsyncIterator[T]" (PEP 585 syntax) 132 | from typing_extensions import AsyncIterable # Y022 Use "collections.abc.AsyncIterable[T]" instead of "typing_extensions.AsyncIterable[T]" (PEP 585 syntax) 133 | from typing_extensions import Coroutine # Y022 Use "collections.abc.Coroutine[YieldType, SendType, ReturnType]" instead of "typing_extensions.Coroutine[YieldType, SendType, ReturnType]" (PEP 585 syntax) 134 | from typing_extensions import Collection # Y022 Use "collections.abc.Collection[T]" instead of "typing_extensions.Collection[T]" (PEP 585 syntax) 135 | from typing_extensions import AsyncGenerator # Y022 Use "collections.abc.AsyncGenerator[YieldType, SendType]" instead of "typing_extensions.AsyncGenerator[YieldType, SendType]" (PEP 585 syntax) 136 | from typing_extensions import Reversible # Y022 Use "collections.abc.Reversible[T]" instead of "typing_extensions.Reversible[T]" (PEP 585 syntax) 137 | from typing_extensions import Generator # Y022 Use "collections.abc.Generator[YieldType, SendType, ReturnType]" instead of "typing_extensions.Generator[YieldType, SendType, ReturnType]" (PEP 585 syntax) 138 | from typing_extensions import Match # Y022 Use "re.Match[T]" instead of "typing_extensions.Match[T]" (PEP 585 syntax) 139 | from typing_extensions import Pattern # Y022 Use "re.Pattern[T]" instead of "typing_extensions.Pattern[T]" (PEP 585 syntax) 140 | 141 | # BAD IMPORTS (Y023 code) 142 | from typing_extensions import ClassVar # Y023 Use "typing.ClassVar[T]" instead of "typing_extensions.ClassVar[T]" 143 | from typing_extensions import runtime_checkable # Y023 Use "typing.runtime_checkable" instead of "typing_extensions.runtime_checkable" 144 | from typing_extensions import Literal # Y023 Use "typing.Literal" instead of "typing_extensions.Literal" 145 | from typing_extensions import final # Y023 Use "typing.final" instead of "typing_extensions.final" 146 | from typing_extensions import Final # Y023 Use "typing.Final" instead of "typing_extensions.Final" 147 | from typing_extensions import TypedDict # Y023 Use "typing.TypedDict" instead of "typing_extensions.TypedDict" 148 | 149 | # BAD IMPORTS: OTHER 150 | from collections import namedtuple # Y024 Use "typing.NamedTuple" instead of "collections.namedtuple" 151 | from collections.abc import Set # Y025 Use "from collections.abc import Set as AbstractSet" to avoid confusion with "builtins.set" 152 | from typing import AbstractSet # Y038 Use "from collections.abc import Set as AbstractSet" instead of "from typing import AbstractSet" (PEP 585 syntax) 153 | from typing_extensions import AbstractSet # Y038 Use "from collections.abc import Set as AbstractSet" instead of "from typing_extensions import AbstractSet" (PEP 585 syntax) 154 | from typing import Text # Y039 Use "str" instead of "typing.Text" 155 | from typing_extensions import Text # Y039 Use "str" instead of "typing_extensions.Text" 156 | from typing import ByteString # Y057 Do not use typing.ByteString, which has unclear semantics and is deprecated 157 | from collections.abc import ByteString # Y057 Do not use collections.abc.ByteString, which has unclear semantics and is deprecated 158 | 159 | # GOOD ATTRIBUTE ACCESS 160 | foo: typing.SupportsIndex 161 | baz: re.Pattern[str] 162 | _T = typing_extensions.TypeVar("_T", default=bool | None) # Y018 TypeVar "_T" is not used 163 | 164 | @typing.final 165 | def bar(arg: collections.abc.Sized) -> typing.Literal[True]: ... 166 | 167 | class Fish: 168 | blah: collections.deque[int] 169 | def method(self, arg: typing.SupportsInt = ...) -> None: ... 170 | 171 | # BAD ATTRIBUTE ACCESS (Y022 code) 172 | a: typing.Dict[str, int] # Y022 Use "dict[KeyType, ValueType]" instead of "typing.Dict[KeyType, ValueType]" (PEP 585 syntax) 173 | h: typing_extensions.Awaitable[float] # Y022 Use "collections.abc.Awaitable[T]" instead of "typing_extensions.Awaitable[T]" (PEP 585 syntax) 174 | i: typing_extensions.ContextManager[None] # Y022 Use "contextlib.AbstractContextManager[T]" instead of "typing_extensions.ContextManager[T]" (PEP 585 syntax) 175 | k: typing_extensions.OrderedDict[int, str] # Y022 Use "collections.OrderedDict[KeyType, ValueType]" instead of "typing_extensions.OrderedDict[KeyType, ValueType]" (PEP 585 syntax) 176 | l: typing.ContextManager # Y022 Use "contextlib.AbstractContextManager[T]" instead of "typing.ContextManager[T]" (PEP 585 syntax) 177 | n: typing.Match[bytes] # Y022 Use "re.Match[T]" instead of "typing.Match[T]" (PEP 585 syntax) 178 | 179 | def func1() -> typing.Counter[float]: ... # Y022 Use "collections.Counter[KeyType]" instead of "typing.Counter[KeyType]" (PEP 585 syntax) 180 | def func2(c: typing.AsyncContextManager[None]) -> None: ... # Y022 Use "contextlib.AbstractAsyncContextManager[T]" instead of "typing.AsyncContextManager[T]" (PEP 585 syntax) 181 | def func3(d: typing.ChainMap[int, str] = ...) -> None: ... # Y022 Use "collections.ChainMap[KeyType, ValueType]" instead of "typing.ChainMap[KeyType, ValueType]" (PEP 585 syntax) 182 | 183 | class Spam: 184 | def meth1(self) -> typing_extensions.DefaultDict[bytes, bytes]: ... # Y022 Use "collections.defaultdict[KeyType, ValueType]" instead of "typing_extensions.DefaultDict[KeyType, ValueType]" (PEP 585 syntax) 185 | def meth2(self, f: typing_extensions.ChainMap[str, str]) -> None: ... # Y022 Use "collections.ChainMap[KeyType, ValueType]" instead of "typing_extensions.ChainMap[KeyType, ValueType]" (PEP 585 syntax) 186 | def meth3(self, g: typing_extensions.AsyncContextManager[Any] = ...) -> None: ... # Y022 Use "contextlib.AbstractAsyncContextManager[T]" instead of "typing_extensions.AsyncContextManager[T]" (PEP 585 syntax) 187 | 188 | # BAD ATTRIBUTE ACCESS (Y023 code) 189 | @typing_extensions.final # Y023 Use "typing.final" instead of "typing_extensions.final" 190 | class Foo: 191 | attribute: typing_extensions.ClassVar[int] # Y023 Use "typing.ClassVar[T]" instead of "typing_extensions.ClassVar[T]" 192 | attribute2: typing_extensions.Final[int] # Y023 Use "typing.Final" instead of "typing_extensions.Final" 193 | 194 | # BAD ATTRIBUTE ACCESS: OTHER 195 | j: collections.namedtuple # Y024 Use "typing.NamedTuple" instead of "collections.namedtuple" 196 | m: typing.Text # Y039 Use "str" instead of "typing.Text" 197 | o: typing.ByteString # Y057 Do not use typing.ByteString, which has unclear semantics and is deprecated 198 | p: collections.abc.ByteString # Y057 Do not use collections.abc.ByteString, which has unclear semantics and is deprecated 199 | -------------------------------------------------------------------------------- /ERRORCODES.md: -------------------------------------------------------------------------------- 1 | ## Types of warnings 2 | 3 | flake8-pyi's error codes can currently be divided into the following categories: 4 | 5 | | Category | Description | Example 6 | |---------------------|-------------|--------- 7 | | Understanding stubs | Stub files differ from `.py` files in many respects when it comes to how they work and how they're used. These error codes flag antipatterns that may demonstrate that a user does not fully understand the semantics or purpose of stub files. | Y010 8 | | Correctness | These error codes flag places where it looks like the stub might be incorrect in a way that would be visible for users of the stub. | Y001 9 | | Redundant code | These error codes flag places where it looks like code could simply be deleted. | Y016 10 | | Style | These error codes enforce a consistent, stub-specific style guide. | Y009 11 | 12 | ## List of warnings 13 | 14 | The following warnings are currently emitted by default: 15 | 16 | | Code | Description | Code category 17 | |------|-------------|--------------- 18 | | Y001 | Names of `TypeVar`s, `ParamSpec`s and `TypeVarTuple`s in stubs should usually start with `_`. This makes sure you don't accidentally expose names internal to the stub. | Correctness 19 | | Y002 | An `if` test must be a simple comparison against `sys.platform` or `sys.version_info`. Stub files support simple conditionals to indicate differences between Python versions or platforms, but type checkers only understand a limited subset of Python syntax. This warning is emitted on conditionals that type checkers may not understand. | Correctness 20 | | Y003 | Unrecognized `sys.version_info` check. Similar to Y002, but adds some additional checks specific to `sys.version_info` comparisons. | Correctness 21 | | Y004 | Version comparison must use only major and minor version. Type checkers like mypy don't know about patch versions of Python (e.g. 3.4.3 versus 3.4.4), only major and minor versions (3.3 versus 3.4). Therefore, version checks in stubs should only use the major and minor versions. If new functionality was introduced in a patch version, pretend that it was there all along. | Correctness 22 | | Y005 | Version comparison must be against a length-n tuple. | Correctness 23 | | Y006 | Use only `<` and `>=` for version comparisons. Comparisons involving `>` and `<=` may produce unintuitive results when tools do use the full `sys.version_info` tuple. | Correctness 24 | | Y007 | Unrecognized `sys.platform` check. Platform checks should be simple string comparisons. | Correctness 25 | | Y008 | Unrecognized platform in a `sys.platform` check. To prevent you from typos, we warn if you use a platform name outside a small set of known platforms (e.g. `"linux"` and `"win32"`). | Correctness 26 | | Y009 | Empty class or function body should contain `...`, not `pass`. | Style 27 | | Y010 | Function body must contain only `...`. Stub files are never executed at runtime, so function bodies should be empty. | Understanding stubs 28 | | Y011 | Only simple default values (`int`, `float`, `complex`, `bytes`, `str`, `bool`, `None`, `...`, or simple container literals) are allowed for typed function arguments. Type checkers ignore the default value, so the default value is not useful information for type-checking, but it may be useful information for other users of stubs such as IDEs. If you're writing a stub for a function that has a more complex default value, use `...` instead of trying to reproduce the runtime default exactly in the stub. Also use `...` for very long numbers, very long strings, very long bytes, or defaults that vary according to the machine Python is being run on. | Style 29 | | Y012 | Class body must not contain `pass`. | Style 30 | | Y013 | Non-empty class body must not contain `...`. | Redundant code 31 | | Y014 | Only simple default values are allowed for any function arguments. A stronger version of Y011 that includes arguments without type annotations. | Style 32 | | Y015 | Only simple default values are allowed for assignments. Similar to Y011, but for assignments rather than parameter annotations. | Style 33 | | Y016 | Unions shouldn't contain duplicates, e.g. `str \| str` is not allowed. | Redundant code 34 | | Y017 | Stubs should not contain assignments with multiple targets or non-name targets. E.g. `T, S = TypeVar("T"), TypeVar("S")` is disallowed, as is `foo.bar = TypeVar("T")`. | Style 35 | | Y018 | A private `TypeVar` should be used at least once in the file in which it is defined. | Redundant code 36 | | Y019 | Certain kinds of methods should use [`typing_extensions.Self`](https://docs.python.org/3/library/typing.html#typing.Self) instead of defining custom `TypeVar`s for their return annotation. This check currently applies for instance methods that return `self`, class methods that return an instance of `cls`, and `__new__` methods. | Style 37 | | Y020 | Quoted annotations should never be used in stubs. Since stub files are never executed at runtime, forward references can be used in any location without having to use quotes. (See also: Y044.) | Understanding stubs 38 | | Y021 | Docstrings should not be included in stubs. | Style 39 | | Y022 | The `typing` and `typing_extensions` modules include various aliases to stdlib objects. Use these as little as possible (e.g. prefer `builtins.list` over `typing.List`, `collections.Counter` over `typing.Counter`, etc.). | Style 40 | | Y023 | Where there is no detriment to backwards compatibility, import objects such as `ClassVar` and `NoReturn` from `typing` rather than `typing_extensions`. | Style 41 | | Y024 | Use `typing.NamedTuple` instead of `collections.namedtuple`, as it allows for more precise type inference. | Correctness 42 | | Y025 | Always alias `collections.abc.Set` when importing it, so as to avoid confusion with `builtins.set`. E.g. use `from collections.abc import Set as AbstractSet` instead of `from collections.abc import Set`. | Style 43 | | Y026 | Type aliases should be explicitly demarcated with `typing.TypeAlias` (or use a [PEP-695 type statement](https://docs.python.org/3/reference/simple_stmts.html#the-type-statement)). | Correctness 44 | | Y028 | Always use class-based syntax for `typing.NamedTuple`, instead of assignment-based syntax. | Correctness 45 | | Y029 | It is almost always redundant to define `__str__` or `__repr__` in a stub file, as the signatures are almost always identical to `object.__str__` and `object.__repr__`. | Understanding stubs 46 | | Y030 | Union expressions should never have more than one `Literal` member, as `Literal[1] \| Literal[2]` is semantically identical to `Literal[1, 2]`. | Style 47 | | Y031 | `TypedDict`s should use class-based syntax instead of assignment-based syntax wherever possible. (In situations where this is not possible, such as if a field is a Python keyword or an invalid identifier, this error will not be emitted.) | Style 48 | | Y032 | The second argument of an `__eq__` or `__ne__` method should usually be annotated with `object` rather than `Any`. | Correctness 49 | | Y033 | Do not use type comments (e.g. `x = ... # type: int`) in stubs. Always use annotations instead (e.g. `x: int`). | Style 50 | | Y034 | Y034 detects common errors where certain methods are annotated as having a fixed return type, despite returning `self` at runtime. Such methods should be annotated with `typing_extensions.Self`. This check looks for:

  **1.**  Any in-place BinOp dunder methods (`__iadd__`, `__ior__`, etc.) that do not return `Self`.
  **2.**  `__new__`, `__enter__` and `__aenter__` methods that return the class's name unparameterised.
  **3.**  `__iter__` methods that return `Iterator`, even if the class inherits directly from `Iterator`.
  **4.**  `__aiter__` methods that return `AsyncIterator`, even if the class inherits directly from `AsyncIterator`.

This check excludes methods decorated with `@overload` or `@abstractmethod`. | Correctness 51 | | Y035 | `__all__`, `__match_args__` and `__slots__` in a stub file should always have values, as these special variables in a `.pyi` file have identical semantics in a stub as at runtime. E.g. write `__all__ = ["foo", "bar"]` instead of `__all__: list[str]`. | Correctness 52 | | Y036 | Y036 detects common errors in `__exit__` and `__aexit__` methods. For example, the first argument in an `__exit__` method should either be annotated with `object`, `_typeshed.Unused` (a special alias for `object`) or `type[BaseException] \| None`. | Correctness 53 | | Y037 | Use PEP 604 syntax instead of `typing(_extensions).Union` and `typing(_extensions).Optional`. E.g. use `str \| int` instead of `Union[str, int]`, and use `str \| None` instead of `Optional[str]`. | Style 54 | | Y038 | Use `from collections.abc import Set as AbstractSet` instead of `from typing import AbstractSet` or `from typing_extensions import AbstractSet`. | Style 55 | | Y039 | Use `str` instead of `typing.Text` or `typing_extensions.Text`. | Style 56 | | Y040 | Never explicitly inherit from `object`, as all classes implicitly inherit from `object` in Python 3. | Style 57 | | Y041 | Y041 detects redundant numeric unions in the context of parameter annotations. For example, PEP 484 specifies that type checkers should allow `int` objects to be passed to a function, even if the function states that it accepts a `float`. As such, `int` is redundant in the union `int \| float` in the context of a parameter annotation. In the same way, `int` is sometimes redundant in the union `int \| complex`, and `float` is sometimes redundant in the union `float \| complex`. | Style 58 | | Y042 | Type alias names should use CamelCase rather than snake_case. | Style 59 | | Y043 | Do not use names ending in "T" for private type aliases. (The "T" suffix implies that an object is a `TypeVar`.) | Style 60 | | Y044 | `from __future__ import annotations` has no effect in stub files, since type checkers automatically treat stubs as having those semantics. (See also: Y020.) | Understanding stubs 61 | | Y045 | `__iter__` methods should never return `Iterable[T]`, as they should always return some kind of iterator. | Correctness 62 | | Y046 | A private `Protocol` should be used at least once in the file in which it is defined. | Redundant code 63 | | Y047 | A private `TypeAlias` should be used at least once in the file in which it is defined. | Redundant code 64 | | Y048 | Function bodies should contain exactly one statement. This is because stub files are never executed at runtime, so any more than one statement would be redundant. (Note that if a function body includes a docstring, the docstring counts as a "statement".) | Understanding stubs 65 | | Y049 | A private `TypedDict` should be used at least once in the file in which it is defined. | Redundant code 66 | | Y050 | Prefer `typing_extensions.Never` over `typing.NoReturn` for argument annotations. | Style 67 | | Y051 | Y051 detects redundant unions between `Literal` types and builtin supertypes. For example, `Literal[5]` is redundant in the union `int \| Literal[5]`, and `Literal[True]` is redundant in the union `Literal[True] \| bool`. | Redundant code 68 | | Y052 | Y052 disallows assignments to constant values where the assignment does not have a type annotation. For example, `x = 0` in the global namespace is ambiguous in a stub, as there are four different types that could be inferred for the variable `x`: `int`, `Final[int]`, `Literal[0]`, or `Final[Literal[0]]`. Enum members are excluded from this check, as are various special assignments such as `__all__` and `__match_args__`. | Correctness 69 | | Y053 | Only string and bytes literals <=50 characters long are permitted. (There are some exceptions, such as `Literal` subscripts, metadata strings inside `Annotated` subscripts, and strings passed to `@deprecated`.) | Style 70 | | Y054 | Only numeric literals with a string representation <=10 characters long are permitted. | Style 71 | | Y055 | Unions of the form `type[X] \| type[Y]` can be simplified to `type[X \| Y]`. Similarly, `Union[type[X], type[Y]]` can be simplified to `type[Union[X, Y]]`. | Style 72 | | Y056 | Do not call methods such as `.append()`, `.extend()` or `.remove()` on `__all__`. Different type checkers have varying levels of support for calling these methods on `__all__`. Use `+=` instead, which is known to be supported by all major type checkers. | Correctness 73 | | Y057 | Do not use `typing.ByteString` or `collections.abc.ByteString`. These types have unclear semantics, and are deprecated; use `typing_extensions.Buffer` or a union such as `bytes \| bytearray \| memoryview` instead. See [PEP 688](https://peps.python.org/pep-0688/) for more details. | Correctness 74 | | Y058 | Use `Iterator` rather than `Generator` as the return value for simple `__iter__` methods, and `AsyncIterator` rather than `AsyncGenerator` as the return value for simple `__aiter__` methods. Using `(Async)Iterator` for these methods is simpler and more elegant, and reflects the fact that the precise kind of iterator returned from an `__iter__` method is usually an implementation detail that could change at any time, and should not be relied upon. | Style 75 | | Y059 | `Generic[]` should always be the last base class, if it is present in a class's bases tuple. At runtime, if `Generic[]` is not the final class in a the bases tuple, this [can cause the class creation to fail](https://github.com/python/cpython/issues/106102). In a stub file, however, this rule is enforced purely for stylistic consistency. | Style 76 | | Y060 | Redundant inheritance from `Generic[]`. For example, `class Foo(Iterable[_T], Generic[_T]): ...` can be written more simply as `class Foo(Iterable[_T]): ...`.

To avoid false-positive errors, and to avoid complexity in the implementation, this check is deliberately conservative: it only flags classes where all subscripted bases have identical code inside their subscript slices. | Style 77 | | Y061 | Do not use `None` inside a `Literal[]` slice. For example, use `Literal["foo"] \| None` instead of `Literal["foo", None]`. While both are legal according to [PEP 586](https://peps.python.org/pep-0586/), the former is preferred for stylistic consistency. Note that this warning is not emitted if Y062 is emitted for the same `Literal[]` slice. For example, `Literal[None, None, True, True]` only causes Y062 to be emitted. | Style 78 | | Y062 | `Literal[]` slices shouldn't contain duplicates, e.g. `Literal[True, True]` is not allowed. | Redundant code 79 | | Y063 | Use [PEP 570 syntax](https://peps.python.org/pep-0570/) (e.g. `def foo(x: int, /) -> None: ...`) to denote positional-only arguments, rather than [the older Python 3.7-compatible syntax described in PEP 484](https://peps.python.org/pep-0484/#positional-only-arguments) (`def foo(__x: int) -> None: ...`, etc.). | Style 80 | | Y064 | Use simpler syntax to define final literal types. For example, use `x: Final = 42` instead of `x: Final[Literal[42]]`. | Style 81 | | Y065 | Don't use bare `Incomplete` in argument and return annotations. Instead, leave them unannotated. Omitting an annotation entirely from a function will cause some type checkers to view the parameter or return type as "untyped"; this may result in stricter type-checking on code that makes use of the stubbed function. | Style 82 | | Y066 | When using if/else with `sys.version_info`, put the code for new Python versions first. | Style 83 | | Y067 | Don't use `Incomplete \| None = None` in argument annotations. Instead, just use `=None`. | Style 84 | | Y068 | Don't use `@override` in stub files. Problems with a function signature deviating from its superclass are inherited from the implementation, and other tools such as stubtest are better placed to recognize deviations between stubs and the implementation. | Understanding stubs 85 | 86 | ## Warnings disabled by default 87 | 88 | The following error codes are also provided, but are disabled by default due to 89 | the risk of false-positive errors. To enable these error codes, use 90 | `--extend-select={code1,code2,...}` on the command line or in your flake8 91 | configuration file. 92 | 93 | Note that `--extend-select` **will not work** if you have 94 | `--select` specified on the command line or in your configuration file. We 95 | recommend only using `--extend-select`, never `--select`. 96 | 97 | | Code | Description | Code category 98 | |------|-------------|--------------- 99 | | Y090 | `tuple[int]` means "a tuple of length 1, in which the sole element is of type `int`". Consider using `tuple[int, ...]` instead, which means "a tuple of arbitrary (possibly 0) length, in which all elements are of type `int`". | Correctness 100 | | Y091 | Protocol methods should not have positional-or-keyword parameters. Usually, a positional-only parameter is better. 101 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | flake8-pyi uses Calendar Versioning (CalVer). 4 | 5 | ## Unreleased 6 | 7 | ### Breaking Changes 8 | 9 | * Previously, flake8-pyi monkey patched flake8's F821 (undefined name) check to 10 | avoid false positives in stub files. This monkey patch has been removed. 11 | Instead, we now recommend to disable F821 when running flake8 on stub files. 12 | * Remove the now unnecessary `--no-pyi-aware-file-checker` option. 13 | 14 | ### New Error Codes 15 | 16 | * Y068: Don't use `@override` in stub files 17 | 18 | ## 25.5.0 19 | 20 | ### New Error Codes 21 | 22 | * Y067: Don't use `Incomplete | None = None` 23 | * Y091: Protocol method parameters should not be positional-or-keyword 24 | 25 | ### Changed Error Codes 26 | 27 | * Y011, Y015: These checks will now allow all defaults that include an attribute access, 28 | for example `math.inf` or enum members. 29 | 30 | ### Other Changes 31 | 32 | * Development-only dependencies are now declared using 33 | [dependency groups](https://packaging.python.org/en/latest/specifications/dependency-groups/) 34 | rather than 35 | [optional dependencies](https://packaging.python.org/en/latest/guides/writing-pyproject-toml/#dependencies-and-requirements). 36 | * The plugin now exists as a `flake8_pyi` package rather than a single `pyi.py` file. 37 | * Declare support for Python 3.14 38 | 39 | ## 24.9.0 40 | 41 | ### Bugfixes 42 | 43 | * Don't emit Y053 for long strings inside `Literal` slices or 44 | metadata strings inside `Annotated` slices. 45 | 46 | ### Other Changes 47 | 48 | * `flake8-pyi` no longer supports being run using Python 3.8. 49 | As a result, it not longer depends on the third-party 50 | `ast_decompiler` package. 51 | 52 | ## 24.6.0 53 | 54 | ### Bugfixes 55 | 56 | * Allow the use of `typing_extensions.TypeVar` in stubs. 57 | `typing_extensions.TypeVar` has the *default* parameter, 58 | which only exists on Python 3.13+ when using `typing.TypeVar`. 59 | * Reduce false positives from Y052 in relation to enum subclasses. 60 | 61 | ### Other Changes 62 | 63 | * Declare support for Python 3.13 64 | 65 | ## 24.4.1 66 | 67 | ### New Error Codes 68 | 69 | * Y066: When using if/else with `sys.version_info`, 70 | put the code for new Python versions first 71 | 72 | ## 24.4.0 73 | 74 | ### Bugfixes 75 | 76 | * Y026: Fix false positive: allow simple assignment to `None` in class scopes 77 | if the class is known to be an enum class. 78 | 79 | ## 24.3.1 80 | 81 | ### New Error Codes 82 | 83 | * Y064: Use simpler syntax to define final literal types. 84 | For example, use `x: Final = 42` instead of `x: Final[Literal[42]]`. 85 | * Y065: Don't use bare `Incomplete` in parameter and return annotations. 86 | 87 | ### Bugfixes 88 | 89 | * Y090: Fix false positive for `tuple[Unpack[Ts]]`. 90 | 91 | ## 24.3.0 92 | 93 | ### New Error Codes 94 | 95 | * Y063: Use [PEP 570 syntax](https://peps.python.org/pep-0570/) to mark 96 | positional-only arguments, rather than 97 | [the older Python 3.7-compatible syntax](https://peps.python.org/pep-0484/#positional-only-arguments) 98 | described in PEP 484. 99 | 100 | ## 24.1.0 101 | 102 | ### New Error Codes 103 | 104 | * Y062: Disallow duplicate elements inside `Literal[]` slices 105 | 106 | ### Changed Error Codes 107 | 108 | * Y023: This check now bans more imports from `typing_extensions` now that typeshed has 109 | dropped support for Python 3.7. 110 | * Y060: Improve error message. 111 | * Y061: This is no longer emitted in situations where Y062 would also be emitted. 112 | 113 | ### Bugfixes 114 | 115 | * Y016: Fix false positive if a method had positional-only parameters (using 116 | [PEP 570 syntax](https://peps.python.org/pep-0570/) and the first 117 | positional-or-keyword parameter following the positional-only parameters used 118 | a custom TypeVar (see #455). 119 | * Y046: Fix false negative where an unused protocol would not be detected if 120 | the protocol was generic. 121 | 122 | ### Other Changes 123 | 124 | * Support flake8>=7.0.0. 125 | 126 | ## 23.11.0 127 | 128 | ### Breaking Changes 129 | 130 | * The undocumented `pyi.__version__` and `pyi.PyiTreeChecker.version` 131 | attributes has been removed. Use `flake8 --version` from the command line, or 132 | `importlib.metadata.version("flake8_pyi")` at runtime, to determine the 133 | version of `flake8-pyi` installed at runtime. 134 | 135 | ### New Error Codes 136 | 137 | * Y058: Use `Iterator` rather than `Generator` as the return value 138 | for simple `__iter__` methods, and `AsyncIterator` rather than 139 | `AsyncGenerator` as the return value for simple `__aiter__` methods 140 | * Y059: `Generic[]` should always be the last base class, if it is 141 | present in the bases of a class. 142 | * Y060: Redundant inheritance from `Generic[]` 143 | * Y061: Do not use `None` inside a `Literal[]` slice. 144 | For example, use `Literal["foo"] | None` instead of `Literal["foo", None]` 145 | 146 | ### Changed Error Codes 147 | 148 | * Y038: This check now flags `from typing_extensions import AbstractSet` as well as 149 | `from typing import AbstractSet`. 150 | * Y022, Y037: These checks now flag more imports from `typing_extensions`. 151 | * Y034: This check now attempts to avoid flagging methods inside classes that inherit from 152 | `builtins.type`, `abc.ABCMeta` and/or `enum.EnumMeta`. Classes that have one 153 | or more of these as bases are metaclasses, and PEP 673 154 | [forbids the use of `typing(_extensions).Self`](https://peps.python.org/pep-0673/#valid-locations-for-self) 155 | for metaclasses. While reliably determining whether a class is a metaclass in 156 | all cases would be impossible for flake8-pyi, the new heuristics should 157 | reduce the number of false positives from this check. 158 | * Y053: This will no longer be emitted for the argument to `@typing_extensions.deprecated`. 159 | * Attempting to import `typing_extensions.Text` now causes Y039 to be emitted 160 | rather than Y023. 161 | 162 | ## 23.10.0 163 | 164 | ### New Error Codes 165 | 166 | * Y090: This check warns if you have an annotation such as `tuple[int]` or 167 | `Tuple[int]`. These mean "a tuple of length 1, in which the sole element is 168 | of type `int`". This is sometimes what you want, but more usually you'll want 169 | `tuple[int, ...]`, which means "a tuple of arbitrary (possibly 0) length, in 170 | which all elements are of type `int`". 171 | 172 | This error code is disabled by default due to the risk of false-positive 173 | errors. To enable it, use the `--extend-select=Y090` option. 174 | 175 | ### Changed Error Codes 176 | 177 | * Y011: This check now ignores `sentinel` and `_typeshed.sentinel` in default values. 178 | 179 | ## 23.6.0 180 | 181 | ### New Error Codes 182 | 183 | * Y057: Do not use `typing.ByteString` or `collections.abc.ByteString`. These 184 | types have unclear semantics, and are deprecated; use `typing_extensions.Buffer` or 185 | a union such as `bytes | bytearray | memoryview` instead. See 186 | [PEP 688](https://peps.python.org/pep-0688/) for more details. 187 | 188 | ### Bugfixes 189 | 190 | * Y018, Y046, Y047, Y049: These checks previously failed to detect unused 191 | TypeVars/ParamSpecs/TypeAliases/TypedDicts/Protocols if the object in question had 192 | multiple definitions in the same file (e.g. across two branches of an `if 193 | sys.version_info >= (3, 10)` check). This bug has now been fixed. 194 | * Y019: Correctly emit errors for PEP-695 methods that are generic around a `TypeVar` 195 | instead of returning `typing_extensions.Self`. 196 | * Y020: This was previously not emitted if quoted annotations were used in TypeVar 197 | constraints. This bug has now been fixed. 198 | * Support [PEP 695](https://peps.python.org/pep-0695/) syntax for declaring 199 | type aliases. 200 | * Support Python 3.12. 201 | 202 | ### Other Changes 203 | 204 | * flake8-pyi no longer supports being run on Python 3.7, which has reached its end of life. 205 | * flake8-pyi no longer supports being run with flake8 50 characters. 275 | Previously this rule only applied to parameter default values; 276 | it now applies everywhere. 277 | * Y054: Disallow numeric literals with a string representation >10 characters long. 278 | Previously this rule only applied to parameter default values; 279 | it now applies everywhere. 280 | 281 | ### Changed Error Codes 282 | 283 | * Y011, Y014, Y015: Simple container literals (`list`, `dict`, `tuple` and `set` 284 | literals) are now allowed as default values. 285 | * Y052: This is now emitted more consistently. 286 | * Some things that used to result in Y011, Y014 or Y015 being emitted 287 | now result in Y053 or Y054 being emitted. 288 | 289 | ## 23.3.0 290 | 291 | 292 | ### Changed Error Codes 293 | 294 | * Y011, Y014, Y015: Allow `math` constants `math.inf`, `math.nan`, `math.e`, 295 | `math.pi`, `math.tau`, and their negatives in default values. Some other 296 | semantically equivalent values, such as `x = inf` (`from math import inf`), 297 | or `x = np.inf` (`import numpy as np`), should be rewritten to `x = math.inf`. 298 | (Contributed by [XuehaiPan](https://github.com/XuehaiPan).) 299 | 300 | ## 23.1.2 301 | 302 | ### Changed Error Codes 303 | 304 | * Y011, Y014, Y015: Increase the maximum character length of literal numbers 305 | in default values from 7 to 10, allowing hexadecimal representation of 306 | 32-bit integers. (Contributed by [Avasam](https://github.com/Avasam).) 307 | 308 | ## 23.1.1 309 | 310 | ### New Error Codes 311 | 312 | * Y052: Disallow default values in global or class namespaces where the 313 | assignment does not have a type annotation. Stubs should be explicit about 314 | the type of all variables in the stub; without type annotations, the type 315 | checker is forced to make inferences, which may have unpredictable 316 | consequences. Enum members are excluded from this check, as are various 317 | special assignments such as `__all__` and `__match_args__`. 318 | 319 | ### Changed Error Codes 320 | 321 | * Y020: Fewer false positives are now emitted when encountering default values 322 | in stub files. 323 | 324 | ### Other Changes 325 | 326 | * Disallow numeric default values where `len(str(default)) > 7`. If a function 327 | has a default value where the string representation is greater than 7 328 | characters, it is likely to be an implementation detail or a constant that 329 | varies depending on the system you're running on, such as `sys.maxsize`. 330 | * Disallow `str` or `bytes` defaults where the default is >50 characters long, 331 | for similar reasons. 332 | * Allow `ast.Attribute` nodes as default values for a small number of special 333 | cases, such as `sys.maxsize` and `sys.executable`. 334 | 335 | ## 23.1.0 336 | 337 | ### Removed Error Codes 338 | 339 | * Y027: Prefer stdlib classes over `typing` aliases 340 | 341 | ### Changed Error Codes 342 | 343 | * Y011, Y014, Y015: These checks have been significantly relaxed. `None`, `bool`s, 344 | `int`s, `float`s, `complex` numbers, strings and `bytes` are all now allowed 345 | as default values for parameter annotations or assignments. 346 | * Y020: Do not emit Y020 (quoted annotations) for strings in parameter defaults. 347 | * Y022: All errors that used to result in Y027 being emitted now result in Y022 348 | being emitted instead. 349 | * Y022: Some errors that used to result in Y023 being emitted now result 350 | in Y022 being emitted instead. 351 | * Y022: `typing.Match` and `typing.Pattern` have been added to the list of banned imports 352 | Use `re.Match` and `re.Pattern` instead. 353 | * Y036: Modify the check so that `_typeshed.Unused` is allowed as an annotation for 354 | parameters in `__(a)exit__` methods. Contributed by 355 | [Avasam](https://github.com/Avasam) 356 | 357 | ### Bugfixes 358 | 359 | * Fix checking of defaults for functions with positional-only parameters. 360 | 361 | ### Other Changes 362 | 363 | * flake8-pyi no longer supports stub files that aim to support Python 2. If your 364 | stubs need to support Python 2, pin flake8-pyi to 22.11.0 or lower. 365 | * Hatchling is now used as the build backend. This should have minimal, if any, 366 | user-facing impact. 367 | 368 | ## 22.11.0 369 | 370 | ### Changed Error Codes 371 | 372 | * Y041: Significant changes have been made to the check. Previously, Y041 flagged 373 | "redundant numeric unions" (e.g. `float | int`, `complex | float` or `complex | int`) 374 | in all contexts outside of type aliases. This was incorrect. PEP 484 only 375 | specifies that type checkers should treat `int` as an implicit subtype of 376 | `float` in the specific context of parameter annotations for functions and 377 | methods. Y041 has therefore been revised to only emit errors on "redundant 378 | numeric unions" in the context of parameter annotations. 379 | 380 | ### Bugfixes 381 | 382 | * Specify encoding when opening files. Prevents `UnicodeDecodeError` on Windows 383 | when the file contains non-CP1252 characters. 384 | Contributed by [Avasam](https://github.com/Avasam). 385 | 386 | ### Other Changes 387 | 388 | * Support running with flake8 v6. 389 | 390 | ## 22.10.0 391 | 392 | ### Bugfixes 393 | 394 | * Do not emit Y020 for empty strings. Y020 concerns "quoted annotations", 395 | but an empty string can never be a quoted annotation. 396 | * Add special-casing so that Y020 is not emitted for `__slots__` definitions 397 | inside `class` blocks. 398 | * Expand Y035 to cover `__slots__` definitions as well as `__match_args__` and 399 | `__all__` definitions. 400 | * Expand Y015 so that errors are emitted for assignments to negative numbers. 401 | 402 | ### Other Changes 403 | 404 | * Since v22.8.1, flake8-pyi has emitted a `FutureWarning` if run with flake8<5, 405 | warning that the plugin would soon become incompatible with flake8<5. Due to 406 | some issues that mean that some users are unable to upgrade to flake8>=5, 407 | however, flake8-pyi no longer intends to remove support for running the 408 | plugin with flake8<5 before Python 3.7 has reached end-of-life. As such, the 409 | `FutureWarning` is no longer emitted. 410 | 411 | ## 22.8.2 412 | 413 | ### New Error Codes 414 | 415 | * Y047: Detect unused `TypeAlias` declarations. 416 | * Y049: Detect unused `TypedDict` definitions. 417 | * Y050: Prefer `typing_extensions.Never` for argument annotations over 418 | `typing.NoReturn`. 419 | * Y051: Detect redundant unions between `Literal` types and builtin supertypes 420 | (e.g. `Literal["foo"] | str`, or `Literal[5] | int`). 421 | 422 | ### Other Changes 423 | 424 | * Support `mypy_extensions.TypedDict`. 425 | 426 | ## 22.8.1 427 | 428 | * Add support for flake8 >= 5.0.0. 429 | 430 | ## 22.8.0 431 | 432 | ### New Error Codes 433 | 434 | * Y046: Detect unused `Protocol`s. 435 | * Y048: Function bodies should contain exactly one statement. 436 | 437 | ### Bugfixes 438 | 439 | * Improve error message for the case where a function body contains a docstring 440 | and a `...` or `pass` statement. 441 | 442 | ### Other Changes 443 | 444 | * Pin required flake8 version to <5.0.0 (flake8-pyi is not currently compatible with flake8 5.0.0). 445 | 446 | ## 22.7.0 447 | 448 | ### New Error Codes 449 | 450 | * Y041: Ban redundant numeric unions (`int | float`, `int | complex`, 451 | `float | complex`) 452 | * Y042: Type alias names should use CamelCase rather than snake_case 453 | * Y043: Ban type aliases from having names ending with an uppercase "T" 454 | * Y044: Discourage unnecessary `from __future__ import annotations` import 455 | (Contributed by Torsten Wörtwein) 456 | * Y045: Ban returning `(Async)Iterable` from `__(a)iter__` methods 457 | 458 | ### Changed Error Codes 459 | 460 | * Y026: Improve error message for check. 461 | * Y026: Expand check. Since version 22.4.0, this has only emitted an error for 462 | assignments to `typing.Literal`, `typing.Union`, and PEP 604 unions. It now also 463 | emits an error for any subscription on the right-hand side of a simple assignment, as 464 | well as for assignments to `typing.Any` and `None`. 465 | * Y034: Slightly expand check to cover the case where a class inheriting from `(Async)Iterator` 466 | returns `(Async)Iterable` from `__(a)iter__`. These classes should nearly always return 467 | `Self` from these methods. 468 | 469 | ### Other Changes 470 | 471 | * Support `typing_extensions.overload` and `typing_extensions.NamedTuple`. 472 | * Support Python 3.11. 473 | 474 | ## 22.5.1 475 | 476 | ### Changed Error Codes 477 | 478 | * Y020: Relax check slightly, enabling the idiom `__all__ += ["foo", "bar"]` to be used 479 | in a stub file. 480 | 481 | ## 22.5.0 482 | 483 | ### New Error Codes 484 | 485 | * Y039: Use `str` instead of `typing.Text` for Python 3 stubs 486 | * Y040: Never explicitly inherit from `object` in Python 3 stubs 487 | 488 | ### Changed Error Codes 489 | 490 | * Y029: Teach the check to emit errors for `__repr__` and `__str__` methods that return 491 | `builtins.str` (as opposed to the unqualified `str`). 492 | * Y036: Teach the check that `builtins.object` (as well as the unqualified `object`) is 493 | acceptable as an annotation for an `__(a)exit__` method argument. 494 | 495 | ## 22.4.1 496 | 497 | ### New Error Codes 498 | 499 | * Y038: Use `from collections.abc import Set as AbstractSet` instead of 500 | `from typing import AbstractSet` 501 | 502 | ### Changed Error Codes 503 | 504 | * Y027: Expand to prohibit importing any objects from the `typing` module that are 505 | aliases for objects living `collections.abc` (except for `typing.AbstractSet`, which 506 | is special-cased). 507 | 508 | ### Bugfixes 509 | 510 | * Improve inaccurate error messages for Y036. 511 | 512 | ## 22.4.0 513 | 514 | ### New Error Codes 515 | 516 | * Y036: Check for badly defined `__exit__` and `__aexit__` methods 517 | * Y037: Use PEP 604 syntax instead of `typing.Union` and `typing.Optional` 518 | (Contributed by Oleg Höfling) 519 | 520 | ### Changed Error Codes 521 | 522 | * Y035: Expand to cover `__match_args__` inside class definitions, as well as `__all__` 523 | in the global scope. 524 | 525 | ### Bugfixes 526 | 527 | * Improve Y026 check (regarding `typing.TypeAlias`) to reduce false-positive errors 528 | emitted when the plugin encountered variable aliases in a stub file. 529 | 530 | ## 22.3.0 531 | 532 | ### Changed Error Codes 533 | 534 | * Add special-casing so that string literals are allowed in the context of 535 | `__match_args__` assignments inside a class definition. 536 | * Add special-casing so that arbitrary values can be assigned to a variable in 537 | a stub file if the variable is annotated with `Final`. 538 | 539 | ### Bugfixes 540 | 541 | * Fix bug where incorrect quoted annotations were not detected within `if` blocks. 542 | 543 | ## 22.2.0 544 | 545 | ### New Error Codes 546 | 547 | * Y032: Prefer `object` to `Any` for the second argument in `__eq__` and 548 | `__ne__` methods 549 | * Y033: Always use annotations in stubs, rather than type comments 550 | * Y034: Detect common errors where return types are hardcoded, but they 551 | should use `TypeVar`s instead 552 | * Y035: `__all__` in a stub has the same semantics as at runtime 553 | 554 | ### Bugfixes 555 | 556 | * Fix bugs in several error codes so that e.g. `_T = typing.TypeVar("_T")` is 557 | recognised as a `TypeVar` definition (previously only `_T = TypeVar("_T")` was 558 | recognised). 559 | * Fix bug where `foo = False` at the module level did not trigger a Y015 error. 560 | * Fix bug where `TypeVar`s were erroneously flagged as unused if they were only used in 561 | a `typing.Union` subscript. 562 | * Improve unclear error messages for Y022, Y023 and Y027 error codes. 563 | 564 | ## 22.1.0 565 | 566 | ### New Error Codes 567 | 568 | * Y016: Duplicate union member 569 | * Y017: Disallows assignments with multiple targets or non-name targets 570 | * Y018: Detect unused `TypeVar`s 571 | * Y019: Detect `TypeVar`s that should be `_typeshed.Self`, but aren't 572 | * Y020: Never use quoted annotations in stubs 573 | * Y021: Docstrings should not be included in stubs 574 | * Y022: Prefer stdlib classes over `typing` aliases 575 | * Y023: Prefer `typing` over `typing_extensions` 576 | * Y024: Prefer `typing.NamedTuple` to `collections.namedtuple` 577 | * Y026: Require using `TypeAlias` for type aliases 578 | * Y025: Always alias `collections.abc.Set` 579 | * Y027: Python 2-incompatible extension of Y022 580 | * Y028: Use class-based syntax for `NamedTuple`s 581 | * Y029: Never define `__repr__` or `__str__` 582 | * Y030: Use `Literal['foo', 'bar']` instead of `Literal['foo'] | Literal['bar']` 583 | * Y031: Use class-based syntax for `TypedDict`s where possible 584 | 585 | ### Removed Error Codes 586 | 587 | * Y092: Top-level attribute must not have a default value 588 | 589 | ### Changed Error Codes 590 | 591 | * Y001: Extend to cover `ParamSpec` and `TypeVarTuple` in addition to `TypeVar`. 592 | * Y010: Extend to check async functions in addition to normal functions. 593 | * Y010: Extend to cover what was previously included in Y090 (disallow 594 | assignments in `__init__` methods) and Y091 (disallow `raise` 595 | statements). The previous checks were disabled by default. 596 | * Detect usage of non-integer indices in `sys.version_info` checks. 597 | 598 | ### Other Changes 599 | 600 | * All errors are now enabled by default. 601 | * `attrs` is no longer a dependency. 602 | * `ast_decompiler` has been added as a dependency on Python 3.8 and 3.7. 603 | * Support Python 3.10. 604 | * Discontinue support for Python 3.6. 605 | 606 | ## 20.10.0 607 | 608 | ### Other Changes 609 | 610 | * Support Python 3.9. 611 | 612 | ## 20.5.0 613 | 614 | ### New Error Codes 615 | 616 | * Y015: Attribute must not have a default value other than `...` 617 | * Y091: Function body must not contain `raise` 618 | * Y092: Top-level attribute must not have a default value 619 | 620 | ### Other Changes 621 | 622 | * Support flake8 3.8.0. 623 | 624 | ## 19.3.0 625 | 626 | ### Other Changes 627 | 628 | * Update pyflakes dependency. 629 | 630 | ## 19.2.0 631 | 632 | ### New Error Codes 633 | 634 | * Y012: Class body must not contain `pass` 635 | * Y013: Non-empty class body must not contain `...` 636 | * Y014: Only simple default values are allowed for any function arguments 637 | 638 | ### Other Changes 639 | 640 | * Support Python 3.7. 641 | * Use `--stdin-display-name` as the filename when reading from stdin. 642 | 643 | ## 18.3.1 644 | 645 | ### New Error Codes 646 | 647 | * Y011: Allow only simple default values 648 | 649 | ## 18.3.0 650 | 651 | Release herp derp, don't use. 652 | 653 | ## 17.3.0 654 | 655 | ### New Error Codes 656 | 657 | * Y001: Y010 658 | * Y090 (disabled by default) 659 | 660 | ## 17.1.0 661 | 662 | ### Other Changes 663 | 664 | * Handle `del` statements in stub files. 665 | 666 | ## 16.12.2 667 | 668 | ### Other Changes 669 | 670 | * Handle annotated assignments in 3.6+ with forward reference support. 671 | 672 | ## 16.12.1 673 | 674 | ### Other Changes 675 | 676 | * Handle forward references during subclassing on module level. 677 | * Handle forward references during type aliasing assignments on module level. 678 | 679 | ## 16.12.0 680 | 681 | First published version 682 | --------------------------------------------------------------------------------