pycqa / flake8-pyi Goto Github PK
View Code? Open in Web Editor NEWA plugin for Flake8 that provides specializations for type hinting stub files
License: MIT License
A plugin for Flake8 that provides specializations for type hinting stub files
License: MIT License
In a typeshed PR the other day, a contributor tried to annotate an object with frozenset[list[str]]
. It's obviously impossible to have a frozenset
of lists, as lists aren't hashable, and so I was surprised that none of the CI checks raised an error.
I'm in two minds about whether adding a check for that here would be a good idea:
set
s, frozenset
s or dict
keys. It wouldn't be comprehensive, but it would detect the most common errors.What do we think? Worth doing?
Silly rst
markup errors aren't being caught by the CI (e.g. #72 fixes a bunch that look like they've been there for a while).
We should probably either convert these files to markdown, or add linting script like CPython's rstlint.py
to the CI.
Files that need to be ported are:
Quotes are redundant for type annotations in stubs.
We could add a check for namedtuple syntax. Warn about collections.namedtuple
in stubs, possibly also about using the non-class syntax.
Thank you for this plugin, it looks useful! However, I have two questions regarding its use.
- repo: https://github.com/pycqa/flake8
rev: 4.0.1
hooks:
- id: flake8
files: ^package/|^tests/|setup.py
additional_dependencies: [flake8-builtins==1.5.3,flake8-docstrings==1.6.0,flake8-pyi==20.10.0,flake8-rst-docstrings==0.2.3,pep8-naming==0.12.1]
flake8
from the terminal directly I get a bunch of D100, D101, D102 that the docstrings are missing in all *.pyi
files.Not quite sure yet what’s going on here, but if you can shed some light on this that’d be great!
Type comments are not necessary in stubs. A check that flags them would be useful.
I'm trying to package your module as an rpm package. So I'm using the typical PEP517 based build, install and test cycle used on building packages from non-root account.
python3 -sBm build -w
Here is pytest output:
+ PYTHONPATH=/home/tkloczko/rpmbuild/BUILDROOT/python-flake8-pyi-20.10.0-2.fc35.x86_64/usr/lib64/python3.8/site-packages:/home/tkloczko/rpmbuild/BUILDROOT/python-flake8-pyi-20.10.0-2.fc35.x86_64/usr/lib/python3.8/site-packages
+ /usr/bin/pytest -ra
=========================================================================== test session starts ============================================================================
platform linux -- Python 3.8.12, pytest-6.2.5, py-1.11.0, pluggy-0.13.1
benchmark: 3.4.1 (defaults: timer=time.perf_counter disable_gc=False min_rounds=5 min_time=0.000005 max_time=1.0 calibration_precision=10 warmup=False warmup_iterations=100000)
Using --randomly-seed=3873507879
rootdir: /home/tkloczko/rpmbuild/BUILD/flake8-pyi-20.10.0
plugins: mock-3.6.1, cov-2.12.1, anyio-3.3.4, flaky-3.7.0, console-scripts-1.2.0, asyncio-0.16.0, freezegun-0.4.2, flake8-1.0.7, rerunfailures-9.1.1, yagot-0.5.0, forked-1.4.0, ordering-0.6, xdist-2.5.0, Faker-10.0.0, benchmark-3.4.1, pyfakefs-4.5.3, datadir-1.3.1, regressions-2.2.0, timeout-2.0.2, randomly-3.10.3, perf-0.10.1, trio-0.7.0, requests-mock-1.9.3, hypothesis-6.31.5, easy-server-0.8.0
collected 15 items
tests/test_pyi.py .....F.......F. [100%]
================================================================================= FAILURES =================================================================================
______________________________________________________________________ PyiTestCase.test_function_def _______________________________________________________________________
self = <tests.test_pyi.PyiTestCase testMethod=test_function_def>
def test_function_def(self) -> None:
stdout_lines = (
'5:5: Y009 Empty body should contain "...", not "pass"',
'19:5: Y010 Function body must contain only "..."',
'23:5: Y010 Function body must contain only "..."',
)
> self.checkFileOutput("emptyfunctions.pyi", stdout_lines=stdout_lines)
tests/test_pyi.py:182:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
tests/test_pyi.py:83: in checkFileOutput
check_output(
tests/test_pyi.py:78: in check_output
self.assertMultiLineEqual(expected, actual)
E AssertionError: 'empt[85 chars]pyi:19:5: Y010 Function body must contain only[71 chars]..."' != 'empt[85 chars]pyi:10:1: B903 Data class should either be imm[585 chars]..."'
E emptyfunctions.pyi:5:5: Y009 Empty body should contain "...", not "pass"
E + emptyfunctions.pyi:10:1: B903 Data class should either be immutable or use __slots__ to save memory. Use collections.namedtuple to generate an immutable class, or enumerate the attributes in a __slot__ declaration in the class to leave attributes mutable.
E + emptyfunctions.pyi:14:1: B903 Data class should either be immutable or use __slots__ to save memory. Use collections.namedtuple to generate an immutable class, or enumerate the attributes in a __slot__ declaration in the class to leave attributes mutable.
E emptyfunctions.pyi:19:5: Y010 Function body must contain only "..."
E emptyfunctions.pyi:23:5: Y010 Function body must contain only "..."
_______________________________________________________________________ PyiTestCase.test_empty_init ________________________________________________________________________
self = <tests.test_pyi.PyiTestCase testMethod=test_empty_init>
def test_empty_init(self) -> None:
# should have no output if it's not explicitly selected
> self.checkFileOutput("emptyinit.pyi", stdout_lines=())
tests/test_pyi.py:186:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
tests/test_pyi.py:83: in checkFileOutput
check_output(
tests/test_pyi.py:78: in check_output
self.assertMultiLineEqual(expected, actual)
E AssertionError: '' != 'emptyinit.pyi:1:1: B903 Data class should[204 chars]ble.'
E + emptyinit.pyi:1:1: B903 Data class should either be immutable or use __slots__ to save memory. Use collections.namedtuple to generate an immutable class, or enumerate the attributes in a __slot__ declaration in the class to leave attributes mutable.
========================================================================= short test summary info ==========================================================================
FAILED tests/test_pyi.py::PyiTestCase::test_function_def - AssertionError: 'empt[85 chars]pyi:19:5: Y010 Function body must contain only[71 chars]..."' != 'empt[85 chars...
FAILED tests/test_pyi.py::PyiTestCase::test_empty_init - AssertionError: '' != 'emptyinit.pyi:1:1: B903 Data class should[204 chars]ble.'
====================================================================== 2 failed, 13 passed in 14.35s =======================================================================
error: Bad exit status from /var/tmp/rpm-tmp.mkEK9d (%check)
With literal types, the actual default value becomes relevant to type checkers in some cases (because in effect, two different values are different types).
I believe Y011 should be updated to accept a non-ellipsis default value that matches a Literal type in the annotation of that parameter.
Class-based syntax cannot be used if a TypedDict
has fields that are Python keywords, but should be used in all other circumstances. The keyword
module could be used to ascertain whether a field is a Python keyword or not.
In order to make releases painless and quick, we should set up something that automatically deploys to PyPI on a tag. That way, you can just use the GitHub release flow to make a release.
I have this setup over on typeshed-client at https://github.com/JelleZijlstra/typeshed_client/blob/master/.github/workflows/publish.yml, so we should be able to port that over.
I'd like to get rid of the disabled-by-default Y090, Y091, and Y092. This will require a couple of different changes both in typeshed and here, so I'm opening this issue to track them all.
Y090 and Y091
These are essentially special cases of Y010 (don't put stuff in a function body) that we disabled for compatibility years ago. There are no remaining triggers for these rules in typeshed, so I propose that we get rid of the separate codes and make Y010 trigger on everything you may put in a function body.
Y092 triggers on a lot of existing typeshed code:
stdlib/typing_extensions.pyi:43:26: Y092 Top-level attribute must not have a default value
stdlib/typing_extensions.pyi:44:23: Y092 Top-level attribute must not have a default value
stdlib/typing_extensions.pyi:45:22: Y092 Top-level attribute must not have a default value
stdlib/typing_extensions.pyi:46:26: Y092 Top-level attribute must not have a default value
stdlib/typing_extensions.pyi:47:29: Y092 Top-level attribute must not have a default value
stdlib/typing_extensions.pyi:51:25: Y092 Top-level attribute must not have a default value
stdlib/typing_extensions.pyi:78:21: Y092 Top-level attribute must not have a default value
stdlib/typing_extensions.pyi:92:27: Y092 Top-level attribute must not have a default value
stdlib/typing_extensions.pyi:93:24: Y092 Top-level attribute must not have a default value
stdlib/typing_extensions.pyi:128:33: Y092 Top-level attribute must not have a default value
stdlib/typing_extensions.pyi:129:31: Y092 Top-level attribute must not have a default value
stdlib/typing_extensions.pyi:130:31: Y092 Top-level attribute must not have a default value
stdlib/typing.pyi:49:23: Y092 Top-level attribute must not have a default value
stdlib/typing.pyi:50:26: Y092 Top-level attribute must not have a default value
stdlib/typing.pyi:51:23: Y092 Top-level attribute must not have a default value
stdlib/typing.pyi:52:25: Y092 Top-level attribute must not have a default value
stdlib/typing.pyi:54:26: Y092 Top-level attribute must not have a default value
stdlib/typing.pyi:55:26: Y092 Top-level attribute must not have a default value
stdlib/typing.pyi:56:22: Y092 Top-level attribute must not have a default value
stdlib/typing.pyi:57:26: Y092 Top-level attribute must not have a default value
stdlib/typing.pyi:58:26: Y092 Top-level attribute must not have a default value
stdlib/typing.pyi:60:27: Y092 Top-level attribute must not have a default value
stdlib/typing.pyi:62:29: Y092 Top-level attribute must not have a default value
stdlib/typing.pyi:90:33: Y092 Top-level attribute must not have a default value
stdlib/typing.pyi:91:31: Y092 Top-level attribute must not have a default value
stdlib/typing.pyi:92:31: Y092 Top-level attribute must not have a default value
stdlib/typing.pyi:136:31: Y092 Top-level attribute must not have a default value
stdlib/@python2/asyncore.pyi:9:24: Y092 Top-level attribute must not have a default value
stdlib/@python2/typing_extensions.pyi:37:26: Y092 Top-level attribute must not have a default value
stdlib/@python2/typing_extensions.pyi:38:23: Y092 Top-level attribute must not have a default value
stdlib/@python2/typing_extensions.pyi:42:25: Y092 Top-level attribute must not have a default value
stdlib/@python2/typing_extensions.pyi:62:21: Y092 Top-level attribute must not have a default value
stdlib/@python2/typing_extensions.pyi:73:27: Y092 Top-level attribute must not have a default value
stdlib/@python2/typing_extensions.pyi:74:24: Y092 Top-level attribute must not have a default value
stdlib/@python2/typing_extensions.pyi:103:29: Y092 Top-level attribute must not have a default value
stdlib/@python2/typing_extensions.pyi:104:27: Y092 Top-level attribute must not have a default value
stdlib/@python2/typing_extensions.pyi:105:27: Y092 Top-level attribute must not have a default value
stdlib/@python2/typing.pyi:25:23: Y092 Top-level attribute must not have a default value
stdlib/@python2/typing.pyi:26:26: Y092 Top-level attribute must not have a default value
stdlib/@python2/typing.pyi:27:23: Y092 Top-level attribute must not have a default value
stdlib/@python2/typing.pyi:28:25: Y092 Top-level attribute must not have a default value
stdlib/@python2/typing.pyi:29:26: Y092 Top-level attribute must not have a default value
stdlib/@python2/typing.pyi:30:26: Y092 Top-level attribute must not have a default value
stdlib/@python2/typing.pyi:31:22: Y092 Top-level attribute must not have a default value
stdlib/@python2/typing.pyi:32:26: Y092 Top-level attribute must not have a default value
stdlib/@python2/typing.pyi:33:23: Y092 Top-level attribute must not have a default value
stdlib/@python2/typing.pyi:39:25: Y092 Top-level attribute must not have a default value
stdlib/@python2/typing.pyi:41:21: Y092 Top-level attribute must not have a default value
stdlib/@python2/bdb.pyi:8:38: Y092 Top-level attribute must not have a default value
stdlib/@python2/signal.pyi:4:16: Y092 Top-level attribute must not have a default value
stdlib/@python2/signal.pyi:5:16: Y092 Top-level attribute must not have a default value
stdlib/@python2/signal.pyi:7:20: Y092 Top-level attribute must not have a default value
stdlib/@python2/signal.pyi:8:23: Y092 Top-level attribute must not have a default value
stdlib/@python2/signal.pyi:9:20: Y092 Top-level attribute must not have a default value
stdlib/@python2/signal.pyi:11:13: Y092 Top-level attribute must not have a default value
stdlib/@python2/signal.pyi:13:16: Y092 Top-level attribute must not have a default value
stdlib/@python2/signal.pyi:14:16: Y092 Top-level attribute must not have a default value
stdlib/@python2/signal.pyi:15:17: Y092 Top-level attribute must not have a default value
stdlib/@python2/signal.pyi:16:15: Y092 Top-level attribute must not have a default value
stdlib/@python2/signal.pyi:17:16: Y092 Top-level attribute must not have a default value
stdlib/@python2/signal.pyi:18:15: Y092 Top-level attribute must not have a default value
stdlib/@python2/signal.pyi:19:16: Y092 Top-level attribute must not have a default value
stdlib/@python2/signal.pyi:20:15: Y092 Top-level attribute must not have a default value
stdlib/@python2/signal.pyi:21:15: Y092 Top-level attribute must not have a default value
stdlib/@python2/signal.pyi:22:15: Y092 Top-level attribute must not have a default value
stdlib/@python2/signal.pyi:23:15: Y092 Top-level attribute must not have a default value
stdlib/@python2/signal.pyi:24:16: Y092 Top-level attribute must not have a default value
stdlib/@python2/signal.pyi:25:15: Y092 Top-level attribute must not have a default value
stdlib/@python2/signal.pyi:26:14: Y092 Top-level attribute must not have a default value
stdlib/@python2/signal.pyi:27:15: Y092 Top-level attribute must not have a default value
stdlib/@python2/signal.pyi:28:16: Y092 Top-level attribute must not have a default value
stdlib/@python2/signal.pyi:29:16: Y092 Top-level attribute must not have a default value
stdlib/@python2/signal.pyi:30:16: Y092 Top-level attribute must not have a default value
stdlib/@python2/signal.pyi:31:16: Y092 Top-level attribute must not have a default value
stdlib/@python2/signal.pyi:32:15: Y092 Top-level attribute must not have a default value
stdlib/@python2/signal.pyi:33:16: Y092 Top-level attribute must not have a default value
stdlib/@python2/signal.pyi:34:17: Y092 Top-level attribute must not have a default value
stdlib/@python2/signal.pyi:35:17: Y092 Top-level attribute must not have a default value
stdlib/@python2/signal.pyi:36:16: Y092 Top-level attribute must not have a default value
stdlib/@python2/signal.pyi:37:16: Y092 Top-level attribute must not have a default value
stdlib/@python2/signal.pyi:38:15: Y092 Top-level attribute must not have a default value
stdlib/@python2/signal.pyi:39:16: Y092 Top-level attribute must not have a default value
stdlib/@python2/signal.pyi:40:16: Y092 Top-level attribute must not have a default value
stdlib/@python2/signal.pyi:41:16: Y092 Top-level attribute must not have a default value
stdlib/@python2/signal.pyi:42:16: Y092 Top-level attribute must not have a default value
stdlib/@python2/signal.pyi:43:16: Y092 Top-level attribute must not have a default value
stdlib/@python2/signal.pyi:44:15: Y092 Top-level attribute must not have a default value
stdlib/@python2/signal.pyi:45:16: Y092 Top-level attribute must not have a default value
stdlib/@python2/signal.pyi:46:16: Y092 Top-level attribute must not have a default value
stdlib/@python2/signal.pyi:47:18: Y092 Top-level attribute must not have a default value
stdlib/@python2/signal.pyi:48:17: Y092 Top-level attribute must not have a default value
stdlib/@python2/signal.pyi:49:16: Y092 Top-level attribute must not have a default value
stdlib/@python2/signal.pyi:50:16: Y092 Top-level attribute must not have a default value
stdlib/@python2/signal.pyi:53:21: Y092 Top-level attribute must not have a default value
stdlib/@python2/signal.pyi:54:25: Y092 Top-level attribute must not have a default value
stdlib/@python2/ctypes/__init__.pyi:26:20: Y092 Top-level attribute must not have a default value
stdlib/@python2/ctypes/__init__.pyi:27:19: Y092 Top-level attribute must not have a default value
stdlib/@python2/ctypes/__init__.pyi:28:21: Y092 Top-level attribute must not have a default value
stdlib/@python2/ctypes/__init__.pyi:54:29: Y092 Top-level attribute must not have a default value
stdlib/@python2/ctypes/__init__.pyi:56:37: Y092 Top-level attribute must not have a default value
stdlib/@python2/ctypes/__init__.pyi:57:37: Y092 Top-level attribute must not have a default value
stdlib/@python2/ctypes/__init__.pyi:58:31: Y092 Top-level attribute must not have a default value
stdlib/@python2/ctypes/__init__.pyi:59:20: Y092 Top-level attribute must not have a default value
stdlib/@python2/dbm/ndbm.pyi:10:16: Y092 Top-level attribute must not have a default value
stubs/paramiko/paramiko/pkey.pyi:6:29: Y092 Top-level attribute must not have a default value
stubs/pyaudio/pyaudio.pyi:4:25: Y092 Top-level attribute must not have a default value
stubs/pyaudio/pyaudio.pyi:5:23: Y092 Top-level attribute must not have a default value
stubs/pyaudio/pyaudio.pyi:6:23: Y092 Top-level attribute must not have a default value
stubs/pyaudio/pyaudio.pyi:7:23: Y092 Top-level attribute must not have a default value
stubs/pyaudio/pyaudio.pyi:8:22: Y092 Top-level attribute must not have a default value
stubs/pyaudio/pyaudio.pyi:9:23: Y092 Top-level attribute must not have a default value
stubs/pyaudio/pyaudio.pyi:10:30: Y092 Top-level attribute must not have a default value
stubs/pyaudio/pyaudio.pyi:12:31: Y092 Top-level attribute must not have a default value
stubs/pyaudio/pyaudio.pyi:13:29: Y092 Top-level attribute must not have a default value
stubs/pyaudio/pyaudio.pyi:14:21: Y092 Top-level attribute must not have a default value
stubs/pyaudio/pyaudio.pyi:15:22: Y092 Top-level attribute must not have a default value
stubs/pyaudio/pyaudio.pyi:16:30: Y092 Top-level attribute must not have a default value
stubs/pyaudio/pyaudio.pyi:17:27: Y092 Top-level attribute must not have a default value
stubs/pyaudio/pyaudio.pyi:18:21: Y092 Top-level attribute must not have a default value
stubs/pyaudio/pyaudio.pyi:19:22: Y092 Top-level attribute must not have a default value
stubs/pyaudio/pyaudio.pyi:20:20: Y092 Top-level attribute must not have a default value
stubs/pyaudio/pyaudio.pyi:21:22: Y092 Top-level attribute must not have a default value
stubs/pyaudio/pyaudio.pyi:22:23: Y092 Top-level attribute must not have a default value
stubs/pyaudio/pyaudio.pyi:23:22: Y092 Top-level attribute must not have a default value
stubs/pyaudio/pyaudio.pyi:24:24: Y092 Top-level attribute must not have a default value
stubs/pyaudio/pyaudio.pyi:25:26: Y092 Top-level attribute must not have a default value
stubs/pyaudio/pyaudio.pyi:27:25: Y092 Top-level attribute must not have a default value
stubs/pyaudio/pyaudio.pyi:28:32: Y092 Top-level attribute must not have a default value
stubs/pyaudio/pyaudio.pyi:29:40: Y092 Top-level attribute must not have a default value
stubs/pyaudio/pyaudio.pyi:30:37: Y092 Top-level attribute must not have a default value
stubs/pyaudio/pyaudio.pyi:31:35: Y092 Top-level attribute must not have a default value
stubs/pyaudio/pyaudio.pyi:32:31: Y092 Top-level attribute must not have a default value
stubs/pyaudio/pyaudio.pyi:33:29: Y092 Top-level attribute must not have a default value
stubs/pyaudio/pyaudio.pyi:34:42: Y092 Top-level attribute must not have a default value
stubs/pyaudio/pyaudio.pyi:35:40: Y092 Top-level attribute must not have a default value
stubs/pyaudio/pyaudio.pyi:36:36: Y092 Top-level attribute must not have a default value
stubs/pyaudio/pyaudio.pyi:37:30: Y092 Top-level attribute must not have a default value
stubs/pyaudio/pyaudio.pyi:38:32: Y092 Top-level attribute must not have a default value
stubs/pyaudio/pyaudio.pyi:39:30: Y092 Top-level attribute must not have a default value
stubs/pyaudio/pyaudio.pyi:40:30: Y092 Top-level attribute must not have a default value
stubs/pyaudio/pyaudio.pyi:41:26: Y092 Top-level attribute must not have a default value
stubs/pyaudio/pyaudio.pyi:42:31: Y092 Top-level attribute must not have a default value
stubs/pyaudio/pyaudio.pyi:43:35: Y092 Top-level attribute must not have a default value
stubs/pyaudio/pyaudio.pyi:44:55: Y092 Top-level attribute must not have a default value
stubs/pyaudio/pyaudio.pyi:45:33: Y092 Top-level attribute must not have a default value
stubs/pyaudio/pyaudio.pyi:46:36: Y092 Top-level attribute must not have a default value
stubs/pyaudio/pyaudio.pyi:47:33: Y092 Top-level attribute must not have a default value
stubs/pyaudio/pyaudio.pyi:48:35: Y092 Top-level attribute must not have a default value
stubs/pyaudio/pyaudio.pyi:49:33: Y092 Top-level attribute must not have a default value
stubs/pyaudio/pyaudio.pyi:50:32: Y092 Top-level attribute must not have a default value
stubs/pyaudio/pyaudio.pyi:51:47: Y092 Top-level attribute must not have a default value
stubs/pyaudio/pyaudio.pyi:52:46: Y092 Top-level attribute must not have a default value
stubs/pyaudio/pyaudio.pyi:53:50: Y092 Top-level attribute must not have a default value
stubs/pyaudio/pyaudio.pyi:54:48: Y092 Top-level attribute must not have a default value
stubs/pyaudio/pyaudio.pyi:55:43: Y092 Top-level attribute must not have a default value
stubs/pyaudio/pyaudio.pyi:57:26: Y092 Top-level attribute must not have a default value
stubs/pyaudio/pyaudio.pyi:58:26: Y092 Top-level attribute must not have a default value
stubs/pyaudio/pyaudio.pyi:59:23: Y092 Top-level attribute must not have a default value
stubs/pyaudio/pyaudio.pyi:61:32: Y092 Top-level attribute must not have a default value
stubs/pyaudio/pyaudio.pyi:62:31: Y092 Top-level attribute must not have a default value
stubs/pyaudio/pyaudio.pyi:63:33: Y092 Top-level attribute must not have a default value
stubs/pyaudio/pyaudio.pyi:64:32: Y092 Top-level attribute must not have a default value
stubs/pyaudio/pyaudio.pyi:65:31: Y092 Top-level attribute must not have a default value
stubs/cryptography/cryptography/x509/oid.pyi:102:43: Y092 Top-level attribute must not have a default value
stubs/cryptography/cryptography/x509/oid.pyi:104:67: Y092 Top-level attribute must not have a default value
stubs/setuptools/pkg_resources/__init__.pyi:41:27: Y092 Top-level attribute must not have a default value
These are mostly of the form constant: type = ...
, and they seem legitimate to me. I'll send a PR to typeshed to get rid of the = ...
. After that we can consider turning it on by default.
The logic around subscripts is getting a bit tangled due to changes in the AST between Python versions. We might want to look at factoring it out, if possible (see https://github.com/PyCQA/flake8-pyi/pull/90/files#r785534189)
Typeshed currently has a script that checks a bunch of things that belong here IMO: https://github.com/python/typeshed/blob/af8e37d2736d3a6fbed8f5dfad71580dd417c612/tests/check_new_syntax.py
typing.Counter
--> collections.Counter
etc (related: #46)List --> list
etctyping.(Async)ContextManager
--> contextlib.Abstract(Async)ContextManager
typing_extensions.ClassVar
--> typing.ClassVar
etcUnion[str, int]
--> str | int
(#45)from collections.abc import Set
, because it's confusingly different from the built-in set
sys.version_info
checks so that the most recent version is firstobject
in Python 3-only stubs.typing.Text
in Python 3-only stubs.We should probably create one error code per Python version, so that it's easy to e.g. enable errors for not using features new in Python 3.8+. Another option would be to make the errors depend on sys.version_info
(maybe too clever, would need good documentation).
In stubs you can always write def f() -> X: ...
instead of def f() -> 'X': ...
. We could detect this and lint against it.
This would be straightforward to implement in flake8-pyi but might require a fair amount of migration in typeshed.
It would be useful to have a check for top-level attributes that have a PEP 526 variable annotation and a default value:
# ok
foo: int
foo = ... # type: int
# not ok
foo: int = 0
foo: int = ...
foo = 0 # type: int
In #109, I wanted to write a test case that would only run on Python 3.8+. I removed it from the PR, because it wasn't especially important; but it might be nice to figure out a way to do that at some point.
We currently have a very specific test for chained comparisons on lines 506-508:
Lines 506 to 508 in 34c17bd
However, we then give a generic error message:
"Y002 If test must be a simple comparison against sys.platform or sys.version_info"
If we split this off into its own error code, we could give a much more specific error message:
YXXX Chained comparisons are not supported by type-checkers
Thoughts?
This was previously Y0092, and it was removed because it had problems: #88
Now that we've added Y018, it should be fairly easy to add a similar check to detect unused type aliases.
Attempting to run flake8-pyi with flake8 3.8 (which hasn't been released yet) raises an AttributeError due to the use of optparse
instead of argparse
.
$ pip install flake8==3.8.0a2 flake8-pyi
$ flake8
...
File ".../lib/python3.8/site-packages/pyi.py", line 359, in add_options
option.to_optparse().default = option.default
File ".../lib/python3.8/site-packages/flake8/options/manager.py", line 332, in to_optparse
raise AttributeError("to_optparse: flake8 now uses argparse")
AttributeError: to_optparse: flake8 now uses argparse
Currently there is check Y011 'Default values for typed arguments must be "..."'. I'd like to have a stronger version of that that warns about any default argument that is not ...
. These are often easily convertible to the preferred ellipsis form. (E.g. foo=0
-> foo: int = ...
')
I can provide a pull request if desired.
Black reformats pyi
files.
The format is not compatible with flak8-pyi
.
Is there a way to establish a friendship between these very useful tools?
I have no configuration except max-line-length = 88
.
As a workaround, I've disabled the plugin for yarl
project but love to re-enable it back.
Y010 currently allows raise statements and assignments in __init__
. Since I just stumbled about a few raise statements in typeshed, I'd like a tighter version of this check that disallows anything but ...
.
jreese@jreese-mbp ~/scratch » python3.8 -m venv flake8-venv
jreese@jreese-mbp ~/scratch » source flake8-venv/bin/activate
(flake8-venv) jreese@jreese-mbp ~/scratch » pip install flake8==3.8.1 flake8-pyi==19.3.0
Collecting flake8==3.8.1
Using cached https://files.pythonhosted.org/packages/bf/47/36e51603431e1a5289eb41636199d2c225fcb1ca286e29c02d219c8e6e88/flake8-3.8.1-py2.py3-none-any.whl
Collecting flake8-pyi==19.3.0
Using cached https://files.pythonhosted.org/packages/cb/77/894472548bbf67ff3a4b9d2208b8a7beb58414112ad77b7d96a792a7d7c5/flake8_pyi-19.3.0-py2.py3-none-any.whl
Collecting mccabe<0.7.0,>=0.6.0 (from flake8==3.8.1)
Using cached https://files.pythonhosted.org/packages/87/89/479dc97e18549e21354893e4ee4ef36db1d237534982482c3681ee6e7b57/mccabe-0.6.1-py2.py3-none-any.whl
Collecting pycodestyle<2.7.0,>=2.6.0a1 (from flake8==3.8.1)
Using cached https://files.pythonhosted.org/packages/10/5b/88879fb861ab79aef45c7e199cae3ef7af487b5603dcb363517a50602dd7/pycodestyle-2.6.0-py2.py3-none-any.whl
Collecting pyflakes<2.3.0,>=2.2.0 (from flake8==3.8.1)
Using cached https://files.pythonhosted.org/packages/69/5b/fd01b0c696f2f9a6d2c839883b642493b431f28fa32b29abc465ef675473/pyflakes-2.2.0-py2.py3-none-any.whl
Collecting attrs (from flake8-pyi==19.3.0)
Using cached https://files.pythonhosted.org/packages/a2/db/4313ab3be961f7a763066401fb77f7748373b6094076ae2bda2806988af6/attrs-19.3.0-py2.py3-none-any.whl
Installing collected packages: mccabe, pycodestyle, pyflakes, flake8, attrs, flake8-pyi
Successfully installed attrs-19.3.0 flake8-3.8.1 flake8-pyi-19.3.0 mccabe-0.6.1 pycodestyle-2.6.0 pyflakes-2.2.0
(flake8-venv) jreese@jreese-mbp ~/scratch » flake8 --version
Traceback (most recent call last):
File "/Users/jreese/scratch/flake8-venv/bin/flake8", line 10, in <module>
sys.exit(main())
File "/Users/jreese/scratch/flake8-venv/lib/python3.8/site-packages/flake8/main/cli.py", line 22, in main
app.run(argv)
File "/Users/jreese/scratch/flake8-venv/lib/python3.8/site-packages/flake8/main/application.py", line 360, in run
self._run(argv)
File "/Users/jreese/scratch/flake8-venv/lib/python3.8/site-packages/flake8/main/application.py", line 347, in _run
self.initialize(argv)
File "/Users/jreese/scratch/flake8-venv/lib/python3.8/site-packages/flake8/main/application.py", line 329, in initialize
self.register_plugin_options()
File "/Users/jreese/scratch/flake8-venv/lib/python3.8/site-packages/flake8/main/application.py", line 165, in register_plugin_options
self.check_plugins.register_options(self.option_manager)
File "/Users/jreese/scratch/flake8-venv/lib/python3.8/site-packages/flake8/plugins/manager.py", line 494, in register_options
list(self.manager.map(register_and_enable))
File "/Users/jreese/scratch/flake8-venv/lib/python3.8/site-packages/flake8/plugins/manager.py", line 302, in map
yield func(self.plugins[name], *args, **kwargs)
File "/Users/jreese/scratch/flake8-venv/lib/python3.8/site-packages/flake8/plugins/manager.py", line 490, in register_and_enable
call_register_options(plugin)
File "/Users/jreese/scratch/flake8-venv/lib/python3.8/site-packages/flake8/plugins/manager.py", line 402, in generated_function
return method(optmanager, *args, **kwargs)
File "/Users/jreese/scratch/flake8-venv/lib/python3.8/site-packages/flake8/plugins/manager.py", line 216, in register_options
add_options(optmanager)
File "/Users/jreese/scratch/flake8-venv/lib/python3.8/site-packages/pyi.py", line 359, in add_options
option.to_optparse().default = option.default
File "/Users/jreese/scratch/flake8-venv/lib/python3.8/site-packages/flake8/options/manager.py", line 332, in to_optparse
raise AttributeError("to_optparse: flake8 now uses argparse")
AttributeError: to_optparse: flake8 now uses argparse
When trying to package flake8-pyi-18.3.1 for openSUSE, I get these two tests failing:
[ 3s] + py.test3 -v -k 'not ()' tests
[ 3s] ============================= test session starts ==============================
[ 3s] platform linux -- Python 3.7.1, pytest-3.8.1, py-1.5.4, pluggy-0.7.1 -- /usr/bin/python3
[ 3s] cachedir: .pytest_cache
[ 3s] rootdir: /home/abuild/rpmbuild/BUILD/flake8-pyi-18.3.1, inifile:
[ 3s] collecting ... collected 11 items
[ 3s]
[ 4s] tests/test_pyi.py::PyiTestCase::test_comparisons PASSED [ 9%]
[ 4s] tests/test_pyi.py::PyiTestCase::test_defaults FAILED [ 18%]
[ 5s] tests/test_pyi.py::PyiTestCase::test_empty_init PASSED [ 27%]
[ 5s] tests/test_pyi.py::PyiTestCase::test_function_def FAILED [ 36%]
[ 6s] tests/test_pyi.py::PyiTestCase::test_patched_flake8_clean_del PASSED [ 45%]
[ 6s] tests/test_pyi.py::PyiTestCase::test_patched_flake8_clean_forward_refs PASSED [ 54%]
[ 6s] tests/test_pyi.py::PyiTestCase::test_sys_platform PASSED [ 63%]
[ 7s] tests/test_pyi.py::PyiTestCase::test_sys_versioninfo PASSED [ 72%]
[ 7s] tests/test_pyi.py::PyiTestCase::test_typevar PASSED [ 81%]
[ 7s] tests/test_pyi.py::PyiTestCase::test_vanilla_flake8_not_clean_del PASSED [ 90%]
[ 8s] tests/test_pyi.py::PyiTestCase::test_vanilla_flake8_not_clean_forward_refs PASSED [100%]
[ 8s]
[ 8s] =================================== FAILURES ===================================
[ 8s] __________________________ PyiTestCase.test_defaults ___________________________
[ 8s]
[ 8s] self = <tests.test_pyi.PyiTestCase testMethod=test_defaults>
[ 8s]
[ 8s] def test_defaults(self) -> None:
[ 8s] stdout_lines = (
[ 8s] '3:17: Y011 Default values for typed arguments must be "..."',
[ 8s] '7:20: Y011 Default values for typed arguments must be "..."',
[ 8s] '13:20: Y011 Default values for typed arguments must be "..."',
[ 8s] )
[ 8s] > self.checkFileOutput('defaults.pyi', stdout_lines=stdout_lines)
[ 8s]
[ 8s] tests/test_pyi.py:141:
[ 8s] _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
[ 8s] tests/test_pyi.py:46: in checkFileOutput
[ 8s] self.assertMultiLineEqual(expected, actual)
[ 8s] E AssertionError: 'defaults.pyi:3:17: Y011 Default values for typed argument[160 chars]..."' != 'defaults.pyi:3:1: E302 expected 2 blank lines, found 0\nd[555 chars]nd 0'
[ 8s] E + defaults.pyi:3:1: E302 expected 2 blank lines, found 0
[ 8s] E defaults.pyi:3:17: Y011 Default values for typed arguments must be "..."
[ 8s] E + defaults.pyi:5:1: E302 expected 2 blank lines, found 0
[ 8s] E + defaults.pyi:7:1: E302 expected 2 blank lines, found 0
[ 8s] E defaults.pyi:7:20: Y011 Default values for typed arguments must be "..."
[ 8s] E + defaults.pyi:9:1: E302 expected 2 blank lines, found 0
[ 8s] E + defaults.pyi:11:1: E302 expected 2 blank lines, found 0
[ 8s] E + defaults.pyi:13:1: E302 expected 2 blank lines, found 0
[ 8s] E - defaults.pyi:13:20: Y011 Default values for typed arguments must be "..."+ defaults.pyi:13:20: Y011 Default values for typed arguments must be "..."
[ 8s] E ? +
[ 8s] E + defaults.pyi:15:1: E302 expected 2 blank lines, found 0
[ 8s] ________________________ PyiTestCase.test_function_def _________________________
[ 8s]
[ 8s] self = <tests.test_pyi.PyiTestCase testMethod=test_function_def>
[ 8s]
[ 8s] def test_function_def(self) -> None:
[ 8s] stdout_lines = (
[ 8s] '5:5: Y009 Empty body should contain "...", not "pass"',
[ 8s] '19:5: Y010 Function body must contain only "..."',
[ 8s] '23:5: Y010 Function body must contain only "..."',
[ 8s] )
[ 8s] > self.checkFileOutput('emptyfunctions.pyi', stdout_lines=stdout_lines)
[ 8s]
[ 8s] tests/test_pyi.py:124:
[ 8s] _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
[ 8s] tests/test_pyi.py:46: in checkFileOutput
[ 8s] self.assertMultiLineEqual(expected, actual)
[ 8s] E AssertionError: 'emptyfunctions.pyi:5:5: Y009 Empty body should contain "..."[146 chars]..."' != 'emptyfunctions.pyi:4:1: E302 expected 2 blank lines, found 1[522 chars]..."'
[ 8s] E + emptyfunctions.pyi:4:1: E302 expected 2 blank lines, found 1
[ 8s] E emptyfunctions.pyi:5:5: Y009 Empty body should contain "...", not "pass"
[ 8s] E + emptyfunctions.pyi:7:1: E302 expected 2 blank lines, found 1
[ 8s] E + emptyfunctions.pyi:10:1: E302 expected 2 blank lines, found 1
[ 8s] E + emptyfunctions.pyi:14:1: E302 expected 2 blank lines, found 1
[ 8s] E + emptyfunctions.pyi:18:1: E302 expected 2 blank lines, found 1
[ 8s] E emptyfunctions.pyi:19:5: Y010 Function body must contain only "..."
[ 8s] E + emptyfunctions.pyi:21:1: E302 expected 2 blank lines, found 1
[ 8s] E emptyfunctions.pyi:23:5: Y010 Function body must contain only "..."
[ 8s] ====================== 2 failed, 9 passed in 4.55 seconds ======================
Are these issues which have been addressed since 18.3.1 was released? If yes, which patches I should add? Thank you.
Sometimes, __all__
gets a type annotation in stubs. __all__
in stubs has the same semantics as in implementation files, so an annotation like __all__: List[str]
is not very useful.
#50 was merged without squash and merge, probably because the dropdown doesn't have it selected by default, unless this isn't your first time merging and your previous merge was a "Squash and merge".
I suggest disabling the default non-squash and merge button, like in typeshed. I don't seem to have permissions to tweak the settings though.
An ALL_CAPS_NAME
in the global namespace should usually be annotated with Final
or (ideally) Literal
, especially if it's a str
, bytes
or int
value. It should be fairly easy to detect module-level constants that don't comply with this rule.
E701 is the flake8 check for "multiple statements on one line". Switching this off for test files will make our tests more readable, more like typeshed stubs, and more concise.
Humble suggestion: add new code to suggest a replacement of Union[...]
types with the |
chain, e.g. Y093 Use PEP 604 union types
. A simple implementation start could be e.g.
class PyVisitor(ast.NodeVisitor):
...
def visit_FunctionDef(self, node):
if node.returns.value is not None and node.returns.value.id in ("Optional", "Union"):
self.error(node.returns, Y093)
...
def visit_arguments(self, node):
if arg.annotation.value.id in ("Optional", "Union"):
self.error(arg, Y093)
...
Hi, would you mind adding the LICENSE to the sdist.
This can be done using MANIFEST.in
, however I believe in the next release it can also be done by configuring setuptools a little. See pypa/setuptools#1636
It doesn't work:
% python -m flake8 --select=Y091 tests/raise.pyi
tests/raise.pyi:4:9: Y091 Function body must not contain "raise"
tests/raise.pyi:7:9: Y091 Function body must not contain "raise"
% python -m flake8 --extend-select=Y091 tests/raise.pyi
%
The README isn't great at the moment, in my opinion:
I plan some initial steps to resolve (1) and (2):
CHANGELOG.rst
file: #70CONTRIBUTING.rst
file: #69Things still to do:
This came up in python/typeshed#6970.
I have noticed a release for this project hasn't been made in a while. If/when #13 (add .flake8
to manifest file) is merged, can a new release be made available? This would be very helpful as I'm trying to package this project in Guix, however, currently, I'm unable to keep the test suite enabled in the project's current state.
A check that knows the special import handling in stubs. E.g.:
from x import a, b, c as c
x: b
This should complain about a
being unused, but not b
or c
.
I noticed that we have a bunch of checks in visit_FunctionDef
, but we don't do the same checks for async functions.
It has no purpose in Python 3-only stubs.
Affects:
AbstractSet
(as collections.abc.Set
)AsyncContextManager
(as contextlib.AbstractAsyncContextManager
)AsyncGenerator
AsyncIterable
AsyncIterator
Awaitable
ByteString
Callable
ChainMap
(as collections.ChainMap
)Collection
Container
ContextManager
(as contextlib.AbstractContextManager
)Coroutine
Counter
(as collections.Counter
)DefaultDict
(as collections.defaultdict
)Deque
(as collections.Deque
)Generator
Hashable
ItemsView
Iterable
Iterator
KeysView
Mapping
MappingView
Match
(as re.Match
)MutableMapping
MutableSequence
MutableSet
OrderedDict
(as collections.OrderedDict
)Pattern
(as re.Pattern
)Reversible
Sequence
Sized
ValuesView
When run with this file:
https://github.com/markshannon/typeshed/blob/362bedecd9778196e44d4c88acc39313bb38a6ba/stdlib/2/os/path.pyi
I get the following failures:
flake8 stdlib/2/os/path.pyi
stdlib/2/os/path.pyi:28:18: F821 undefined name 'EitherStr'
stdlib/2/os/path.pyi:29:19: F821 undefined name 'EitherStr'
etc.
but with --no-pyi-aware-file-checker
it is OK.
flake8 --no-pyi-aware-file-checker stdlib/2/os/path.pyi
It is not clear to me whether this is a flake8-pyi
or a flake8
issue, but it appears to be the former.
I'd like to make several changes to pyi.py
to improve code readability. These are all quite small changes, but I'll submit them as separate PRs, to make them easier to review:
Error
namedtuple: #65PyiVisitor._in_class
is quite poorly named: it sounds like it's a boolean value from the name, but it's actually an integer that tells you the level of class nesting the visitor is in: #64Additional changes I'd like to see:
I also like the new @attrs.define
API much more than the legacy API. I'd quite like to refactor pyi.py
to use the newer API; however, that would mean we'd have to pin attrs
to >=21.3.0
. Thoughts on that?
We've made a lot of improvements, so I'd like to make a release soon so we can start using them in typeshed.
I can make the actual release, but here's what I'd like to do first:
Let me know if there's something else you'd like to go in first! In the future, we should be able to make releases basically whenever we have something we'd like to go out.
I want all these features in my src (I don't use stubs). Or should I look to pyupgrade for this?
For example foo: Pattern
instead of foo: Pattern[str]
or foo: Pattern[Any]
.
See also python/typeshed#4913.
For example:
def foo(x: _F) -> None: ...
Refs discussion in #101. Ideas include:
scripts
subdirectory with some example codemodding scripts (but with a big red warning that it's untested code, and might have unpredictable consequences).This package can be very helpful for keeping stub files healthy, but development has kind of stalled. For example, typeshed now has some lint-type checks in a custom file (check_new_syntax.py). I'd like to keep such checks in this package in the future, so they can be reused by other repos than typeshed and so we can take advantage of the flake8 ecosystem (e.g., standardized noqa comments).
Here's a few process improvements I'm thinking of:
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.