├── 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 |
--------------------------------------------------------------------------------