Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-pyflakes for openSUSE:Factory checked in at 2021-03-21 23:19:16 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-pyflakes (Old) and /work/SRC/openSUSE:Factory/.python-pyflakes.new.2401 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-pyflakes" Sun Mar 21 23:19:16 2021 rev:26 rq:879832 version:2.3.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-pyflakes/python-pyflakes.changes 2020-05-28 09:06:39.183579162 +0200 +++ /work/SRC/openSUSE:Factory/.python-pyflakes.new.2401/python-pyflakes.changes 2021-03-21 23:19:18.812717469 +0100 @@ -1,0 +2,15 @@ +Tue Mar 16 23:13:47 UTC 2021 - John Vandenberg <jay...@gmail.com> + +- Update to v2.3.0 + * Recognize tuple concatenation in ``__all__`` export definitions + * Better support use of annotation-only assignments when using + ``from __future__ import annotations`` + * Recognize special-case typing for ``Annotated`` + * Fix undefined name ``__qualname__`` in class scope + * Recognize special-cased typing for ``TypeVar`` + * Errors for undefined exports in ``__all__`` are shown in a + deterministic order + * Fix false positives in certain typing constructs (TypeVar, + NamedTuple, TypedDict, cast) + +------------------------------------------------------------------- Old: ---- pyflakes-2.2.0.tar.gz New: ---- pyflakes-2.3.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-pyflakes.spec ++++++ --- /var/tmp/diff_new_pack.wE9NZR/_old 2021-03-21 23:19:19.276717629 +0100 +++ /var/tmp/diff_new_pack.wE9NZR/_new 2021-03-21 23:19:19.280717630 +0100 @@ -19,7 +19,7 @@ %{?!python_module:%define python_module() python-%{**} python3-%{**}} %bcond_without test Name: python-pyflakes -Version: 2.2.0 +Version: 2.3.0 Release: 0 Summary: Passive checker of Python programs License: MIT ++++++ pyflakes-2.2.0.tar.gz -> pyflakes-2.3.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyflakes-2.2.0/NEWS.rst new/pyflakes-2.3.0/NEWS.rst --- old/pyflakes-2.2.0/NEWS.rst 2020-04-10 05:49:52.000000000 +0200 +++ new/pyflakes-2.3.0/NEWS.rst 2021-03-14 17:32:52.000000000 +0100 @@ -1,3 +1,15 @@ +2.3.0 (2021-03-14) + +- Recognize tuple concatenation in ``__all__`` export definitions +- Better support use of annotation-only assignments when using + ``from __future__ import annotations`` +- Recognize special-case typing for ``Annotated`` +- Fix undefined name ``__qualname__`` in class scope +- Recognize special-cased typing for ``TypeVar`` +- Errors for undefined exports in ``__all__`` are shown in a deterministic order +- Fix false positives in certain typing constructs (``TypeVar``, + ``NamedTuple``, ``TypedDict``, ``cast``) + 2.2.0 (2020-04-08) - Include column information in error messages diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyflakes-2.2.0/PKG-INFO new/pyflakes-2.3.0/PKG-INFO --- old/pyflakes-2.2.0/PKG-INFO 2020-04-10 05:51:46.000000000 +0200 +++ new/pyflakes-2.3.0/PKG-INFO 2021-03-14 17:33:07.042730300 +0100 @@ -1,6 +1,6 @@ Metadata-Version: 1.2 Name: pyflakes -Version: 2.2.0 +Version: 2.3.0 Summary: passive checker of Python programs Home-page: https://github.com/PyCQA/pyflakes Author: A lot of people @@ -17,7 +17,7 @@ modules with side effects. It's also much faster. It is `available on PyPI <https://pypi.org/project/pyflakes/>`_ - and it supports all active versions of Python: 2.7 and 3.4 to 3.7. + and it supports all active versions of Python: 2.7 and 3.4 to 3.8. @@ -80,13 +80,13 @@ All changes should include tests and pass flake8_. - .. image:: https://api.travis-ci.org/PyCQA/pyflakes.svg?branch=master - :target: https://travis-ci.org/PyCQA/pyflakes - :alt: Build status + .. image:: https://github.com/PyCQA/pyflakes/workflows/Test/badge.svg + :target: https://github.com/PyCQA/pyflakes/actions + :alt: GitHub Actions build status - .. _Pylint: http://www.pylint.org/ + .. _Pylint: https://www.pylint.org/ .. _flake8: https://pypi.org/project/flake8/ - .. _`PEP 8`: http://legacy.python.org/dev/peps/pep-0008/ + .. _`PEP 8`: https://www.python.org/dev/peps/pep-0008/ .. _Pychecker: http://pychecker.sourceforge.net/ .. _`rebase your changes`: https://git-scm.com/book/en/v2/Git-Branching-Rebasing .. _`GitHub pull request`: https://github.com/PyCQA/pyflakes/pulls @@ -109,6 +109,7 @@ Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Topic :: Software Development diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyflakes-2.2.0/README.rst new/pyflakes-2.3.0/README.rst --- old/pyflakes-2.2.0/README.rst 2020-04-10 05:48:16.000000000 +0200 +++ new/pyflakes-2.3.0/README.rst 2021-03-14 17:31:42.000000000 +0100 @@ -9,7 +9,7 @@ modules with side effects. It's also much faster. It is `available on PyPI <https://pypi.org/project/pyflakes/>`_ -and it supports all active versions of Python: 2.7 and 3.4 to 3.7. +and it supports all active versions of Python: 2.7 and 3.4 to 3.8. @@ -72,13 +72,13 @@ All changes should include tests and pass flake8_. -.. image:: https://api.travis-ci.org/PyCQA/pyflakes.svg?branch=master - :target: https://travis-ci.org/PyCQA/pyflakes - :alt: Build status +.. image:: https://github.com/PyCQA/pyflakes/workflows/Test/badge.svg + :target: https://github.com/PyCQA/pyflakes/actions + :alt: GitHub Actions build status -.. _Pylint: http://www.pylint.org/ +.. _Pylint: https://www.pylint.org/ .. _flake8: https://pypi.org/project/flake8/ -.. _`PEP 8`: http://legacy.python.org/dev/peps/pep-0008/ +.. _`PEP 8`: https://www.python.org/dev/peps/pep-0008/ .. _Pychecker: http://pychecker.sourceforge.net/ .. _`rebase your changes`: https://git-scm.com/book/en/v2/Git-Branching-Rebasing .. _`GitHub pull request`: https://github.com/PyCQA/pyflakes/pulls diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyflakes-2.2.0/pyflakes/__init__.py new/pyflakes-2.3.0/pyflakes/__init__.py --- old/pyflakes-2.2.0/pyflakes/__init__.py 2020-04-10 05:50:00.000000000 +0200 +++ new/pyflakes-2.3.0/pyflakes/__init__.py 2021-03-14 17:32:52.000000000 +0100 @@ -1 +1 @@ -__version__ = '2.2.0' +__version__ = '2.3.0' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyflakes-2.2.0/pyflakes/checker.py new/pyflakes-2.3.0/pyflakes/checker.py --- old/pyflakes-2.2.0/pyflakes/checker.py 2020-04-10 05:48:16.000000000 +0200 +++ new/pyflakes-2.3.0/pyflakes/checker.py 2021-03-14 17:31:42.000000000 +0100 @@ -79,6 +79,10 @@ LOOP_TYPES = (ast.While, ast.For) FUNCTION_TYPES = (ast.FunctionDef,) +if PY36_PLUS: + ANNASSIGN_TYPES = (ast.AnnAssign,) +else: + ANNASSIGN_TYPES = () if PY38_PLUS: def _is_singleton(node): # type: (ast.AST) -> bool @@ -124,6 +128,13 @@ return _is_constant(node) and not _is_singleton(node) +def _is_name_or_attr(node, name): # type: (ast.Ast, str) -> bool + return ( + (isinstance(node, ast.Name) and node.id == name) or + (isinstance(node, ast.Attribute) and node.attr == name) + ) + + # https://github.com/python/typed_ast/blob/1.4.0/ast27/Parser/tokenizer.c#L102-L104 TYPE_COMMENT_RE = re.compile(r'^#\s*type:\s*') # https://github.com/python/typed_ast/blob/1.4.0/ast27/Parser/tokenizer.c#L1408-L1413 @@ -528,6 +539,16 @@ """ +class Annotation(Binding): + """ + Represents binding a name to a type without an associated value. + + As long as this name is not assigned a value in another binding, it is considered + undefined for most purposes. One notable exception is using the name as a type + annotation. + """ + + class FunctionDefinition(Definition): pass @@ -542,7 +563,7 @@ can be determined statically, they will be treated as names for export and additional checking applied to them. - The only recognized C{__all__} assignment via list concatenation is in the + The only recognized C{__all__} assignment via list/tuple concatenation is in the following format: __all__ = ['a'] + ['b'] + ['c'] @@ -564,10 +585,10 @@ if isinstance(source.value, (ast.List, ast.Tuple)): _add_to_names(source.value) - # If concatenating lists + # If concatenating lists or tuples elif isinstance(source.value, ast.BinOp): currentValue = source.value - while isinstance(currentValue.right, ast.List): + while isinstance(currentValue.right, (ast.List, ast.Tuple)): left = currentValue.left right = currentValue.right _add_to_names(right) @@ -575,7 +596,7 @@ if isinstance(left, ast.BinOp): currentValue = left # If just two lists are being added - elif isinstance(left, ast.List): + elif isinstance(left, (ast.List, ast.Tuple)): _add_to_names(left) # All lists accounted for - done break @@ -648,6 +669,10 @@ self.col_offset = col_offset +class DetectClassScopedMagic: + names = dir() + + # Globally defined names which are not attributes of the builtins module, or # are only present on some platforms. _MAGIC_GLOBALS = ['__file__', '__builtins__', 'WindowsError'] @@ -732,6 +757,12 @@ ) +class AnnotationState: + NONE = 0 + STRING = 1 + BARE = 2 + + def in_annotation(func): @functools.wraps(func) def in_annotation_func(self, *args, **kwargs): @@ -740,6 +771,14 @@ return in_annotation_func +def in_string_annotation(func): + @functools.wraps(func) + def in_annotation_func(self, *args, **kwargs): + with self._enter_annotation(AnnotationState.STRING): + return func(self, *args, **kwargs) + return in_annotation_func + + def make_tokens(code): # PY3: tokenize.tokenize requires readline of bytes if not isinstance(code, bytes): @@ -826,8 +865,7 @@ nodeDepth = 0 offset = None traceTree = False - _in_annotation = False - _in_typing_literal = False + _in_annotation = AnnotationState.NONE _in_deferred = False builtIns = set(builtin_vars).union(_MAGIC_GLOBALS) @@ -954,7 +992,10 @@ if all_binding: all_names = set(all_binding.names) - undefined = all_names.difference(scope) + undefined = [ + name for name in all_binding.names + if name not in scope + ] else: all_names = undefined = [] @@ -1099,7 +1140,10 @@ # then assume the rebound name is used as a global or within a loop value.used = self.scope[value.name].used - self.scope[value.name] = value + # don't treat annotations as assignments if there is an existing value + # in scope + if value.name not in self.scope or not isinstance(value, Annotation): + self.scope[value.name] = value def _unknown_handler(self, node): # this environment variable configures whether to error on unknown @@ -1146,8 +1190,11 @@ # iteration continue - if (name == 'print' and - isinstance(scope.get(name, None), Builtin)): + binding = scope.get(name, None) + if isinstance(binding, Annotation) and not self._in_postponed_annotation: + continue + + if name == 'print' and isinstance(binding, Builtin): parent = self.getParent(node) if (isinstance(parent, ast.BinOp) and isinstance(parent.op, ast.RShift)): @@ -1194,7 +1241,7 @@ # the special name __path__ is valid only in packages return - if name == '__module__' and isinstance(self.scope, ClassScope): + if name in DetectClassScopedMagic.names and isinstance(self.scope, ClassScope): return # protected with a NameError handler? @@ -1222,7 +1269,9 @@ break parent_stmt = self.getParent(node) - if isinstance(parent_stmt, (FOR_TYPES, ast.comprehension)) or ( + if isinstance(parent_stmt, ANNASSIGN_TYPES) and parent_stmt.value is None: + binding = Annotation(name, node) + elif isinstance(parent_stmt, (FOR_TYPES, ast.comprehension)) or ( parent_stmt != node._pyflakes_parent and not self.isLiteralTupleUnpacking(parent_stmt)): binding = Binding(name, node) @@ -1265,13 +1314,20 @@ self.report(messages.UndefinedName, node, name) @contextlib.contextmanager - def _enter_annotation(self): - orig, self._in_annotation = self._in_annotation, True + def _enter_annotation(self, ann_type=AnnotationState.BARE): + orig, self._in_annotation = self._in_annotation, ann_type try: yield finally: self._in_annotation = orig + @property + def _in_postponed_annotation(self): + return ( + self._in_annotation == AnnotationState.STRING or + self.annotationsFutureEnabled + ) + def _handle_type_comments(self, node): for (lineno, col_offset), comment in self._type_comments.get(node, ()): comment = comment.split(':', 1)[1].strip() @@ -1399,7 +1455,7 @@ self.popScope() self.scopeStack = saved_stack - @in_annotation + @in_string_annotation def handleStringAnnotation(self, s, node, ref_lineno, ref_col_offset, err): try: tree = ast.parse(s) @@ -1457,20 +1513,36 @@ STARRED = NAMECONSTANT = NAMEDEXPR = handleChildren def SUBSCRIPT(self, node): - if ( - ( - isinstance(node.value, ast.Name) and - node.value.id == 'Literal' - ) or ( - isinstance(node.value, ast.Attribute) and - node.value.attr == 'Literal' - ) - ): - orig, self._in_typing_literal = self._in_typing_literal, True - try: + if _is_name_or_attr(node.value, 'Literal'): + with self._enter_annotation(AnnotationState.NONE): self.handleChildren(node) - finally: - self._in_typing_literal = orig + elif _is_name_or_attr(node.value, 'Annotated'): + self.handleNode(node.value, node) + + # py39+ + if isinstance(node.slice, ast.Tuple): + slice_tuple = node.slice + # <py39 + elif ( + isinstance(node.slice, ast.Index) and + isinstance(node.slice.value, ast.Tuple) + ): + slice_tuple = node.slice.value + else: + slice_tuple = None + + # not a multi-arg `Annotated` + if slice_tuple is None or len(slice_tuple.elts) < 2: + self.handleNode(node.slice, node) + else: + # the first argument is the type + self.handleNode(slice_tuple.elts[0], node) + # the rest of the arguments are not + with self._enter_annotation(AnnotationState.NONE): + for arg in slice_tuple.elts[1:]: + self.handleNode(arg, node) + + self.handleNode(node.ctx, node) else: if _is_any_typing_member(node.value, self.scopeStack): with self._enter_annotation(): @@ -1606,15 +1678,79 @@ ): self._handle_string_dot_format(node) + omit = [] + annotated = [] + not_annotated = [] + if ( _is_typing(node.func, 'cast', self.scopeStack) and - len(node.args) >= 1 and - isinstance(node.args[0], ast.Str) + len(node.args) >= 1 ): with self._enter_annotation(): self.handleNode(node.args[0], node) - self.handleChildren(node) + elif _is_typing(node.func, 'TypeVar', self.scopeStack): + + # TypeVar("T", "int", "str") + omit += ["args"] + annotated += [arg for arg in node.args[1:]] + + # TypeVar("T", bound="str") + omit += ["keywords"] + annotated += [k.value for k in node.keywords if k.arg == "bound"] + not_annotated += [ + (k, ["value"] if k.arg == "bound" else None) + for k in node.keywords + ] + + elif _is_typing(node.func, "TypedDict", self.scopeStack): + # TypedDict("a", {"a": int}) + if len(node.args) > 1 and isinstance(node.args[1], ast.Dict): + omit += ["args"] + annotated += node.args[1].values + not_annotated += [ + (arg, ["values"] if i == 1 else None) + for i, arg in enumerate(node.args) + ] + + # TypedDict("a", a=int) + omit += ["keywords"] + annotated += [k.value for k in node.keywords] + not_annotated += [(k, ["value"]) for k in node.keywords] + + elif _is_typing(node.func, "NamedTuple", self.scopeStack): + # NamedTuple("a", [("a", int)]) + if ( + len(node.args) > 1 and + isinstance(node.args[1], (ast.Tuple, ast.List)) and + all(isinstance(x, (ast.Tuple, ast.List)) and + len(x.elts) == 2 for x in node.args[1].elts) + ): + omit += ["args"] + annotated += [elt.elts[1] for elt in node.args[1].elts] + not_annotated += [(elt.elts[0], None) for elt in node.args[1].elts] + not_annotated += [ + (arg, ["elts"] if i == 1 else None) + for i, arg in enumerate(node.args) + ] + not_annotated += [(elt, "elts") for elt in node.args[1].elts] + + # NamedTuple("a", a=int) + omit += ["keywords"] + annotated += [k.value for k in node.keywords] + not_annotated += [(k, ["value"]) for k in node.keywords] + + if omit: + with self._enter_annotation(AnnotationState.NONE): + for na_node, na_omit in not_annotated: + self.handleChildren(na_node, omit=na_omit) + self.handleChildren(node, omit=omit) + + with self._enter_annotation(): + for annotated_node in annotated: + self.handleNode(annotated_node, node) + else: + self.handleChildren(node) def _handle_percent_format(self, node): try: @@ -1728,7 +1864,7 @@ self.handleChildren(node) def STR(self, node): - if self._in_annotation and not self._in_typing_literal: + if self._in_annotation: fn = functools.partial( self.handleStringAnnotation, node.s, @@ -2224,11 +2360,7 @@ self.scope[node.name] = prev_definition def ANNASSIGN(self, node): - if node.value: - # Only bind the *targets* if the assignment has a value. - # Otherwise it's not really ast.Store and shouldn't silence - # UndefinedLocal warnings. - self.handleNode(node.target, node) + self.handleNode(node.target, node) self.handleAnnotation(node.annotation, node) if node.value: # If the assignment has value, handle the *value* now. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyflakes-2.2.0/pyflakes/test/test_api.py new/pyflakes-2.3.0/pyflakes/test/test_api.py --- old/pyflakes-2.2.0/pyflakes/test/test_api.py 2020-04-10 05:48:16.000000000 +0200 +++ new/pyflakes-2.3.0/pyflakes/test/test_api.py 2021-03-14 17:31:42.000000000 +0100 @@ -513,8 +513,10 @@ """ with self.makeTempFile(source) as sourcePath: if ERROR_HAS_LAST_LINE: - if PYPY and sys.version_info >= (3,): + if PYPY: column = 7 + elif sys.version_info >= (3, 9): + column = 21 elif sys.version_info >= (3, 8): column = 9 else: @@ -541,8 +543,10 @@ """ with self.makeTempFile(source) as sourcePath: if ERROR_HAS_LAST_LINE: - if PYPY and sys.version_info >= (3,): + if PYPY: column = 12 + elif sys.version_info >= (3, 9): + column = 17 elif sys.version_info >= (3, 8): column = 14 else: @@ -576,7 +580,9 @@ else: position_end = 1 if PYPY: - column = 6 + column = 5 + elif ver >= (3, 9): + column = 13 else: column = 7 # Column has been "fixed" since 3.2.4 and 3.3.1 @@ -715,13 +721,6 @@ """ Tests of the pyflakes script that actually spawn the script. """ - - # https://bitbucket.org/pypy/pypy/issues/3069/pypy36-on-windows-incorrect-line-separator - if PYPY and sys.version_info >= (3,) and WIN: - LINESEP = '\n' - else: - LINESEP = os.linesep - def setUp(self): self.tempdir = tempfile.mkdtemp() self.tempfilepath = os.path.join(self.tempdir, 'temp') @@ -782,7 +781,7 @@ fd.write("import contraband\n".encode('ascii')) d = self.runPyflakes([self.tempfilepath]) expected = UnusedImport(self.tempfilepath, Node(1), 'contraband') - self.assertEqual(d, ("%s%s" % (expected, self.LINESEP), '', 1)) + self.assertEqual(d, ("%s%s" % (expected, os.linesep), '', 1)) def test_errors_io(self): """ @@ -792,7 +791,7 @@ """ d = self.runPyflakes([self.tempfilepath]) error_msg = '%s: No such file or directory%s' % (self.tempfilepath, - self.LINESEP) + os.linesep) self.assertEqual(d, ('', error_msg, 1)) def test_errors_syntax(self): @@ -805,7 +804,7 @@ fd.write("import".encode('ascii')) d = self.runPyflakes([self.tempfilepath]) error_msg = '{0}:1:{2}: invalid syntax{1}import{1} {3}^{1}'.format( - self.tempfilepath, self.LINESEP, 6 if PYPY else 7, '' if PYPY else ' ') + self.tempfilepath, os.linesep, 6 if PYPY else 7, '' if PYPY else ' ') self.assertEqual(d, ('', error_msg, 1)) def test_readFromStdin(self): @@ -814,15 +813,13 @@ """ d = self.runPyflakes([], stdin='import contraband') expected = UnusedImport('<stdin>', Node(1), 'contraband') - self.assertEqual(d, ("%s%s" % (expected, self.LINESEP), '', 1)) + self.assertEqual(d, ("%s%s" % (expected, os.linesep), '', 1)) class TestMain(IntegrationTests): """ Tests of the pyflakes main function. """ - LINESEP = os.linesep - def runPyflakes(self, paths, stdin=None): try: with SysStreamCapturing(stdin) as capture: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyflakes-2.2.0/pyflakes/test/test_imports.py new/pyflakes-2.3.0/pyflakes/test/test_imports.py --- old/pyflakes-2.2.0/pyflakes/test/test_imports.py 2020-04-10 05:48:16.000000000 +0200 +++ new/pyflakes-2.3.0/pyflakes/test/test_imports.py 2021-03-14 17:31:42.000000000 +0100 @@ -1084,7 +1084,7 @@ __all__ += ['c', 'd'] ''', m.UndefinedExport, m.UndefinedExport) - def test_concatenationAssignment(self): + def test_list_concatenation_assignment(self): """ The C{__all__} variable is defined through list concatenation. """ @@ -1093,6 +1093,15 @@ __all__ = ['a'] + ['b'] + ['c'] ''', m.UndefinedExport, m.UndefinedExport, m.UndefinedExport, m.UnusedImport) + def test_tuple_concatenation_assignment(self): + """ + The C{__all__} variable is defined through tuple concatenation. + """ + self.flakes(''' + import sys + __all__ = ('a',) + ('b',) + ('c',) + ''', m.UndefinedExport, m.UndefinedExport, m.UndefinedExport, m.UnusedImport) + def test_all_with_attributes(self): self.flakes(''' from foo import bar diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyflakes-2.2.0/pyflakes/test/test_type_annotations.py new/pyflakes-2.3.0/pyflakes/test/test_type_annotations.py --- old/pyflakes-2.2.0/pyflakes/test/test_type_annotations.py 2020-04-10 05:48:16.000000000 +0200 +++ new/pyflakes-2.3.0/pyflakes/test/test_type_annotations.py 2021-03-14 17:31:42.000000000 +0100 @@ -263,6 +263,14 @@ class A: pass ''') self.flakes(''' + T: object + def f(t: T): pass + ''', m.UndefinedName) + self.flakes(''' + T: object + def g(t: 'T'): pass + ''') + self.flakes(''' a: 'A B' ''', m.ForwardAnnotationSyntaxError) self.flakes(''' @@ -275,6 +283,26 @@ a: 'a: "A"' ''', m.ForwardAnnotationSyntaxError) + @skipIf(version_info < (3, 6), 'new in Python 3.6') + def test_unused_annotation(self): + # Unused annotations are fine in module and class scope + self.flakes(''' + x: int + class Cls: + y: int + ''') + # TODO: this should print a UnusedVariable message + self.flakes(''' + def f(): + x: int + ''') + # This should only print one UnusedVariable message + self.flakes(''' + def f(): + x: int + x = 3 + ''', m.UnusedVariable) + @skipIf(version_info < (3, 5), 'new in Python 3.5') def test_annotated_async_def(self): self.flakes(''' @@ -300,6 +328,26 @@ class B: pass ''', m.UndefinedName) + self.flakes(''' + from __future__ import annotations + T: object + def f(t: T): pass + def g(t: 'T'): pass + ''') + + @skipIf(version_info < (3, 6), 'new in Python 3.6') + def test_type_annotation_clobbers_all(self): + self.flakes('''\ + from typing import TYPE_CHECKING, List + + from y import z + + if not TYPE_CHECKING: + __all__ = ("z",) + else: + __all__: List[str] + ''') + def test_typeCommentsMarkImportsAsUsed(self): self.flakes(""" from mod import A, B, C, D, E, F, G @@ -489,6 +537,21 @@ maybe_int = tsac('Maybe[int]', 42) """) + def test_quoted_TypeVar_constraints(self): + self.flakes(""" + from typing import TypeVar, Optional + + T = TypeVar('T', 'str', 'Optional[int]', bytes) + """) + + def test_quoted_TypeVar_bound(self): + self.flakes(""" + from typing import TypeVar, Optional, List + + T = TypeVar('T', bound='Optional[int]') + S = TypeVar('S', int, bound='List[int]') + """) + @skipIf(version_info < (3,), 'new in Python 3') def test_literal_type_typing(self): self.flakes(""" @@ -508,6 +571,42 @@ """) @skipIf(version_info < (3,), 'new in Python 3') + def test_annotated_type_typing_missing_forward_type(self): + self.flakes(""" + from typing import Annotated + + def f(x: Annotated['integer']) -> None: + return None + """, m.UndefinedName) + + @skipIf(version_info < (3,), 'new in Python 3') + def test_annotated_type_typing_missing_forward_type_multiple_args(self): + self.flakes(""" + from typing import Annotated + + def f(x: Annotated['integer', 1]) -> None: + return None + """, m.UndefinedName) + + @skipIf(version_info < (3,), 'new in Python 3') + def test_annotated_type_typing_with_string_args(self): + self.flakes(""" + from typing import Annotated + + def f(x: Annotated[int, '> 0']) -> None: + return None + """) + + @skipIf(version_info < (3,), 'new in Python 3') + def test_annotated_type_typing_with_string_args_in_union(self): + self.flakes(""" + from typing import Annotated, Union + + def f(x: Union[Annotated['int', '>0'], 'integer']) -> None: + return None + """, m.UndefinedName) + + @skipIf(version_info < (3,), 'new in Python 3') def test_literal_type_some_other_module(self): """err on the side of false-negatives for types named Literal""" self.flakes(""" @@ -552,3 +651,88 @@ def f() -> Optional['Queue[str]']: return None """) + + def test_idomiatic_typing_guards(self): + # typing.TYPE_CHECKING: python3.5.3+ + self.flakes(""" + from typing import TYPE_CHECKING + + if TYPE_CHECKING: + from t import T + + def f(): # type: () -> T + pass + """) + # False: the old, more-compatible approach + self.flakes(""" + if False: + from t import T + + def f(): # type: () -> T + pass + """) + # some choose to assign a constant and do it that way + self.flakes(""" + MYPY = False + + if MYPY: + from t import T + + def f(): # type: () -> T + pass + """) + + def test_typing_guard_for_protocol(self): + self.flakes(""" + from typing import TYPE_CHECKING + + if TYPE_CHECKING: + from typing import Protocol + else: + Protocol = object + + class C(Protocol): + def f(): # type: () -> int + pass + """) + + def test_typednames_correct_forward_ref(self): + self.flakes(""" + from typing import TypedDict, List, NamedTuple + + List[TypedDict("x", {})] + List[TypedDict("x", x=int)] + List[NamedTuple("a", a=int)] + List[NamedTuple("a", [("a", int)])] + """) + self.flakes(""" + from typing import TypedDict, List, NamedTuple, TypeVar + + List[TypedDict("x", {"x": "Y"})] + List[TypedDict("x", x="Y")] + List[NamedTuple("a", [("a", "Y")])] + List[NamedTuple("a", a="Y")] + List[TypedDict("x", {"x": List["a"]})] + List[TypeVar("A", bound="C")] + List[TypeVar("A", List["C"])] + """, *[m.UndefinedName]*7) + self.flakes(""" + from typing import NamedTuple, TypeVar, cast + from t import A, B, C, D, E + + NamedTuple("A", [("a", A["C"])]) + TypeVar("A", bound=A["B"]) + TypeVar("A", A["D"]) + cast(A["E"], []) + """) + + @skipIf(version_info < (3, 6), 'new in Python 3.6') + def test_namedtypes_classes(self): + self.flakes(""" + from typing import TypedDict, NamedTuple + class X(TypedDict): + y: TypedDict("z", {"zz":int}) + + class Y(NamedTuple): + y: NamedTuple("v", [("vv", int)]) + """) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyflakes-2.2.0/pyflakes/test/test_undefined_names.py new/pyflakes-2.3.0/pyflakes/test/test_undefined_names.py --- old/pyflakes-2.2.0/pyflakes/test/test_undefined_names.py 2020-04-10 05:48:16.000000000 +0200 +++ new/pyflakes-2.3.0/pyflakes/test/test_undefined_names.py 2021-03-14 17:31:42.000000000 +0100 @@ -279,6 +279,23 @@ __module__ ''', m.UndefinedName) + @skipIf(version_info < (3, 3), "Python >= 3.3 only") + def test_magicQualnameInClassScope(self): + """ + Use of the C{__qualname__} magic builtin should not emit an undefined + name warning if used in class scope. + """ + self.flakes('__qualname__', m.UndefinedName) + self.flakes(''' + class Foo: + __qualname__ + ''') + self.flakes(''' + class Foo: + def bar(self): + __qualname__ + ''', m.UndefinedName) + def test_globalImportStar(self): """Can't find undefined names with import *.""" self.flakes('from fu import *; bar', diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyflakes-2.2.0/pyflakes.egg-info/PKG-INFO new/pyflakes-2.3.0/pyflakes.egg-info/PKG-INFO --- old/pyflakes-2.2.0/pyflakes.egg-info/PKG-INFO 2020-04-10 05:51:46.000000000 +0200 +++ new/pyflakes-2.3.0/pyflakes.egg-info/PKG-INFO 2021-03-14 17:33:06.000000000 +0100 @@ -1,6 +1,6 @@ Metadata-Version: 1.2 Name: pyflakes -Version: 2.2.0 +Version: 2.3.0 Summary: passive checker of Python programs Home-page: https://github.com/PyCQA/pyflakes Author: A lot of people @@ -17,7 +17,7 @@ modules with side effects. It's also much faster. It is `available on PyPI <https://pypi.org/project/pyflakes/>`_ - and it supports all active versions of Python: 2.7 and 3.4 to 3.7. + and it supports all active versions of Python: 2.7 and 3.4 to 3.8. @@ -80,13 +80,13 @@ All changes should include tests and pass flake8_. - .. image:: https://api.travis-ci.org/PyCQA/pyflakes.svg?branch=master - :target: https://travis-ci.org/PyCQA/pyflakes - :alt: Build status + .. image:: https://github.com/PyCQA/pyflakes/workflows/Test/badge.svg + :target: https://github.com/PyCQA/pyflakes/actions + :alt: GitHub Actions build status - .. _Pylint: http://www.pylint.org/ + .. _Pylint: https://www.pylint.org/ .. _flake8: https://pypi.org/project/flake8/ - .. _`PEP 8`: http://legacy.python.org/dev/peps/pep-0008/ + .. _`PEP 8`: https://www.python.org/dev/peps/pep-0008/ .. _Pychecker: http://pychecker.sourceforge.net/ .. _`rebase your changes`: https://git-scm.com/book/en/v2/Git-Branching-Rebasing .. _`GitHub pull request`: https://github.com/PyCQA/pyflakes/pulls @@ -109,6 +109,7 @@ Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Topic :: Software Development diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyflakes-2.2.0/setup.py new/pyflakes-2.3.0/setup.py --- old/pyflakes-2.2.0/setup.py 2020-04-10 05:48:16.000000000 +0200 +++ new/pyflakes-2.3.0/setup.py 2021-03-14 17:31:42.000000000 +0100 @@ -58,6 +58,7 @@ "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Software Development",