Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-flake8-pyi for openSUSE:Factory checked in at 2023-12-06 23:48:10 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-flake8-pyi (Old) and /work/SRC/openSUSE:Factory/.python-flake8-pyi.new.25432 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-flake8-pyi" Wed Dec 6 23:48:10 2023 rev:11 rq:1131208 version:23.11.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-flake8-pyi/python-flake8-pyi.changes 2023-08-28 17:14:05.138216989 +0200 +++ /work/SRC/openSUSE:Factory/.python-flake8-pyi.new.25432/python-flake8-pyi.changes 2023-12-06 23:48:41.936300707 +0100 @@ -1,0 +2,33 @@ +Tue Dec 5 21:07:25 UTC 2023 - Dirk Müller <dmuel...@suse.com> + +- update to 23.11.0: + * 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. + * Y059: `Generic[]` should always be the last base class, if it + is present in the bases of a class. + * Y060, which flags redundant inheritance from `Generic[]`. + * Y061: Do not use `None` inside a `Literal[]` slice. + * For example, use `Literal["foo"] | None` instead of + `Literal["foo", None]`. + * Y022 and Y037 now flag more imports from `typing_extensions`. + * Y034 now attempts to avoid flagging methods inside classes + that inherit from `builtins.type`, `abc.ABCMeta` and/or + `enum.EnumMeta`. Classes that have one or more of these as + bases are metaclasses, and PEP 673 forbids the use of + `typing(_extensions).Self` for metaclasses. + * Attempting to import `typing_extensions.Text` now causes Y039 + to be emitted rather than Y023. + * Y053 will no longer be emitted for the argument to + `@typing_extensions.deprecated`. + * Introduce Y090, which warns if you have an annotation such as + `tuple[int]` or `Tuple[int]`. These mean "a tuple of length 1, + in which the sole element is of type `int`". This is sometimes + what you want, but more usually you'll want `tuple[int, ...]`, + which means "a tuple of arbitrary (possibly 0) length, in + which all elements are of type `int`". + * Y011 now ignores `sentinel` and `_typeshed.sentinel` in + default values. + +------------------------------------------------------------------- Old: ---- flake8_pyi-23.6.0.tar.gz New: ---- flake8_pyi-23.11.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-flake8-pyi.spec ++++++ --- /var/tmp/diff_new_pack.ghVi0W/_old 2023-12-06 23:48:42.900335548 +0100 +++ /var/tmp/diff_new_pack.ghVi0W/_new 2023-12-06 23:48:42.904335692 +0100 @@ -16,9 +16,9 @@ # -%define skip_python2 1 +%{?sle15_python_module_pythons} Name: python-flake8-pyi -Version: 23.6.0 +Version: 23.11.0 Release: 0 Summary: A plugin for flake8 to enable linting .pyi files License: MIT @@ -30,6 +30,7 @@ Patch0: set-tests-python-path.patch BuildRequires: %{python_module ast-decompiler} BuildRequires: %{python_module base >= 3.8.0} +BuildRequires: %{python_module hatch_vcs} BuildRequires: %{python_module hatchling} BuildRequires: %{python_module pip} BuildRequires: %{python_module wheel} @@ -42,6 +43,7 @@ BuildRequires: %{python_module pytest} BuildRequires: %{python_module black} BuildRequires: %{python_module flake8-bugbear} +BuildRequires: %{python_module pytest-xdist} BuildRequires: %{python_module typing} # /SECTION BuildRequires: fdupes ++++++ flake8_pyi-23.6.0.tar.gz -> flake8_pyi-23.11.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/flake8_pyi-23.6.0/.flake8 new/flake8_pyi-23.11.0/.flake8 --- old/flake8_pyi-23.6.0/.flake8 2020-02-02 01:00:00.000000000 +0100 +++ new/flake8_pyi-23.11.0/.flake8 2020-02-02 01:00:00.000000000 +0100 @@ -24,10 +24,10 @@ # produces false positives if you're surrounding things with double quotes [flake8] +extend-select = B9 max-line-length = 80 max-complexity = 12 noqa-require-code = true -select = B,C,E,F,W,Y,B9,NQA per-file-ignores = *.py: B905, B907, B950, E203, E501, W503, W291, W293 *.pyi: B, E301, E302, E305, E501, E701, E704, W503 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/flake8_pyi-23.6.0/.github/ISSUE_TEMPLATE/bug.md new/flake8_pyi-23.11.0/.github/ISSUE_TEMPLATE/bug.md --- old/flake8_pyi-23.6.0/.github/ISSUE_TEMPLATE/bug.md 1970-01-01 01:00:00.000000000 +0100 +++ new/flake8_pyi-23.11.0/.github/ISSUE_TEMPLATE/bug.md 2020-02-02 01:00:00.000000000 +0100 @@ -0,0 +1,5 @@ +--- +name: Bug report +about: Report a bug +labels: type-bug +--- diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/flake8_pyi-23.6.0/.github/ISSUE_TEMPLATE/docs.md new/flake8_pyi-23.11.0/.github/ISSUE_TEMPLATE/docs.md --- old/flake8_pyi-23.6.0/.github/ISSUE_TEMPLATE/docs.md 1970-01-01 01:00:00.000000000 +0100 +++ new/flake8_pyi-23.11.0/.github/ISSUE_TEMPLATE/docs.md 2020-02-02 01:00:00.000000000 +0100 @@ -0,0 +1,5 @@ +--- +name: Documentation +about: Report an issue with our docs +labels: type-documentation +--- diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/flake8_pyi-23.6.0/.github/ISSUE_TEMPLATE/feature.md new/flake8_pyi-23.11.0/.github/ISSUE_TEMPLATE/feature.md --- old/flake8_pyi-23.6.0/.github/ISSUE_TEMPLATE/feature.md 1970-01-01 01:00:00.000000000 +0100 +++ new/flake8_pyi-23.11.0/.github/ISSUE_TEMPLATE/feature.md 2020-02-02 01:00:00.000000000 +0100 @@ -0,0 +1,5 @@ +--- +name: Feature request +about: Suggest a new feature +labels: type-feature +--- diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/flake8_pyi-23.6.0/.github/workflows/check.yml new/flake8_pyi-23.11.0/.github/workflows/check.yml --- old/flake8_pyi-23.6.0/.github/workflows/check.yml 2020-02-02 01:00:00.000000000 +0100 +++ new/flake8_pyi-23.11.0/.github/workflows/check.yml 2020-02-02 01:00:00.000000000 +0100 @@ -29,15 +29,16 @@ timeout-minutes: 5 strategy: matrix: - python-version: ["3.8", "3.11"] + python-version: ["3.8", "3.12"] fail-fast: false steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} cache: pip cache-dependency-path: pyproject.toml + allow-prereleases: true - run: pip install -e .[dev] - run: mypy @@ -46,7 +47,7 @@ runs-on: ubuntu-latest timeout-minutes: 5 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-python@v4 with: python-version: "3.11" @@ -66,7 +67,7 @@ python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] fail-fast: false steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/flake8_pyi-23.6.0/.github/workflows/publish.yml new/flake8_pyi-23.11.0/.github/workflows/publish.yml --- old/flake8_pyi-23.6.0/.github/workflows/publish.yml 2020-02-02 01:00:00.000000000 +0100 +++ new/flake8_pyi-23.11.0/.github/workflows/publish.yml 2020-02-02 01:00:00.000000000 +0100 @@ -12,8 +12,11 @@ build-n-publish: name: Build and publish Python distributions to PyPI runs-on: ubuntu-20.04 + permissions: + # needed for PyPI trusted publishing + id-token: write steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python 3.10 uses: actions/setup-python@v4 with: @@ -35,5 +38,3 @@ - name: Publish distribution to PyPI if: startsWith(github.ref, 'refs/tags') uses: pypa/gh-action-pypi-publish@release/v1 - with: - password: ${{ secrets.PYPI_API_TOKEN }} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/flake8_pyi-23.6.0/.github/workflows/typeshed_primer.yml new/flake8_pyi-23.11.0/.github/workflows/typeshed_primer.yml --- old/flake8_pyi-23.6.0/.github/workflows/typeshed_primer.yml 2020-02-02 01:00:00.000000000 +0100 +++ new/flake8_pyi-23.11.0/.github/workflows/typeshed_primer.yml 2020-02-02 01:00:00.000000000 +0100 @@ -23,16 +23,16 @@ runs-on: ubuntu-latest steps: - name: Checkout flake8-pyi on target branch - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: ref: ${{ github.base_ref }} path: old_plugin - name: Checkout flake8-pyi on PR branch - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: path: new_plugin - name: Checkout typeshed - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: repository: python/typeshed path: typeshed diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/flake8_pyi-23.6.0/.github/workflows/typeshed_primer_comment.yml new/flake8_pyi-23.11.0/.github/workflows/typeshed_primer_comment.yml --- old/flake8_pyi-23.6.0/.github/workflows/typeshed_primer_comment.yml 2020-02-02 01:00:00.000000000 +0100 +++ new/flake8_pyi-23.11.0/.github/workflows/typeshed_primer_comment.yml 2020-02-02 01:00:00.000000000 +0100 @@ -17,7 +17,7 @@ if: ${{ github.event.workflow_run.conclusion == 'success' }} runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Download errors uses: actions/github-script@v6 with: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/flake8_pyi-23.6.0/.pre-commit-config.yaml new/flake8_pyi-23.11.0/.pre-commit-config.yaml --- old/flake8_pyi-23.6.0/.pre-commit-config.yaml 2020-02-02 01:00:00.000000000 +0100 +++ new/flake8_pyi-23.11.0/.pre-commit-config.yaml 2020-02-02 01:00:00.000000000 +0100 @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 # must match pyproject.toml + rev: v4.5.0 # must match pyproject.toml hooks: - id: trailing-whitespace - id: end-of-file-fixer @@ -8,8 +8,8 @@ - id: check-toml - id: check-merge-conflict - id: mixed-line-ending - - repo: https://github.com/psf/black - rev: 23.3.0 # must match pyproject.toml + - repo: https://github.com/psf/black-pre-commit-mirror + rev: 23.9.1 # must match pyproject.toml hooks: - id: black language_version: python3.8 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/flake8_pyi-23.6.0/CHANGELOG.md new/flake8_pyi-23.11.0/CHANGELOG.md --- old/flake8_pyi-23.6.0/CHANGELOG.md 2020-02-02 01:00:00.000000000 +0100 +++ new/flake8_pyi-23.11.0/CHANGELOG.md 2020-02-02 01:00:00.000000000 +0100 @@ -1,5 +1,48 @@ # Change Log +## 23.11.0 + +New error codes: +* 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. +* Y059: `Generic[]` should always be the last base class, if it is + present in the bases of a class. +* Y060, which flags redundant inheritance from `Generic[]`. +* Y061: Do not use `None` inside a `Literal[]` slice. + For example, use `Literal["foo"] | None` instead of `Literal["foo", None]`. + +Other changes: +* The undocumented `pyi.__version__` and `pyi.PyiTreeChecker.version` + attributes has been removed. Use `flake8 --version` from the command line, or + `importlib.metadata.version("flake8_pyi")` at runtime, to determine the + version of `flake8-pyi` installed at runtime. +* Y038 now flags `from typing_extensions import AbstractSet` as well as + `from typing import AbstractSet`. +* Y022 and Y037 now flag more imports from `typing_extensions`. +* Y034 now attempts to avoid flagging methods inside classes that inherit from + `builtins.type`, `abc.ABCMeta` and/or `enum.EnumMeta`. Classes that have one + or more of these as bases are metaclasses, and PEP 673 + [forbids the use of `typing(_extensions).Self`](https://peps.python.org/pep-0673/#valid-locations-for-self) + for metaclasses. While reliably determining whether a class is a metaclass in + all cases would be impossible for flake8-pyi, the new heuristics should + reduce the number of false positives from this check. +* Attempting to import `typing_extensions.Text` now causes Y039 to be emitted + rather than Y023. +* Y053 will no longer be emitted for the argument to `@typing_extensions.deprecated`. + +## 23.10.0 + +* Introduce Y090, which warns if you have an annotation such as `tuple[int]` or + `Tuple[int]`. These mean "a tuple of length 1, in which the sole element is + of type `int`". This is sometimes what you want, but more usually you'll want + `tuple[int, ...]`, which means "a tuple of arbitrary (possibly 0) length, in + which all elements are of type `int`". + + This error code is disabled by default due to the risk of false-positive + errors. To enable it, use the `--extend-select=Y090` option. +* Y011 now ignores `sentinel` and `_typeshed.sentinel` in default values. + ## 23.6.0 Features: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/flake8_pyi-23.6.0/CONTRIBUTING.md new/flake8_pyi-23.11.0/CONTRIBUTING.md --- old/flake8_pyi-23.6.0/CONTRIBUTING.md 2020-02-02 01:00:00.000000000 +0100 +++ new/flake8_pyi-23.11.0/CONTRIBUTING.md 2020-02-02 01:00:00.000000000 +0100 @@ -51,8 +51,7 @@ Releasing a new version is easy: -- Make a PR that updates the version header in `CHANGELOG.md` - and the `__version__` attribute in `pyi.py`. +- Make a PR that updates the version header in `CHANGELOG.md`. - Merge the PR and wait for tests to complete. - Draft a release using the GitHub UI. The tag name should be identical to the version (e.g., `22.1.0`) and the release notes diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/flake8_pyi-23.6.0/ERRORCODES.md new/flake8_pyi-23.11.0/ERRORCODES.md --- old/flake8_pyi-23.6.0/ERRORCODES.md 2020-02-02 01:00:00.000000000 +0100 +++ new/flake8_pyi-23.11.0/ERRORCODES.md 2020-02-02 01:00:00.000000000 +0100 @@ -1,6 +1,6 @@ ## List of warnings -The following warnings are currently emitted: +The following warnings are currently emitted by default: | Code | Description |------|------------- @@ -39,9 +39,9 @@ | 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:<br><br> **1.** Any in-place BinOp dunder methods (`__iadd__`, `__ior__`, etc.) that do not return `Self`.<br> **2.** `__new__`, `__enter__` and `__aenter__` methods that return the class's name unparameterised.<br> **3.** `__iter__` methods that return `Iterator`, even if the class inherits directly from `Iterator`.<br> **4.** `__aiter__` methods that return `AsyncIterator`, even if the class inherits directly from `AsyncIterator`.<br><br>This check excludes methods decorated with `@overload` or `@abstractmethod`. | 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]`. | 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`. -| Y037 | Use PEP 604 syntax instead of `typing.Union` and `typing.Optional`. E.g. use `str \| int` instead of `Union[str, int]`, and use `str \| None` instead of `Optional[str]`. -| Y038 | Use `from collections.abc import Set as AbstractSet` instead of `from typing import AbstractSet`. -| Y039 | Use `str` instead of `typing.Text`. +| 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]`. +| Y038 | Use `from collections.abc import Set as AbstractSet` instead of `from typing import AbstractSet` or `from typing_extensions import AbstractSet`. +| Y039 | Use `str` instead of `typing.Text` or `typing_extensions.Text`. | Y040 | Never explicitly inherit from `object`, as all classes implicitly inherit from `object` in Python 3. | 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`. | Y042 | Type alias names should use CamelCase rather than snake_case. @@ -60,3 +60,22 @@ | 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]]`. | 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. | 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. +| 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. +| 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. +| Y060 | Redundant inheritance from `Generic[]`. For example, `class Foo(Iterable[_T], Generic[_T]): ...` can be written more simply as `class Foo(Iterable[_T]): ...`.<br><br>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. +| 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. + +## Warnings disabled by default + +The following error codes are also provided, but are disabled by default due to +the risk of false-positive errors. To enable these error codes, use +`--extend-select={code1,code2,...}` on the command line or in your flake8 +configuration file. + +Note that `--extend-select` **will not work** if you have +`--select` specified on the command line or in your configuration file. We +recommend only using `--extend-select`, never `--select`. + +| Code | Description +|------|------------ +| 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`". diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/flake8_pyi-23.6.0/PKG-INFO new/flake8_pyi-23.11.0/PKG-INFO --- old/flake8_pyi-23.6.0/PKG-INFO 2020-02-02 01:00:00.000000000 +0100 +++ new/flake8_pyi-23.11.0/PKG-INFO 2020-02-02 01:00:00.000000000 +0100 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: flake8-pyi -Version: 23.6.0 +Version: 23.11.0 Summary: A plugin for flake8 to enable linting .pyi stub files. Project-URL: Homepage, https://github.com/PyCQA/flake8-pyi Project-URL: Source, https://github.com/PyCQA/flake8-pyi @@ -32,13 +32,14 @@ Requires-Dist: flake8<7.0.0,>=6.0.0 Requires-Dist: pyflakes>=2.1.1 Provides-Extra: dev -Requires-Dist: black==23.3.0; extra == 'dev' -Requires-Dist: flake8-bugbear==23.6.5; extra == 'dev' +Requires-Dist: black==23.9.1; extra == 'dev' +Requires-Dist: flake8-bugbear==23.9.16; extra == 'dev' Requires-Dist: flake8-noqa==1.3.2; extra == 'dev' Requires-Dist: isort==5.12.0; extra == 'dev' -Requires-Dist: mypy==1.4.1; extra == 'dev' -Requires-Dist: pre-commit-hooks==4.4.0; extra == 'dev' -Requires-Dist: pytest==7.4.0; extra == 'dev' +Requires-Dist: mypy==1.6.0; extra == 'dev' +Requires-Dist: pre-commit-hooks==4.5.0; extra == 'dev' +Requires-Dist: pytest-xdist==3.3.1; extra == 'dev' +Requires-Dist: pytest==7.4.2; extra == 'dev' Requires-Dist: types-pyflakes<4; extra == 'dev' Description-Content-Type: text/markdown diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/flake8_pyi-23.6.0/pyi.py new/flake8_pyi-23.11.0/pyi.py --- old/flake8_pyi-23.6.0/pyi.py 2020-02-02 01:00:00.000000000 +0100 +++ new/flake8_pyi-23.11.0/pyi.py 2020-02-02 01:00:00.000000000 +0100 @@ -12,7 +12,7 @@ from copy import deepcopy from dataclasses import dataclass from functools import partial -from itertools import chain, zip_longest +from itertools import chain, groupby, zip_longest from keyword import iskeyword from typing import TYPE_CHECKING, Any, ClassVar, NamedTuple, Union @@ -37,8 +37,6 @@ # and mypy thinks typing_extensions is part of the stdlib. from typing_extensions import Literal, TypeAlias, TypeGuard -__version__ = "23.6.0" - LOG = logging.getLogger("flake8.pyi") if sys.version_info >= (3, 12): @@ -64,102 +62,89 @@ name: str +def all_equal(iterable: Iterable[object]) -> bool: + """Returns True if all the elements are equal to each other. + + Adapted from the CPython itertools documentation.""" + g = groupby(iterable) + next(g, True) + return not next(g, False) + + _MAPPING_SLICE = "KeyType, ValueType" -_TYPE_SLICE = "MyClass" -_COUNTER_SLICE = "KeyType" -_COROUTINE_SLICE = "YieldType, SendType, ReturnType" -_ASYNCGEN_SLICE = "YieldType, SendType" # Y022: Use stdlib imports instead of aliases from typing/typing_extensions _BAD_Y022_IMPORTS: dict[str, tuple[str, str | None]] = { - # typing aliases for collections - "typing.Counter": ("collections.Counter", _COUNTER_SLICE), - "typing.Deque": ("collections.deque", "T"), - "typing.DefaultDict": ("collections.defaultdict", _MAPPING_SLICE), - "typing.ChainMap": ("collections.ChainMap", _MAPPING_SLICE), - "typing.OrderedDict": ("collections.OrderedDict", _MAPPING_SLICE), - # typing aliases for builtins - "typing.Dict": ("dict", _MAPPING_SLICE), - "typing.FrozenSet": ("frozenset", "T"), - "typing.List": ("list", "T"), - "typing.Set": ("set", "T"), - "typing.Tuple": ("tuple", "Foo, Bar"), - "typing.Type": ("type", _TYPE_SLICE), - # typing aliases for contextlib - "typing.ContextManager": ("contextlib.AbstractContextManager", "T"), - "typing.AsyncContextManager": ("contextlib.AbstractAsyncContextManager", "T"), - # typing aliases for re - "typing.Match": ("re.Match", "T"), - "typing.Pattern": ("re.Pattern", "T"), - # typing_extensions aliases for collections - "typing_extensions.Counter": ("collections.Counter", _COUNTER_SLICE), - "typing_extensions.Deque": ("collections.deque", "T"), - "typing_extensions.DefaultDict": ("collections.defaultdict", _MAPPING_SLICE), - "typing_extensions.ChainMap": ("collections.ChainMap", _MAPPING_SLICE), - "typing_extensions.OrderedDict": ("collections.OrderedDict", _MAPPING_SLICE), - # One typing_extensions alias for a builtin - "typing_extensions.Type": ("type", _TYPE_SLICE), - # Typing_extensions aliases for contextlib - "typing_extensions.ContextManager": ("contextlib.AbstractContextManager", "T"), - "typing_extensions.AsyncContextManager": ( - "contextlib.AbstractAsyncContextManager", - "T", - ), - # typing aliases for collections.abc - # typing.AbstractSet and typing.ByteString are deliberately omitted + # Aliases for collections + "Counter": ("collections.Counter", "KeyType"), + "Deque": ("collections.deque", "T"), + "DefaultDict": ("collections.defaultdict", _MAPPING_SLICE), + "ChainMap": ("collections.ChainMap", _MAPPING_SLICE), + "OrderedDict": ("collections.OrderedDict", _MAPPING_SLICE), + # Aliases for builtins + "Dict": ("dict", _MAPPING_SLICE), + "FrozenSet": ("frozenset", "T"), + "List": ("list", "T"), + "Set": ("set", "T"), + "Tuple": ("tuple", "Foo, Bar"), + "Type": ("type", "MyClass"), + # Aliases for contextlib + "ContextManager": ("contextlib.AbstractContextManager", "T"), + "AsyncContextManager": ("contextlib.AbstractAsyncContextManager", "T"), + # Aliases for re + "Match": ("re.Match", "T"), + "Pattern": ("re.Pattern", "T"), + # Aliases for collections.abc + # AbstractSet and ByteString are deliberately omitted # (special-cased elsewhere). # If the second element of the tuple is `None`, # it signals that the object shouldn't be parameterized - "typing.Collection": ("collections.abc.Collection", "T"), - "typing.ItemsView": ("collections.abc.ItemsView", _MAPPING_SLICE), - "typing.KeysView": ("collections.abc.KeysView", "KeyType"), - "typing.Mapping": ("collections.abc.Mapping", _MAPPING_SLICE), - "typing.MappingView": ("collections.abc.MappingView", None), - "typing.MutableMapping": ("collections.abc.MutableMapping", _MAPPING_SLICE), - "typing.MutableSequence": ("collections.abc.MutableSequence", "T"), - "typing.MutableSet": ("collections.abc.MutableSet", "T"), - "typing.Sequence": ("collections.abc.Sequence", "T"), - "typing.ValuesView": ("collections.abc.ValuesView", "ValueType"), - "typing.Iterable": ("collections.abc.Iterable", "T"), - "typing.Iterator": ("collections.abc.Iterator", "T"), - "typing.Generator": ( - "collections.abc.Generator", - "YieldType, SendType, ReturnType", - ), - "typing.Hashable": ("collections.abc.Hashable", None), - "typing.Reversible": ("collections.abc.Reversible", "T"), - "typing.Sized": ("collections.abc.Sized", None), - "typing.Coroutine": ("collections.abc.Coroutine", _COROUTINE_SLICE), - "typing.AsyncGenerator": ("collections.abc.AsyncGenerator", _ASYNCGEN_SLICE), - "typing.AsyncIterator": ("collections.abc.AsyncIterator", "T"), - "typing.AsyncIterable": ("collections.abc.AsyncIterable", "T"), - "typing.Awaitable": ("collections.abc.Awaitable", "T"), - "typing.Callable": ("collections.abc.Callable", None), - "typing.Container": ("collections.abc.Container", "T"), - # typing_extensions aliases for collections.abc - "typing_extensions.Awaitable": ("collections.abc.Awaitable", "T"), - "typing_extensions.Coroutine": ("collections.abc.Coroutine", _COROUTINE_SLICE), - "typing_extensions.AsyncIterable": ("collections.abc.AsyncIterable", "T"), - "typing_extensions.AsyncIterator": ("collections.abc.AsyncIterator", "T"), - "typing_extensions.AsyncGenerator": ( - "collections.abc.AsyncGenerator", - _ASYNCGEN_SLICE, - ), + "Collection": ("collections.abc.Collection", "T"), + "ItemsView": ("collections.abc.ItemsView", _MAPPING_SLICE), + "KeysView": ("collections.abc.KeysView", "KeyType"), + "Mapping": ("collections.abc.Mapping", _MAPPING_SLICE), + "MappingView": ("collections.abc.MappingView", None), + "MutableMapping": ("collections.abc.MutableMapping", _MAPPING_SLICE), + "MutableSequence": ("collections.abc.MutableSequence", "T"), + "MutableSet": ("collections.abc.MutableSet", "T"), + "Sequence": ("collections.abc.Sequence", "T"), + "ValuesView": ("collections.abc.ValuesView", "ValueType"), + "Iterable": ("collections.abc.Iterable", "T"), + "Iterator": ("collections.abc.Iterator", "T"), + "Generator": ("collections.abc.Generator", "YieldType, SendType, ReturnType"), + "Hashable": ("collections.abc.Hashable", None), + "Reversible": ("collections.abc.Reversible", "T"), + "Sized": ("collections.abc.Sized", None), + "Coroutine": ("collections.abc.Coroutine", "YieldType, SendType, ReturnType"), + "AsyncGenerator": ("collections.abc.AsyncGenerator", "YieldType, SendType"), + "AsyncIterator": ("collections.abc.AsyncIterator", "T"), + "AsyncIterable": ("collections.abc.AsyncIterable", "T"), + "Awaitable": ("collections.abc.Awaitable", "T"), + "Callable": ("collections.abc.Callable", None), + "Container": ("collections.abc.Container", "T"), } # Y023: Import things from typing instead of typing_extensions # if they're available from the typing module on 3.7+ _BAD_TYPINGEXTENSIONS_Y023_IMPORTS = frozenset( { + "AnyStr", + "BinaryIO", + "ForwardRef", + "Generic", + "IO", "Protocol", + "TextIO", "runtime_checkable", "NewType", "overload", - "Text", "NoReturn", # ClassVar deliberately omitted, # as it's the only one in this group that should be parameterised. # It is special-cased elsewhere. + # + # Text is also deliberately omitted, + # as you shouldn't be importing it from anywhere! (Y039) } ) @@ -284,6 +269,7 @@ _TYPING_MODULES = frozenset({"typing", "typing_extensions"}) +_TYPING_OR_COLLECTIONS_ABC = _TYPING_MODULES | {"collections.abc"} def _is_object(node: ast.AST | None, name: str, *, from_: Container[str]) -> bool: @@ -295,8 +281,7 @@ where <parent> is a string that can be found within the `from_` collection of strings. - >>> modules = _TYPING_MODULES | {"collections.abc"} - >>> _is_AsyncIterator = partial(_is_object, name="AsyncIterator", from_=modules) + >>> _is_AsyncIterator = partial(_is_object, name="AsyncIterator", from_=_TYPING_OR_COLLECTIONS_ABC) >>> _is_AsyncIterator(_ast_node_for("AsyncIterator")) True >>> _is_AsyncIterator(_ast_node_for("typing.AsyncIterator")) @@ -323,12 +308,15 @@ _is_BaseException = partial(_is_object, name="BaseException", from_={"builtins"}) _is_TypeAlias = partial(_is_object, name="TypeAlias", from_=_TYPING_MODULES) _is_NamedTuple = partial(_is_object, name="NamedTuple", from_=_TYPING_MODULES) +_is_deprecated = partial( + _is_object, name="deprecated", from_={"typing_extensions", "warnings"} +) _is_TypedDict = partial( _is_object, name="TypedDict", from_=_TYPING_MODULES | {"mypy_extensions"} ) _is_Literal = partial(_is_object, name="Literal", from_=_TYPING_MODULES) _is_abstractmethod = partial(_is_object, name="abstractmethod", from_={"abc"}) -_is_Any = partial(_is_object, name="Any", from_={"typing"}) +_is_Any = partial(_is_object, name="Any", from_=_TYPING_MODULES) _is_overload = partial(_is_object, name="overload", from_=_TYPING_MODULES) _is_final = partial(_is_object, name="final", from_=_TYPING_MODULES) _is_Self = partial(_is_object, name="Self", from_=({"_typeshed"} | _TYPING_MODULES)) @@ -336,13 +324,18 @@ _is_builtins_object = partial(_is_object, name="object", from_={"builtins"}) _is_builtins_type = partial(_is_object, name="type", from_={"builtins"}) _is_Unused = partial(_is_object, name="Unused", from_={"_typeshed"}) -_is_Iterable = partial(_is_object, name="Iterable", from_={"typing", "collections.abc"}) +_is_Iterable = partial(_is_object, name="Iterable", from_=_TYPING_OR_COLLECTIONS_ABC) _is_AsyncIterable = partial( - _is_object, name="AsyncIterable", from_={"collections.abc"} | _TYPING_MODULES + _is_object, name="AsyncIterable", from_=_TYPING_OR_COLLECTIONS_ABC ) _is_Protocol = partial(_is_object, name="Protocol", from_=_TYPING_MODULES) _is_NoReturn = partial(_is_object, name="NoReturn", from_=_TYPING_MODULES) _is_Final = partial(_is_object, name="Final", from_=_TYPING_MODULES) +_is_Generator = partial(_is_object, name="Generator", from_=_TYPING_OR_COLLECTIONS_ABC) +_is_AsyncGenerator = partial( + _is_object, name="AsyncGenerator", from_=_TYPING_OR_COLLECTIONS_ABC +) +_is_Generic = partial(_is_object, name="Generic", from_=_TYPING_MODULES) def _is_object_or_Unused(node: ast.expr | None) -> bool: @@ -473,7 +466,7 @@ if not isinstance(node, ast.Subscript): return None return _get_name_of_class_if_from_modules( - node.value, modules=_TYPING_MODULES | {"collections.abc"} + node.value, modules=_TYPING_OR_COLLECTIONS_ABC ) @@ -500,6 +493,11 @@ method: ast.FunctionDef | ast.AsyncFunctionDef, *, classdef: ast.ClassDef ) -> bool: """Return `True` if `function` should be rewritten with `typing_extensions.Self`.""" + # PEP 673 forbids the use of `typing(_extensions).Self` in metaclasses. + # Do our best to avoid false positives here: + if _is_metaclass(classdef): + return False + # Much too complex for our purposes to worry # about overloaded functions or abstractmethods if any( @@ -693,9 +691,12 @@ "sys.version", "sys.version_info", "sys.winver", + "_typeshed.sentinel", } ) +_ALLOWED_SIMPLE_ATTRIBUTES_IN_DEFAULTS = frozenset({"sentinel"}) + def _is_valid_default_value_with_annotation( node: ast.expr, *, allow_containers: bool = True @@ -784,6 +785,8 @@ return (fullname in _ALLOWED_ATTRIBUTES_IN_DEFAULTS) or ( fullname in _ALLOWED_MATH_ATTRIBUTES_IN_DEFAULTS ) + elif isinstance(node, ast.Name): + return node.id in _ALLOWED_SIMPLE_ATTRIBUTES_IN_DEFAULTS return False @@ -834,6 +837,86 @@ return any(_is_enum_base(base) for base in node.bases) +_COMMON_METACLASSES = { + "type": "builtins", + "ABCMeta": "abc", + "EnumMeta": "enum", + "EnumType": "enum", +} + + +def _is_metaclass_base(node: ast.expr) -> bool: + if isinstance(node, ast.Name): + return node.id in _COMMON_METACLASSES + return ( + isinstance(node, ast.Attribute) + and node.attr in _COMMON_METACLASSES + and _is_name(node.value, _COMMON_METACLASSES[node.attr]) + ) + + +def _is_metaclass(node: ast.ClassDef) -> bool: + """Best-effort attempt to determine if a class is a metaclass or not.""" + return any(_is_metaclass_base(base) for base in node.bases) + + +def _check_import_or_attribute( + node: ast.Attribute | ast.ImportFrom, module_name: str, object_name: str +) -> str | None: + """If `node` represents a bad import, return the approriate error message. + + Else, return None. + """ + fullname = f"{module_name}.{object_name}" + + # Y057 errors + if fullname in {"typing.ByteString", "collections.abc.ByteString"}: + return Y057.format(module=module_name) + + # Y024 errors + if fullname == "collections.namedtuple": + return Y024 + + if module_name in _TYPING_MODULES: + # Y022 errors + if object_name in _BAD_Y022_IMPORTS: + good_cls_name, slice_contents = _BAD_Y022_IMPORTS[object_name] + params = "" if slice_contents is None else f"[{slice_contents}]" + return Y022.format( + good_syntax=f'"{good_cls_name}{params}"', + bad_syntax=f'"{fullname}{params}"', + ) + + # Y037 errors + if object_name == "Optional": + return Y037.format( + old_syntax=fullname, example='"int | None" instead of "Optional[int]"' + ) + if object_name == "Union": + return Y037.format( + old_syntax=fullname, example='"int | str" instead of "Union[int, str]"' + ) + + # Y039 errors + if object_name == "Text": + return Y039.format(module=module_name) + + # Y023 errors + if module_name == "typing_extensions": + if object_name in _BAD_TYPINGEXTENSIONS_Y023_IMPORTS: + return Y023.format( + good_syntax=f'"typing.{object_name}"', + bad_syntax=f'"typing_extensions.{object_name}"', + ) + if object_name == "ClassVar": + return Y023.format( + good_syntax='"typing.ClassVar[T]"', + bad_syntax='"typing_extensions.ClassVar[T]"', + ) + + return None + + @dataclass class NestingCounter: """Class to help the PyiVisitor keep track of internal state""" @@ -878,6 +961,7 @@ all_name_occurrences: Counter[str] string_literals_allowed: NestingCounter + long_strings_allowed: NestingCounter in_function: NestingCounter in_class: NestingCounter visiting_arg: NestingCounter @@ -895,6 +979,7 @@ self.typealias_decls = defaultdict(list) self.all_name_occurrences = Counter() self.string_literals_allowed = NestingCounter() + self.long_strings_allowed = NestingCounter() self.in_function = NestingCounter() self.in_class = NestingCounter() self.visiting_arg = NestingCounter() @@ -902,67 +987,12 @@ def __repr__(self) -> str: return f"{self.__class__.__name__}(filename={self.filename!r})" - def _check_import_or_attribute( - self, node: ast.Attribute | ast.ImportFrom, module_name: str, object_name: str - ) -> None: - fullname = f"{module_name}.{object_name}" - - # Y057 errors - if fullname in {"typing.ByteString", "collections.abc.ByteString"}: - error_message = Y057.format(module=module_name) - - # Y022 errors - elif fullname in _BAD_Y022_IMPORTS: - good_cls_name, slice_contents = _BAD_Y022_IMPORTS[fullname] - params = "" if slice_contents is None else f"[{slice_contents}]" - error_message = Y022.format( - good_syntax=f'"{good_cls_name}{params}"', - bad_syntax=f'"{fullname}{params}"', - ) - - # Y023 errors - elif module_name == "typing_extensions": - if object_name in _BAD_TYPINGEXTENSIONS_Y023_IMPORTS: - error_message = Y023.format( - good_syntax=f'"typing.{object_name}"', - bad_syntax=f'"typing_extensions.{object_name}"', - ) - elif object_name == "ClassVar": - error_message = Y023.format( - good_syntax='"typing.ClassVar[T]"', - bad_syntax='"typing_extensions.ClassVar[T]"', - ) - else: - return - - # Y024 errors - elif fullname == "collections.namedtuple": - error_message = Y024 - - # Y037 errors - elif fullname == "typing.Optional": - error_message = Y037.format( - old_syntax=fullname, example='"int | None" instead of "Optional[int]"' - ) - elif fullname == "typing.Union": - error_message = Y037.format( - old_syntax=fullname, example='"int | str" instead of "Union[int, str]"' - ) - - # Y039 errors - elif fullname == "typing.Text": - error_message = Y039 - - else: - return - - self.error(node, error_message) - def visit_Attribute(self, node: ast.Attribute) -> None: self.generic_visit(node) - self._check_import_or_attribute( + if error_msg := _check_import_or_attribute( node=node, module_name=unparse(node.value), object_name=node.attr - ) + ): + self.error(node, error_msg) def visit_ImportFrom(self, node: ast.ImportFrom) -> None: self.generic_visit(node) @@ -986,10 +1016,11 @@ self.error(node, Y025) for object_name in imported_names: - self._check_import_or_attribute(node, module_name, object_name) + if error_msg := _check_import_or_attribute(node, module_name, object_name): + self.error(node, error_msg) - if module_name == "typing" and "AbstractSet" in imported_names: - self.error(node, Y038) + if module_name in _TYPING_MODULES and "AbstractSet" in imported_names: + self.error(node, Y038.format(module=module_name)) def _check_for_typevarlike_assignments( self, node: ast.Assign, function: ast.expr, object_name: str @@ -1130,6 +1161,11 @@ if _is_bad_TypedDict(node): self.error(node, Y031) return + elif _is_deprecated(function): + with self.string_literals_allowed.enabled(), self.long_strings_allowed.enabled(): + for arg in chain(node.args, node.keywords): + self.visit(arg) + return elif ( isinstance(function, ast.Attribute) and isinstance(function.value, ast.Name) @@ -1150,7 +1186,10 @@ def visit_Constant(self, node: ast.Constant) -> None: if isinstance(node.value, str) and not self.string_literals_allowed.active: self.error(node, Y020) - elif isinstance(node.value, (str, bytes)): + elif ( + isinstance(node.value, (str, bytes)) + and not self.long_strings_allowed.active + ): if len(node.value) > 50: self.error(node, Y053) elif isinstance(node.value, (int, float, complex)): @@ -1260,23 +1299,23 @@ def _check_for_Y051_violations(self, analysis: UnionAnalysis) -> None: """Search for redundant unions such as `str | Literal["foo"]`, etc.""" - literal_classes_present: defaultdict[str, list[_SliceContents]] - literal_classes_present = defaultdict(list) + seen_builtins: set[type] = set() for literal in analysis.combined_literal_members: - interesting_builtins = {str, bytes, int, bool} + if not isinstance(literal, ast.Constant): + continue + typ = type(literal.value) + typename = typ.__name__ if ( - isinstance(literal, ast.Constant) - and type(literal.value) in interesting_builtins + typ in {str, bytes, int, bool} + and typename in analysis.builtins_classes_in_union + and typ not in seen_builtins ): - literal_classes_present[type(literal.value).__name__].append(literal) - for cls, literals in literal_classes_present.items(): - if cls in analysis.builtins_classes_in_union: - first_literal_present = literals[0] + seen_builtins.add(typ) self.error( - first_literal_present, + literal, Y051.format( - literal_subtype=f"Literal[{unparse(first_literal_present)}]", - builtin_supertype=cls, + literal_subtype=f"Literal[{unparse(literal)}]", + builtin_supertype=typename, ), ) @@ -1355,21 +1394,56 @@ self._check_union_members(members, is_pep_604_union=True) + def _Y090_error(self, node: ast.Subscript) -> None: + current_code = unparse(node) + typ = unparse(node.slice) + copied_node = deepcopy(node) + new_slice = ast.Tuple(elts=[copied_node.slice, ast.Constant(...)]) + if sys.version_info >= (3, 9): + copied_node.slice = new_slice + else: + copied_node.slice = ast.Index(new_slice) + suggestion = unparse(copied_node) + self.error(node, Y090.format(original=current_code, typ=typ, new=suggestion)) + def visit_Subscript(self, node: ast.Subscript) -> None: subscripted_object = node.value subscripted_object_name = _get_name_of_class_if_from_modules( - subscripted_object, modules=_TYPING_MODULES + subscripted_object, modules=_TYPING_MODULES | {"builtins"} ) self.visit(subscripted_object) if subscripted_object_name == "Literal": with self.string_literals_allowed.enabled(): - self.visit(node.slice) + self._visit_typing_Literal(node) return if isinstance(node.slice, ast.Tuple): self._visit_slice_tuple(node.slice, subscripted_object_name) else: self.visit(node.slice) + if subscripted_object_name in {"tuple", "Tuple"}: + self._Y090_error(node) + + def _visit_typing_Literal(self, node: ast.Subscript) -> None: + if isinstance(node.slice, ast.Constant) and _is_None(node.slice): + # Special case for `Literal[None]` + self.error(node.slice, Y061.format(suggestion="None")) + elif isinstance(node.slice, ast.Tuple): + elts = node.slice.elts + for i, elt in enumerate(elts): + if _is_None(elt): + elts_without_none = elts[:i] + [ + elt for elt in elts[i + 1 :] if not _is_None(elt) + ] + if len(elts_without_none) == 1: + new_literal_slice = unparse(elts_without_none[0]) + else: + new_slice_node = ast.Tuple(elts=elts_without_none) + new_literal_slice = unparse(new_slice_node).strip("()") + suggestion = f"Literal[{new_literal_slice}] | None" + self.error(elt, Y061.format(suggestion=suggestion)) + break # Only report the first `None` + self.visit(node.slice) def _visit_slice_tuple(self, node: ast.Tuple, parent: str | None) -> None: if parent == "Union": @@ -1516,6 +1590,32 @@ else: self.error(node, Y007) + def _check_class_bases(self, bases: list[ast.expr]) -> None: + Y040_encountered = False + Y059_encountered = False + Generic_basenode: ast.Subscript | None = None + subscript_bases: list[ast.Subscript] = [] + num_bases = len(bases) + + for i, base_node in enumerate(bases, start=1): + if not Y040_encountered and _is_builtins_object(base_node): + self.error(base_node, Y040) + Y040_encountered = True + if isinstance(base_node, ast.Subscript): + subscript_bases.append(base_node) + if _is_Generic(base_node.value): + Generic_basenode = base_node + if i < num_bases and not Y059_encountered: + Y059_encountered = True + self.error(base_node, Y059) + + if Generic_basenode is not None: + assert subscript_bases + if len(subscript_bases) > 1 and all_equal( + ast.dump(subscript_base.slice) for subscript_base in subscript_bases + ): + self.error(Generic_basenode, Y060) + def visit_ClassDef(self, node: ast.ClassDef) -> None: if node.name.startswith("_") and not self.in_class.active: for base in node.bases: @@ -1532,8 +1632,7 @@ self.generic_visit(node) self.current_class_node = old_class_node - if any(_is_builtins_object(base_node) for base_node in node.bases): - self.error(node, Y040) + self._check_class_bases(node.bases) # empty class body should contain "..." not "pass" if len(node.body) == 1: @@ -1683,6 +1782,15 @@ ) self.error(node, error_message) + def _Y058_error( + self, node: ast.FunctionDef, args: list[ast.arg], example_returns: str + ) -> None: + assert node.name in {"__iter__", "__aiter__"} + good_cls = "Iterator" if node.name == "__iter__" else "AsyncIterator" + example = f"def {node.name}({args[0].arg}) -> {example_returns}: ..." + msg = Y058.format(iter_method=node.name, good_cls=good_cls, example=example) + self.error(node, msg) + def _check_iter_returns( self, node: ast.FunctionDef, returns: ast.expr | None ) -> None: @@ -1693,6 +1801,24 @@ iter_method="__iter__", good_cls="Iterator", bad_cls="Iterable" ) self.error(node, msg) + return + non_kw_only_args = node.args.posonlyargs + node.args.args + if len(non_kw_only_args) == 1 and not node.args.kwonlyargs: + if _is_Generator(returns): + self._Y058_error(node, non_kw_only_args, "Iterator") + elif ( + isinstance(returns, ast.Subscript) + and _is_Generator(returns.value) + and isinstance(returns.slice, ast.Tuple) + ): + elts = returns.slice.elts + if ( + len(elts) == 3 + and (_is_Any(elts[1]) or _is_None(elts[1])) + and (_is_Any(elts[2]) or _is_None(elts[2])) + ): + example_returns = f"Iterator[{unparse(returns.slice.elts[0])}]" + self._Y058_error(node, non_kw_only_args, example_returns) def _check_aiter_returns( self, node: ast.FunctionDef, returns: ast.expr | None @@ -1706,6 +1832,20 @@ bad_cls="AsyncIterable", ) self.error(node, msg) + return + non_kw_only_args = node.args.posonlyargs + node.args.args + if len(non_kw_only_args) == 1 and not node.args.kwonlyargs: + if _is_AsyncGenerator(returns): + self._Y058_error(node, non_kw_only_args, "AsyncIterator") + elif ( + isinstance(returns, ast.Subscript) + and _is_AsyncGenerator(returns.value) + and isinstance(returns.slice, ast.Tuple) + ): + elts = returns.slice.elts + if len(elts) == 2 and (_is_Any(elts[1]) or _is_None(elts[1])): + example_returns = f"AsyncIterator[{unparse(returns.slice.elts[0])}]" + self._Y058_error(node, non_kw_only_args, example_returns) def _visit_synchronous_method(self, node: ast.FunctionDef) -> None: method_name = node.name @@ -1788,7 +1928,7 @@ return True if sys.version_info < (3, 12): return False - return any( # type: ignore[unreachable] + return any( # type: ignore[unreachable,unused-ignore] isinstance(param, ast.TypeVar) and param.name == tvar_name for param in method.type_params ) @@ -1986,8 +2126,6 @@ @dataclass class PyiTreeChecker: name: ClassVar[str] = "flake8-pyi" - version: ClassVar[str] = __version__ - tree: ast.Module lines: list[str] filename: str = "(none)" @@ -2002,6 +2140,7 @@ def add_options(parser: OptionManager) -> None: """This is brittle, there's multiple levels of caching of defaults.""" parser.parser.set_defaults(filename="*.py,*.pyi") + parser.extend_default_ignore(DISABLED_BY_DEFAULT) parser.add_option( "--no-pyi-aware-file-checker", default=False, @@ -2074,9 +2213,9 @@ Y037 = "Y037 Use PEP 604 union types instead of {old_syntax} (e.g. {example})." Y038 = ( 'Y038 Use "from collections.abc import Set as AbstractSet" ' - 'instead of "from typing import AbstractSet" (PEP 585 syntax)' + 'instead of "from {module} import AbstractSet" (PEP 585 syntax)' ) -Y039 = 'Y039 Use "str" instead of "typing.Text"' +Y039 = 'Y039 Use "str" instead of "{module}.Text"' Y040 = 'Y040 Do not inherit from "object" explicitly, as it is redundant in Python 3' Y041 = ( 'Y041 Use "{implicit_supertype}" ' @@ -2109,3 +2248,20 @@ Y057 = ( "Y057 Do not use {module}.ByteString, which has unclear semantics and is deprecated" ) +Y058 = ( + 'Y058 Use "{good_cls}" as the return value for simple "{iter_method}" methods, ' + 'e.g. "{example}"' +) +Y059 = 'Y059 "Generic[]" should always be the last base class' +Y060 = ( + 'Y060 Redundant inheritance from "Generic[]"; ' + "class would be inferred as generic anyway" +) +Y061 = 'Y061 None inside "Literal[]" expression. Replace with "{suggestion}"' +Y090 = ( + 'Y090 "{original}" means ' + '"a tuple of length 1, in which the sole element is of type {typ!r}". ' + 'Perhaps you meant "{new}"?' +) + +DISABLED_BY_DEFAULT = ["Y090"] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/flake8_pyi-23.6.0/pyproject.toml new/flake8_pyi-23.11.0/pyproject.toml --- old/flake8_pyi-23.6.0/pyproject.toml 2020-02-02 01:00:00.000000000 +0100 +++ new/flake8_pyi-23.11.0/pyproject.toml 2020-02-02 01:00:00.000000000 +0100 @@ -1,5 +1,5 @@ [build-system] -requires = ["hatchling"] +requires = ["hatchling", "hatch-vcs"] build-backend = "hatchling.build" [project] @@ -60,13 +60,14 @@ [project.optional-dependencies] dev = [ - "black==23.3.0", # Must match .pre-commit-config.yaml - "flake8-bugbear==23.6.5", + "black==23.9.1", # Must match .pre-commit-config.yaml + "flake8-bugbear==23.9.16", "flake8-noqa==1.3.2", "isort==5.12.0", # Must match .pre-commit-config.yaml - "mypy==1.4.1", - "pre-commit-hooks==4.4.0", # Must match .pre-commit-config.yaml - "pytest==7.4.0", + "mypy==1.6.0", + "pre-commit-hooks==4.5.0", # Must match .pre-commit-config.yaml + "pytest==7.4.2", + "pytest-xdist==3.3.1", "types-pyflakes<4", ] @@ -74,12 +75,15 @@ "flake8.extension" = {Y0 = "pyi:PyiTreeChecker"} [tool.hatch.version] -path = "pyi.py" +source = "vcs" + +[tool.hatch.version.raw-options] +local_scheme = "no-local-version" [tool.isort] profile = "black" combine_as_imports = true -skip = ["tests/imports.pyi"] +skip = ["tests/imports.pyi", "tests/pep604_union_types.pyi"] skip_gitignore = true [tool.black] @@ -101,4 +105,5 @@ ignore_missing_imports = true [tool.pytest.ini_options] -addopts = "--doctest-modules" +addopts = "--doctest-modules -nauto" +filterwarnings = ["error"] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/flake8_pyi-23.6.0/tests/classdefs.pyi new/flake8_pyi-23.11.0/tests/classdefs.pyi --- old/flake8_pyi-23.6.0/tests/classdefs.pyi 2020-02-02 01:00:00.000000000 +0100 +++ new/flake8_pyi-23.11.0/tests/classdefs.pyi 2020-02-02 01:00:00.000000000 +0100 @@ -3,10 +3,21 @@ import abc import builtins import collections.abc +import enum import typing -from abc import abstractmethod -from collections.abc import AsyncIterable, AsyncIterator, Iterable, Iterator -from typing import Any, overload +from abc import ABCMeta, abstractmethod +from collections.abc import ( + AsyncGenerator, + AsyncIterable, + AsyncIterator, + Container, + Generator, + Iterable, + Iterator, + Mapping, +) +from enum import EnumMeta +from typing import Any, Generic, TypeVar, overload import typing_extensions from _typeshed import Self @@ -94,12 +105,54 @@ class IteratorReturningIterable: def __iter__(self) -> Iterable[str]: ... # Y045 "__iter__" methods should return an Iterator, not an Iterable +class IteratorReturningSimpleGenerator1: + def __iter__(self) -> Generator: ... # Y058 Use "Iterator" as the return value for simple "__iter__" methods, e.g. "def __iter__(self) -> Iterator: ..." + +class IteratorReturningSimpleGenerator2: + 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]: ..." + +class IteratorReturningComplexGenerator: + def __iter__(self) -> Generator[str, int, bytes]: ... + class BadAsyncIterator(collections.abc.AsyncIterator[str]): 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) class AsyncIteratorReturningAsyncIterable: def __aiter__(self) -> AsyncIterable[str]: ... # Y045 "__aiter__" methods should return an AsyncIterator, not an AsyncIterable +class AsyncIteratorReturningSimpleAsyncGenerator1: + def __aiter__(self) -> AsyncGenerator: ... # Y058 Use "AsyncIterator" as the return value for simple "__aiter__" methods, e.g. "def __aiter__(self) -> AsyncIterator: ..." + +class AsyncIteratorReturningSimpleAsyncGenerator2: + 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]: ..." + +class AsyncIteratorReturningComplexAsyncGenerator: + def __aiter__(self) -> AsyncGenerator[str, int]: ... + +class MetaclassInWhichSelfCannotBeUsed(type): + def __new__(cls) -> MetaclassInWhichSelfCannotBeUsed: ... + def __enter__(self) -> MetaclassInWhichSelfCannotBeUsed: ... + async def __aenter__(self) -> MetaclassInWhichSelfCannotBeUsed: ... + def __isub__(self, other: MetaclassInWhichSelfCannotBeUsed) -> MetaclassInWhichSelfCannotBeUsed: ... + +class MetaclassInWhichSelfCannotBeUsed2(EnumMeta): + def __new__(cls) -> MetaclassInWhichSelfCannotBeUsed2: ... + def __enter__(self) -> MetaclassInWhichSelfCannotBeUsed2: ... + async def __aenter__(self) -> MetaclassInWhichSelfCannotBeUsed2: ... + def __isub__(self, other: MetaclassInWhichSelfCannotBeUsed2) -> MetaclassInWhichSelfCannotBeUsed2: ... + +class MetaclassInWhichSelfCannotBeUsed3(enum.EnumType): + def __new__(cls) -> MetaclassInWhichSelfCannotBeUsed3: ... + def __enter__(self) -> MetaclassInWhichSelfCannotBeUsed3: ... + async def __aenter__(self) -> MetaclassInWhichSelfCannotBeUsed3: ... + def __isub__(self, other: MetaclassInWhichSelfCannotBeUsed3) -> MetaclassInWhichSelfCannotBeUsed3: ... + +class MetaclassInWhichSelfCannotBeUsed4(ABCMeta): + def __new__(cls) -> MetaclassInWhichSelfCannotBeUsed4: ... + def __enter__(self) -> MetaclassInWhichSelfCannotBeUsed4: ... + async def __aenter__(self) -> MetaclassInWhichSelfCannotBeUsed4: ... + def __isub__(self, other: MetaclassInWhichSelfCannotBeUsed4) -> MetaclassInWhichSelfCannotBeUsed4: ... + class Abstract(Iterator[str]): @abstractmethod def __iter__(self) -> Iterator[str]: ... @@ -134,3 +187,43 @@ def __eq__(self, other: Any) -> bool: ... def __ne__(self, other: Any) -> bool: ... def __imul__(self, other: Any) -> list[str]: ... + +_S = TypeVar("_S") +_T = TypeVar("_T") + +class BadGeneric(Generic[_T], int): ... # Y059 "Generic[]" should always be the last base class +class GoodGeneric(Generic[_T]): ... + +class BadGeneric2(int, typing.Generic[_T], str): ... # Y059 "Generic[]" should always be the last base class +class GoodGeneric2(int, typing.Generic[_T]): ... + +class BadGeneric3(typing_extensions.Generic[_T], int, str): ... # Y059 "Generic[]" should always be the last base class +class GoodGeneric3(int, str, typing_extensions.Generic[_T]): ... + +class BadGeneric4(Generic[_T], Iterable[int], str): ... # Y059 "Generic[]" should always be the last base class +class GoodGeneric4(Iterable[int], str, Generic[_T]): ... + +class RedundantGeneric1(Iterable[_T], Generic[_T]): ... # Y060 Redundant inheritance from "Generic[]"; class would be inferred as generic anyway +class Corrected1(Iterable[_T]): ... + +class RedundantGeneric2(Generic[_S], GoodGeneric[_S]): ... # Y059 "Generic[]" should always be the last base class # Y060 Redundant inheritance from "Generic[]"; class would be inferred as generic anyway +class Corrected2(GoodGeneric[_S]): ... + +class RedundantGeneric3(int, Iterator[_T], str, float, memoryview, bytes, Generic[_T]): ... # Y060 Redundant inheritance from "Generic[]"; class would be inferred as generic anyway +class Corrected3(int, Iterator[_T], str, float, memoryview, bytes): ... + +class RedundantGeneric4(Iterable[_T], Iterator[_T], Generic[_T]): ... # Y060 Redundant inheritance from "Generic[]"; class would be inferred as generic anyway +class Corrected4(Iterable[_T], Iterator[_T]): ... + +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[]"; class would be inferred as generic anyway +class Corrected5(Container[_T]): ... + +# Strictly speaking this inheritance from Generic is "redundant", +# but people may consider it more readable to explicitly inherit from Generic, +# so we deliberately don't flag it with Y060 +class GoodGeneric5(Container[_S], Iterator[_T], Generic[_S, _T]): ... + +# And these definitely arent't redundant, +# since the order of the type variables is changed via the inheritance from Generic: +class GoodGeneric6(Container[_S], Iterator[_T], Generic[_T, _S]): ... +class GoodGeneric7(Mapping[_S, _T], Generic[_T, _S]): ... diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/flake8_pyi-23.6.0/tests/defaults.pyi new/flake8_pyi-23.11.0/tests/defaults.pyi --- old/flake8_pyi-23.6.0/tests/defaults.pyi 2020-02-02 01:00:00.000000000 +0100 +++ new/flake8_pyi-23.11.0/tests/defaults.pyi 2020-02-02 01:00:00.000000000 +0100 @@ -2,7 +2,8 @@ import os import sys -from _typeshed import SupportsRead, SupportsWrite +import _typeshed +from _typeshed import SupportsRead, SupportsWrite, sentinel def f1(x: int = ...) -> None: ... def f2(x: int = 3) -> None: ... @@ -68,6 +69,8 @@ def f34(x: str = sys.version) -> None: ... def f35(x: tuple[int, ...] = sys.version_info) -> None: ... def f36(x: str = sys.winver) -> None: ... +def f361(x: str | None = sentinel) -> None: ... +def f362(x: str | None = _typeshed.sentinel) -> None: ... def f37(x: str = "a_very_long_stringgggggggggggggggggggggggggggggggggggggggggggggg") -> None: ... # Y053 String and bytes literals >50 characters long are not permitted def f38(x: bytes = b"a_very_long_byte_stringggggggggggggggggggggggggggggggggggggg") -> None: ... # Y053 String and bytes literals >50 characters long are not permitted diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/flake8_pyi-23.6.0/tests/disabled_by_default.pyi new/flake8_pyi-23.11.0/tests/disabled_by_default.pyi --- old/flake8_pyi-23.6.0/tests/disabled_by_default.pyi 1970-01-01 01:00:00.000000000 +0100 +++ new/flake8_pyi-23.11.0/tests/disabled_by_default.pyi 2020-02-02 01:00:00.000000000 +0100 @@ -0,0 +1,9 @@ +# This test file checks that disabled-by-default error codes aren't triggered, +# unless they're explicitly enabled +from typing import ( # Y022 Use "tuple[Foo, Bar]" instead of "typing.Tuple[Foo, Bar]" (PEP 585 syntax) + Tuple, +) + +# These would trigger Y090, but it's disabled by default +x: tuple[int] +y: Tuple[str] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/flake8_pyi-23.6.0/tests/imports.pyi new/flake8_pyi-23.11.0/tests/imports.pyi --- old/flake8_pyi-23.6.0/tests/imports.pyi 2020-02-02 01:00:00.000000000 +0100 +++ new/flake8_pyi-23.11.0/tests/imports.pyi 2020-02-02 01:00:00.000000000 +0100 @@ -80,17 +80,8 @@ from typing import Counter # Y022 Use "collections.Counter[KeyType]" instead of "typing.Counter[KeyType]" (PEP 585 syntax) from typing import AsyncContextManager # Y022 Use "contextlib.AbstractAsyncContextManager[T]" instead of "typing.AsyncContextManager[T]" (PEP 585 syntax) from typing import ChainMap # Y022 Use "collections.ChainMap[KeyType, ValueType]" instead of "typing.ChainMap[KeyType, ValueType]" (PEP 585 syntax) -from typing_extensions import Type # Y022 Use "type[MyClass]" instead of "typing_extensions.Type[MyClass]" (PEP 585 syntax) -from typing_extensions import DefaultDict # Y022 Use "collections.defaultdict[KeyType, ValueType]" instead of "typing_extensions.DefaultDict[KeyType, ValueType]" (PEP 585 syntax) -from typing_extensions import ChainMap # Y022 Use "collections.ChainMap[KeyType, ValueType]" instead of "typing_extensions.ChainMap[KeyType, ValueType]" (PEP 585 syntax) -from typing_extensions import AsyncContextManager # Y022 Use "contextlib.AbstractAsyncContextManager[T]" instead of "typing_extensions.AsyncContextManager[T]" (PEP 585 syntax) -from typing_extensions import Awaitable # Y022 Use "collections.abc.Awaitable[T]" instead of "typing_extensions.Awaitable[T]" (PEP 585 syntax) -from typing_extensions import ContextManager # Y022 Use "contextlib.AbstractContextManager[T]" instead of "typing_extensions.ContextManager[T]" (PEP 585 syntax) -from typing_extensions import AsyncGenerator # Y022 Use "collections.abc.AsyncGenerator[YieldType, SendType]" instead of "typing_extensions.AsyncGenerator[YieldType, SendType]" (PEP 585 syntax) -from typing_extensions import Coroutine # Y022 Use "collections.abc.Coroutine[YieldType, SendType, ReturnType]" instead of "typing_extensions.Coroutine[YieldType, SendType, ReturnType]" (PEP 585 syntax) from typing import ContextManager # Y022 Use "contextlib.AbstractContextManager[T]" instead of "typing.ContextManager[T]" (PEP 585 syntax) from typing import OrderedDict # Y022 Use "collections.OrderedDict[KeyType, ValueType]" instead of "typing.OrderedDict[KeyType, ValueType]" (PEP 585 syntax) -from typing_extensions import OrderedDict # Y022 Use "collections.OrderedDict[KeyType, ValueType]" instead of "typing_extensions.OrderedDict[KeyType, ValueType]" (PEP 585 syntax) from typing import Callable # Y022 Use "collections.abc.Callable" instead of "typing.Callable" (PEP 585 syntax) from typing import Container # Y022 Use "collections.abc.Container[T]" instead of "typing.Container[T]" (PEP 585 syntax) from typing import Hashable # Y022 Use "collections.abc.Hashable" instead of "typing.Hashable" (PEP 585 syntax) @@ -116,6 +107,37 @@ from typing import Generator # Y022 Use "collections.abc.Generator[YieldType, SendType, ReturnType]" instead of "typing.Generator[YieldType, SendType, ReturnType]" (PEP 585 syntax) from typing import Match # Y022 Use "re.Match[T]" instead of "typing.Match[T]" (PEP 585 syntax) from typing import Pattern # Y022 Use "re.Pattern[T]" instead of "typing.Pattern[T]" (PEP 585 syntax) +from typing_extensions import Dict # Y022 Use "dict[KeyType, ValueType]" instead of "typing_extensions.Dict[KeyType, ValueType]" (PEP 585 syntax) +from typing_extensions import Counter # Y022 Use "collections.Counter[KeyType]" instead of "typing_extensions.Counter[KeyType]" (PEP 585 syntax) +from typing_extensions import AsyncContextManager # Y022 Use "contextlib.AbstractAsyncContextManager[T]" instead of "typing_extensions.AsyncContextManager[T]" (PEP 585 syntax) +from typing_extensions import ChainMap # Y022 Use "collections.ChainMap[KeyType, ValueType]" instead of "typing_extensions.ChainMap[KeyType, ValueType]" (PEP 585 syntax) +from typing_extensions import ContextManager # Y022 Use "contextlib.AbstractContextManager[T]" instead of "typing_extensions.ContextManager[T]" (PEP 585 syntax) +from typing_extensions import OrderedDict # Y022 Use "collections.OrderedDict[KeyType, ValueType]" instead of "typing_extensions.OrderedDict[KeyType, ValueType]" (PEP 585 syntax) +from typing_extensions import Callable # Y022 Use "collections.abc.Callable" instead of "typing_extensions.Callable" (PEP 585 syntax) +from typing_extensions import Container # Y022 Use "collections.abc.Container[T]" instead of "typing_extensions.Container[T]" (PEP 585 syntax) +from typing_extensions import Hashable # Y022 Use "collections.abc.Hashable" instead of "typing_extensions.Hashable" (PEP 585 syntax) +from typing_extensions import ItemsView # Y022 Use "collections.abc.ItemsView[KeyType, ValueType]" instead of "typing_extensions.ItemsView[KeyType, ValueType]" (PEP 585 syntax) +from typing_extensions import Iterable # Y022 Use "collections.abc.Iterable[T]" instead of "typing_extensions.Iterable[T]" (PEP 585 syntax) +from typing_extensions import Iterator # Y022 Use "collections.abc.Iterator[T]" instead of "typing_extensions.Iterator[T]" (PEP 585 syntax) +from typing_extensions import KeysView # Y022 Use "collections.abc.KeysView[KeyType]" instead of "typing_extensions.KeysView[KeyType]" (PEP 585 syntax) +from typing_extensions import Mapping # Y022 Use "collections.abc.Mapping[KeyType, ValueType]" instead of "typing_extensions.Mapping[KeyType, ValueType]" (PEP 585 syntax) +from typing_extensions import MappingView # Y022 Use "collections.abc.MappingView" instead of "typing_extensions.MappingView" (PEP 585 syntax) +from typing_extensions import MutableMapping # Y022 Use "collections.abc.MutableMapping[KeyType, ValueType]" instead of "typing_extensions.MutableMapping[KeyType, ValueType]" (PEP 585 syntax) +from typing_extensions import MutableSequence # Y022 Use "collections.abc.MutableSequence[T]" instead of "typing_extensions.MutableSequence[T]" (PEP 585 syntax) +from typing_extensions import MutableSet # Y022 Use "collections.abc.MutableSet[T]" instead of "typing_extensions.MutableSet[T]" (PEP 585 syntax) +from typing_extensions import Sequence # Y022 Use "collections.abc.Sequence[T]" instead of "typing_extensions.Sequence[T]" (PEP 585 syntax) +from typing_extensions import Sized # Y022 Use "collections.abc.Sized" instead of "typing_extensions.Sized" (PEP 585 syntax) +from typing_extensions import ValuesView # Y022 Use "collections.abc.ValuesView[ValueType]" instead of "typing_extensions.ValuesView[ValueType]" (PEP 585 syntax) +from typing_extensions import Awaitable # Y022 Use "collections.abc.Awaitable[T]" instead of "typing_extensions.Awaitable[T]" (PEP 585 syntax) +from typing_extensions import AsyncIterator # Y022 Use "collections.abc.AsyncIterator[T]" instead of "typing_extensions.AsyncIterator[T]" (PEP 585 syntax) +from typing_extensions import AsyncIterable # Y022 Use "collections.abc.AsyncIterable[T]" instead of "typing_extensions.AsyncIterable[T]" (PEP 585 syntax) +from typing_extensions import Coroutine # Y022 Use "collections.abc.Coroutine[YieldType, SendType, ReturnType]" instead of "typing_extensions.Coroutine[YieldType, SendType, ReturnType]" (PEP 585 syntax) +from typing_extensions import Collection # Y022 Use "collections.abc.Collection[T]" instead of "typing_extensions.Collection[T]" (PEP 585 syntax) +from typing_extensions import AsyncGenerator # Y022 Use "collections.abc.AsyncGenerator[YieldType, SendType]" instead of "typing_extensions.AsyncGenerator[YieldType, SendType]" (PEP 585 syntax) +from typing_extensions import Reversible # Y022 Use "collections.abc.Reversible[T]" instead of "typing_extensions.Reversible[T]" (PEP 585 syntax) +from typing_extensions import Generator # Y022 Use "collections.abc.Generator[YieldType, SendType, ReturnType]" instead of "typing_extensions.Generator[YieldType, SendType, ReturnType]" (PEP 585 syntax) +from typing_extensions import Match # Y022 Use "re.Match[T]" instead of "typing_extensions.Match[T]" (PEP 585 syntax) +from typing_extensions import Pattern # Y022 Use "re.Pattern[T]" instead of "typing_extensions.Pattern[T]" (PEP 585 syntax) # BAD IMPORTS (Y023 code) from typing_extensions import ClassVar # Y023 Use "typing.ClassVar[T]" instead of "typing_extensions.ClassVar[T]" @@ -125,7 +147,9 @@ from collections import namedtuple # Y024 Use "typing.NamedTuple" instead of "collections.namedtuple" from collections.abc import Set # Y025 Use "from collections.abc import Set as AbstractSet" to avoid confusion with "builtins.set" from typing import AbstractSet # Y038 Use "from collections.abc import Set as AbstractSet" instead of "from typing import AbstractSet" (PEP 585 syntax) +from typing_extensions import AbstractSet # Y038 Use "from collections.abc import Set as AbstractSet" instead of "from typing_extensions import AbstractSet" (PEP 585 syntax) from typing import Text # Y039 Use "str" instead of "typing.Text" +from typing_extensions import Text # Y039 Use "str" instead of "typing_extensions.Text" from typing import ByteString # Y057 Do not use typing.ByteString, which has unclear semantics and is deprecated from collections.abc import ByteString # Y057 Do not use collections.abc.ByteString, which has unclear semantics and is deprecated diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/flake8_pyi-23.6.0/tests/literals.pyi new/flake8_pyi-23.11.0/tests/literals.pyi --- old/flake8_pyi-23.6.0/tests/literals.pyi 1970-01-01 01:00:00.000000000 +0100 +++ new/flake8_pyi-23.11.0/tests/literals.pyi 2020-02-02 01:00:00.000000000 +0100 @@ -0,0 +1,5 @@ +from typing import Literal + +Literal[None] # Y061 None inside "Literal[]" expression. Replace with "None" +Literal[True, None] # Y061 None inside "Literal[]" expression. Replace with "Literal[True] | None" +Literal[1, None, "foo", None] # Y061 None inside "Literal[]" expression. Replace with "Literal[1, 'foo'] | None" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/flake8_pyi-23.6.0/tests/pep604_union_types.pyi new/flake8_pyi-23.11.0/tests/pep604_union_types.pyi --- old/flake8_pyi-23.6.0/tests/pep604_union_types.pyi 2020-02-02 01:00:00.000000000 +0100 +++ new/flake8_pyi-23.11.0/tests/pep604_union_types.pyi 2020-02-02 01:00:00.000000000 +0100 @@ -1,8 +1,14 @@ +# flags: --extend-ignore=F401,F811 +# +# Note: DO NOT RUN ISORT ON THIS FILE. +# It's excluded in our pyproject.toml. + import typing -from typing import ( # Y037 Use PEP 604 union types instead of typing.Optional (e.g. "int | None" instead of "Optional[int]"). # Y037 Use PEP 604 union types instead of typing.Union (e.g. "int | str" instead of "Union[int, str]"). - Optional, - Union, -) +import typing_extensions +from typing import Optional # Y037 Use PEP 604 union types instead of typing.Optional (e.g. "int | None" instead of "Optional[int]"). +from typing import Union # Y037 Use PEP 604 union types instead of typing.Union (e.g. "int | str" instead of "Union[int, str]"). +from typing_extensions import Optional # Y037 Use PEP 604 union types instead of typing_extensions.Optional (e.g. "int | None" instead of "Optional[int]"). +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]"). x1: Optional[str] x2: Optional @@ -14,6 +20,10 @@ y2: typing.Optional # Y037 Use PEP 604 union types instead of typing.Optional (e.g. "int | None" instead of "Optional[int]"). y3: typing.Union[str, int] # Y037 Use PEP 604 union types instead of typing.Union (e.g. "int | str" instead of "Union[int, str]"). y4: typing.Union # Y037 Use PEP 604 union types instead of typing.Union (e.g. "int | str" instead of "Union[int, str]"). +y5: typing_extensions.Optional[str] # Y037 Use PEP 604 union types instead of typing_extensions.Optional (e.g. "int | None" instead of "Optional[int]"). +y6: typing_extensions.Optional # Y037 Use PEP 604 union types instead of typing_extensions.Optional (e.g. "int | None" instead of "Optional[int]"). +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]"). +y8: typing_extensions.Union # Y037 Use PEP 604 union types instead of typing_extensions.Union (e.g. "int | str" instead of "Union[int, str]"). def f1(x: Optional[str] = ...) -> None: ... diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/flake8_pyi-23.6.0/tests/single_element_tuples.pyi new/flake8_pyi-23.11.0/tests/single_element_tuples.pyi --- old/flake8_pyi-23.6.0/tests/single_element_tuples.pyi 1970-01-01 01:00:00.000000000 +0100 +++ new/flake8_pyi-23.11.0/tests/single_element_tuples.pyi 2020-02-02 01:00:00.000000000 +0100 @@ -0,0 +1,8 @@ +# flags: --extend-select=Y090 +import builtins +import typing + +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, ...]"? +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, ...]"? +c: tuple[int, ...] +d: typing.Tuple[builtins.str, builtins.complex] # Y022 Use "tuple[Foo, Bar]" instead of "typing.Tuple[Foo, Bar]" (PEP 585 syntax) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/flake8_pyi-23.6.0/tests/test_pyi_files.py new/flake8_pyi-23.11.0/tests/test_pyi_files.py --- old/flake8_pyi-23.6.0/tests/test_pyi_files.py 2020-02-02 01:00:00.000000000 +0100 +++ new/flake8_pyi-23.11.0/tests/test_pyi_files.py 2020-02-02 01:00:00.000000000 +0100 @@ -35,30 +35,50 @@ expected_output += f"{path}:{lineno}: {match.group(1)}{message}\n" bad_flag_msg = ( - "--ignore flags in test files override the .flake8 config file. " - "Use --extend-ignore instead." - ) + "--{flag} flags in test files override the .flake8 config file. " + "Use --extend-{flag} instead." + ).format + for flag in flags: option = flag.split("=")[0] - assert option != "--ignore", bad_flag_msg + assert option not in {"--ignore", "--select"}, bad_flag_msg(option[2:]) + + # Silence DeprecationWarnings from our dependencies (pyflakes, flake8-bugbear, etc.) + # + # For DeprecationWarnings coming from flake8-pyi itself, + # print the first occurence of each warning to stderr. + # This will fail CI the same as `-Werror:::pyi`, + # but the test failure report that pytest gives is much easier to read + # if we use `-Wdefault:::pyi` + flake8_invocation = [ + sys.executable, + "-Wignore", + "-Wdefault:::pyi", + "-m", + "flake8", + "-j0", + ] run_results = [ # Passing a file on command line subprocess.run( - ["flake8", "-j0", *flags, path], + [*flake8_invocation, *flags, path], env={**os.environ, "PYTHONPATH": "."}, - stdout=subprocess.PIPE, + capture_output=True, + text=True, ), # Passing "-" as the file, and reading from stdin instead subprocess.run( - ["flake8", "-j0", "--stdin-display-name", path, *flags, "-"], + [*flake8_invocation, "--stdin-display-name", path, *flags, "-"], env={**os.environ, "PYTHONPATH": "."}, - input=file_contents.encode("utf-8"), - stdout=subprocess.PIPE, + input=file_contents, + capture_output=True, + text=True, ), ] for run_result in run_results: - output = run_result.stdout.decode("utf-8") - output = re.sub(":[0-9]+: ", ": ", output) # ignore column numbers + output = re.sub(":[0-9]+: ", ": ", run_result.stdout) # ignore column numbers + if run_result.stderr: + output += "\n" + run_result.stderr assert output == expected_output ++++++ set-tests-python-path.patch ++++++ --- /var/tmp/diff_new_pack.ghVi0W/_old 2023-12-06 23:48:42.992338873 +0100 +++ /var/tmp/diff_new_pack.ghVi0W/_new 2023-12-06 23:48:42.996339018 +0100 @@ -1,29 +1,24 @@ -Index: flake8_pyi-23.6.0/tests/test_pyi_files.py +Index: flake8_pyi-23.11.0/tests/test_pyi_files.py =================================================================== ---- flake8_pyi-23.6.0.orig/tests/test_pyi_files.py -+++ flake8_pyi-23.6.0/tests/test_pyi_files.py -@@ -42,16 +42,21 @@ def test_pyi_file(path: str) -> None: +--- flake8_pyi-23.11.0.orig/tests/test_pyi_files.py ++++ flake8_pyi-23.11.0/tests/test_pyi_files.py +@@ -43,6 +43,9 @@ def test_pyi_file(path: str) -> None: option = flag.split("=")[0] - assert option != "--ignore", bad_flag_msg + assert option not in {"--ignore", "--select"}, bad_flag_msg(option[2:]) + pythonpath = os.environ.get("PYTHONPATH") + pythonpath = f"PYTHONPATH={pythonpath}:." + - run_results = [ - # Passing a file on command line - subprocess.run( -- ["flake8", "-j0", *flags, path], -+ " ".join([pythonpath, "flake8", "-j0", *flags, path]), -+ shell=True, - env={**os.environ, "PYTHONPATH": "."}, - stdout=subprocess.PIPE, - ), - # Passing "-" as the file, and reading from stdin instead - subprocess.run( -- ["flake8", "-j0", "--stdin-display-name", path, *flags, "-"], -+ " ".join([pythonpath, "flake8", "-j0", "--stdin-display-name", path, *flags, "-"]), -+ shell=True, - env={**os.environ, "PYTHONPATH": "."}, - input=file_contents.encode("utf-8"), - stdout=subprocess.PIPE, + # Silence DeprecationWarnings from our dependencies (pyflakes, flake8-bugbear, etc.) + # + # For DeprecationWarnings coming from flake8-pyi itself, +@@ -51,6 +54,8 @@ def test_pyi_file(path: str) -> None: + # but the test failure report that pytest gives is much easier to read + # if we use `-Wdefault:::pyi` + flake8_invocation = [ ++ "env", ++ pythonpath, + sys.executable, + "-Wignore", + "-Wdefault:::pyi",