pyflyby 1.9.3__tar.gz → 1.9.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pyflyby-1.9.3/lib/python/pyflyby.egg-info → pyflyby-1.9.4}/PKG-INFO +6 -3
- {pyflyby-1.9.3 → pyflyby-1.9.4}/README.rst +1 -1
- {pyflyby-1.9.3 → pyflyby-1.9.4}/bin/autopython +1 -1
- {pyflyby-1.9.3 → pyflyby-1.9.4}/bin/create-imports +1 -1
- pyflyby-1.9.4/bin/pyflyby/.DS_Store +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/bin/pyflyby/__init__.py +3 -1
- {pyflyby-1.9.3 → pyflyby-1.9.4}/bin/pyflyby/_autoimp.py +80 -31
- {pyflyby-1.9.3 → pyflyby-1.9.4}/bin/pyflyby/_cmdline.py +2 -2
- {pyflyby-1.9.3 → pyflyby-1.9.4}/bin/pyflyby/_comms.py +3 -1
- {pyflyby-1.9.3/lib/python → pyflyby-1.9.4/bin}/pyflyby/_file.py +5 -3
- {pyflyby-1.9.3/lib/python → pyflyby-1.9.4/bin}/pyflyby/_flags.py +1 -1
- pyflyby-1.9.4/bin/pyflyby/_import_sorting.py +165 -0
- {pyflyby-1.9.3/lib/python → pyflyby-1.9.4/bin}/pyflyby/_importclns.py +28 -14
- {pyflyby-1.9.3/lib/python → pyflyby-1.9.4/bin}/pyflyby/_importdb.py +4 -2
- {pyflyby-1.9.3 → pyflyby-1.9.4}/bin/pyflyby/_imports2s.py +54 -84
- {pyflyby-1.9.3 → pyflyby-1.9.4}/bin/pyflyby/_importstmt.py +52 -11
- {pyflyby-1.9.3 → pyflyby-1.9.4}/bin/pyflyby/_interactive.py +2 -2
- {pyflyby-1.9.3 → pyflyby-1.9.4}/bin/pyflyby/_livepatch.py +4 -1
- {pyflyby-1.9.3/lib/python → pyflyby-1.9.4/bin}/pyflyby/_modules.py +20 -5
- {pyflyby-1.9.3/lib/python → pyflyby-1.9.4/bin}/pyflyby/_parse.py +259 -242
- {pyflyby-1.9.3/lib/python → pyflyby-1.9.4/bin}/pyflyby/_py.py +10 -20
- {pyflyby-1.9.3/lib/python → pyflyby-1.9.4/bin}/pyflyby/_version.py +1 -2
- {pyflyby-1.9.3/lib/python → pyflyby-1.9.4/bin}/pyflyby/autoimport.py +1 -1
- {pyflyby-1.9.3 → pyflyby-1.9.4}/bin/pyflyby-diff +1 -1
- {pyflyby-1.9.3 → pyflyby-1.9.4}/bin/tidy-imports +37 -14
- pyflyby-1.9.4/doc/.DS_Store +0 -0
- pyflyby-1.9.4/lib/.DS_Store +0 -0
- pyflyby-1.9.4/lib/python/.DS_Store +0 -0
- pyflyby-1.9.4/lib/python/pyflyby/.DS_Store +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/lib/python/pyflyby/__init__.py +3 -1
- {pyflyby-1.9.3 → pyflyby-1.9.4}/lib/python/pyflyby/_autoimp.py +80 -31
- {pyflyby-1.9.3 → pyflyby-1.9.4}/lib/python/pyflyby/_cmdline.py +2 -2
- {pyflyby-1.9.3 → pyflyby-1.9.4}/lib/python/pyflyby/_comms.py +3 -1
- {pyflyby-1.9.3/bin → pyflyby-1.9.4/lib/python}/pyflyby/_file.py +5 -3
- {pyflyby-1.9.3/bin → pyflyby-1.9.4/lib/python}/pyflyby/_flags.py +1 -1
- pyflyby-1.9.4/lib/python/pyflyby/_import_sorting.py +165 -0
- {pyflyby-1.9.3/bin → pyflyby-1.9.4/lib/python}/pyflyby/_importclns.py +28 -14
- {pyflyby-1.9.3/bin → pyflyby-1.9.4/lib/python}/pyflyby/_importdb.py +4 -2
- {pyflyby-1.9.3 → pyflyby-1.9.4}/lib/python/pyflyby/_imports2s.py +54 -84
- {pyflyby-1.9.3 → pyflyby-1.9.4}/lib/python/pyflyby/_importstmt.py +52 -11
- {pyflyby-1.9.3 → pyflyby-1.9.4}/lib/python/pyflyby/_interactive.py +2 -2
- {pyflyby-1.9.3 → pyflyby-1.9.4}/lib/python/pyflyby/_livepatch.py +4 -1
- {pyflyby-1.9.3/bin → pyflyby-1.9.4/lib/python}/pyflyby/_modules.py +20 -5
- {pyflyby-1.9.3/bin → pyflyby-1.9.4/lib/python}/pyflyby/_parse.py +259 -242
- {pyflyby-1.9.3/bin → pyflyby-1.9.4/lib/python}/pyflyby/_py.py +10 -20
- {pyflyby-1.9.3/bin → pyflyby-1.9.4/lib/python}/pyflyby/_version.py +1 -2
- {pyflyby-1.9.3/bin → pyflyby-1.9.4/lib/python}/pyflyby/autoimport.py +1 -1
- {pyflyby-1.9.3 → pyflyby-1.9.4/lib/python/pyflyby.egg-info}/PKG-INFO +6 -3
- {pyflyby-1.9.3 → pyflyby-1.9.4}/lib/python/pyflyby.egg-info/SOURCES.txt +7 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/lib/python/pyflyby.egg-info/requires.txt +0 -1
- {pyflyby-1.9.3 → pyflyby-1.9.4}/libexec/pyflyby/colordiff +1 -1
- {pyflyby-1.9.3 → pyflyby-1.9.4}/setup.py +2 -2
- {pyflyby-1.9.3 → pyflyby-1.9.4}/tests/test_autoimp.py +46 -1
- {pyflyby-1.9.3 → pyflyby-1.9.4}/tests/test_imports2s.py +3 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/tests/test_jupyterlab_pyflyby.py +2 -1
- {pyflyby-1.9.3 → pyflyby-1.9.4}/tests/test_parse.py +116 -44
- {pyflyby-1.9.3 → pyflyby-1.9.4}/tests/test_py.py +221 -207
- {pyflyby-1.9.3 → pyflyby-1.9.4}/tests/tests_sorts.py +65 -12
- {pyflyby-1.9.3 → pyflyby-1.9.4}/.pyflyby +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/LICENSE.txt +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/MANIFEST.in +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/bin/autoipython +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/bin/collect-exports +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/bin/collect-imports +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/bin/find-import +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/bin/list-bad-xrefs +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/bin/prune-broken-imports +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/bin/py +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/bin/pyflyby/__main__.py +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/bin/pyflyby/_dbg.py +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/bin/pyflyby/_docxref.py +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/bin/pyflyby/_format.py +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/bin/pyflyby/_idents.py +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/bin/pyflyby/_log.py +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/bin/pyflyby/_util.py +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/bin/pyflyby/importdb.py +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/bin/reformat-imports +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/bin/replace-star-imports +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/bin/transform-imports +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/doc/LICENSE.txt +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/doc/Makefile +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/doc/TODO.txt +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/doc/__init__.py +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/doc/api/api.rst +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/doc/api/autoimp.rst +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/doc/api/cmdline.rst +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/doc/api/comms.rst +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/doc/api/dbg.rst +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/doc/api/file.rst +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/doc/api/flags.rst +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/doc/api/format.rst +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/doc/api/idents.rst +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/doc/api/importclns.rst +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/doc/api/importdb.rst +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/doc/api/imports2s.rst +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/doc/api/importstmt.rst +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/doc/api/interactive.rst +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/doc/api/livepatch.rst +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/doc/api/log.rst +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/doc/api/modules.rst +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/doc/api/parse.rst +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/doc/api/py.rst +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/doc/api/util.rst +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/doc/cli/autoipython.rst +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/doc/cli/cli.rst +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/doc/cli/collect_exports.rst +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/doc/cli/collect_imports.rst +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/doc/cli/find_import.rst +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/doc/cli/prune_broken_imports.rst +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/doc/cli/py.rst +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/doc/cli/pyflyby_diff.rst +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/doc/cli/reformat_imports.rst +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/doc/cli/replace_star_imports.rst +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/doc/cli/tidy_imports.rst +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/doc/cli/transform_imports.rst +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/doc/conf.py +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/doc/index.rst +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/doc/make.bat +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/doc/testing.txt +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/etc/pyflyby/canonical.py +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/etc/pyflyby/common.py +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/etc/pyflyby/forget.py +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/etc/pyflyby/mandatory.py +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/etc/pyflyby/numpy.py +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/etc/pyflyby/std.py +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/lib/emacs/pyflyby.el +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/lib/python/pyflyby/__main__.py +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/lib/python/pyflyby/_dbg.py +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/lib/python/pyflyby/_docxref.py +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/lib/python/pyflyby/_format.py +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/lib/python/pyflyby/_idents.py +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/lib/python/pyflyby/_log.py +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/lib/python/pyflyby/_util.py +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/lib/python/pyflyby/importdb.py +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/lib/python/pyflyby.egg-info/dependency_links.txt +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/lib/python/pyflyby.egg-info/entry_points.txt +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/lib/python/pyflyby.egg-info/top_level.txt +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/libexec/pyflyby/diff-colorize +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/setup.cfg +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/tests/__init__.py +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/tests/test_0testconfig.py +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/tests/test_cmdline.py +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/tests/test_docxref.py +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/tests/test_file.py +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/tests/test_flags.py +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/tests/test_format.py +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/tests/test_idents.py +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/tests/test_importclns.py +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/tests/test_importdb.py +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/tests/test_importstmt.py +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/tests/test_interactive.py +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/tests/test_livepatch.py +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/tests/test_modules.py +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/tests/test_util.py +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/tests/xrefs.py +0 -0
- {pyflyby-1.9.3 → pyflyby-1.9.4}/tox.ini +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: pyflyby
|
|
3
|
-
Version: 1.9.
|
|
3
|
+
Version: 1.9.4
|
|
4
4
|
Summary: pyflyby - Python development productivity tools, in particular automatic import management
|
|
5
5
|
Home-page: https://pypi.org/project/pyflyby/
|
|
6
6
|
Author: Karl Chen
|
|
@@ -16,8 +16,11 @@ Classifier: Topic :: Software Development :: Interpreters
|
|
|
16
16
|
Classifier: Intended Audience :: Developers
|
|
17
17
|
Classifier: License :: OSI Approved :: MIT License
|
|
18
18
|
Classifier: Programming Language :: Python
|
|
19
|
-
Requires-Python: >3.
|
|
19
|
+
Requires-Python: >3.8, <4
|
|
20
20
|
License-File: LICENSE.txt
|
|
21
|
+
Requires-Dist: six
|
|
22
|
+
Requires-Dist: toml
|
|
23
|
+
Requires-Dist: black
|
|
21
24
|
|
|
22
25
|
#########
|
|
23
26
|
Pyflyby
|
|
@@ -29,7 +32,7 @@ License-File: LICENSE.txt
|
|
|
29
32
|
.. image:: https://travis-ci.org/deshaw/pyflyby.png?branch=master
|
|
30
33
|
:target: https://travis-ci.org/deshaw/pyflyby
|
|
31
34
|
|
|
32
|
-
Pyflyby is a set of Python programming productivity tools for Python 3.
|
|
35
|
+
Pyflyby is a set of Python programming productivity tools for Python 3.8+.
|
|
33
36
|
|
|
34
37
|
For command-line interaction:
|
|
35
38
|
* ``py``: command-line multitool
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
.. image:: https://travis-ci.org/deshaw/pyflyby.png?branch=master
|
|
9
9
|
:target: https://travis-ci.org/deshaw/pyflyby
|
|
10
10
|
|
|
11
|
-
Pyflyby is a set of Python programming productivity tools for Python 3.
|
|
11
|
+
Pyflyby is a set of Python programming productivity tools for Python 3.8+.
|
|
12
12
|
|
|
13
13
|
For command-line interaction:
|
|
14
14
|
* ``py``: command-line multitool
|
|
Binary file
|
|
@@ -40,6 +40,8 @@ from pyflyby._dbg import (breakpoint, debug_exception,
|
|
|
40
40
|
enable_signal_handler_breakpoint,
|
|
41
41
|
waitpoint)
|
|
42
42
|
|
|
43
|
+
from typing import Sequence
|
|
44
|
+
|
|
43
45
|
|
|
44
46
|
# Promote the function & classes that we've chosen to expose publicly to be
|
|
45
47
|
# known as pyflyby.Foo instead of pyflyby._module.Foo.
|
|
@@ -51,4 +53,4 @@ del x
|
|
|
51
53
|
|
|
52
54
|
# Discourage "from pyflyby import *".
|
|
53
55
|
# Use the tidy-imports/autoimporter instead!
|
|
54
|
-
__all__ = []
|
|
56
|
+
__all__:Sequence[str] = []
|
|
@@ -6,13 +6,9 @@
|
|
|
6
6
|
|
|
7
7
|
import ast
|
|
8
8
|
import builtins
|
|
9
|
+
from collections.abc import Sequence
|
|
9
10
|
import contextlib
|
|
10
11
|
import copy
|
|
11
|
-
from six import reraise
|
|
12
|
-
import sys
|
|
13
|
-
import types
|
|
14
|
-
|
|
15
|
-
from collections.abc import Sequence
|
|
16
12
|
|
|
17
13
|
from pyflyby._file import FileText, Filename
|
|
18
14
|
from pyflyby._flags import CompilerFlags
|
|
@@ -22,7 +18,13 @@ from pyflyby._importdb import ImportDB
|
|
|
22
18
|
from pyflyby._importstmt import Import
|
|
23
19
|
from pyflyby._log import logger
|
|
24
20
|
from pyflyby._modules import ModuleHandle
|
|
25
|
-
from pyflyby._parse import PythonBlock,
|
|
21
|
+
from pyflyby._parse import (PythonBlock, _is_ast_str,
|
|
22
|
+
infer_compile_mode)
|
|
23
|
+
|
|
24
|
+
from six import reraise
|
|
25
|
+
import sys
|
|
26
|
+
import types
|
|
27
|
+
from typing import Any, Set
|
|
26
28
|
|
|
27
29
|
if sys.version_info >= (3, 12):
|
|
28
30
|
ATTRIBUTE_NAME = "value"
|
|
@@ -34,13 +36,13 @@ if sys.version_info > (3, 11):
|
|
|
34
36
|
else:
|
|
35
37
|
LOAD_SHIFT = 0
|
|
36
38
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
+
|
|
40
|
+
if sys.version_info >= (3, 10):
|
|
41
|
+
from types import NoneType, EllipsisType
|
|
39
42
|
else:
|
|
40
|
-
|
|
43
|
+
NoneType = type(None)
|
|
44
|
+
EllipsisType = type(Ellipsis)
|
|
41
45
|
|
|
42
|
-
NoneType = type(None)
|
|
43
|
-
EllipsisType = type(Ellipsis)
|
|
44
46
|
|
|
45
47
|
class _ClassScope(dict):
|
|
46
48
|
pass
|
|
@@ -419,7 +421,8 @@ class _MissingImportFinder(object):
|
|
|
419
421
|
|
|
420
422
|
def scan_for_import_issues(self, codeblock):
|
|
421
423
|
# See global `scan_for_import_issues`
|
|
422
|
-
|
|
424
|
+
if not isinstance(codeblock, PythonBlock):
|
|
425
|
+
codeblock = PythonBlock(codeblock)
|
|
423
426
|
node = codeblock.ast_node
|
|
424
427
|
self._scan_node(node)
|
|
425
428
|
# Get missing imports now, before handling docstrings. We don't want
|
|
@@ -589,7 +592,10 @@ class _MissingImportFinder(object):
|
|
|
589
592
|
logger.warning("Don't know how to handle __all__ with list elements other than str")
|
|
590
593
|
return
|
|
591
594
|
for e in node.value.elts:
|
|
592
|
-
|
|
595
|
+
if sys.version_info > (3,10):
|
|
596
|
+
self._visit_Load_defered_global(e.value)
|
|
597
|
+
else:
|
|
598
|
+
self._visit_Load_defered_global(e.s)
|
|
593
599
|
|
|
594
600
|
def visit_ClassDef(self, node):
|
|
595
601
|
logger.debug("visit_ClassDef(%r)", node)
|
|
@@ -634,6 +640,10 @@ class _MissingImportFinder(object):
|
|
|
634
640
|
else:
|
|
635
641
|
assert node._fields == ('name', 'args', 'body', 'decorator_list', 'returns', 'type_comment'), node._fields
|
|
636
642
|
with self._NewScopeCtx(include_class_scopes=True):
|
|
643
|
+
# we want `__class__` to only be defined in
|
|
644
|
+
# methods and not class body
|
|
645
|
+
if self._in_class_def:
|
|
646
|
+
self.scopestack[-1]["__class__"] = None # we just need to to be defined
|
|
637
647
|
self.visit(node.decorator_list)
|
|
638
648
|
self.visit(node.args)
|
|
639
649
|
if node.returns:
|
|
@@ -1064,7 +1074,8 @@ def scan_for_import_issues(codeblock, find_unused_imports=True, parse_docstrings
|
|
|
1064
1074
|
|
|
1065
1075
|
"""
|
|
1066
1076
|
logger.debug("scan_for_import_issues()")
|
|
1067
|
-
|
|
1077
|
+
if not isinstance(codeblock, PythonBlock):
|
|
1078
|
+
codeblock = PythonBlock(codeblock)
|
|
1068
1079
|
namespaces = ScopeStack([{}])
|
|
1069
1080
|
finder = _MissingImportFinder(namespaces,
|
|
1070
1081
|
find_unused_imports=find_unused_imports,
|
|
@@ -1152,10 +1163,13 @@ def _find_loads_without_stores_in_code(co, loads_without_stores):
|
|
|
1152
1163
|
"_find_loads_without_stores_in_code(): expected a CodeType; got a %s"
|
|
1153
1164
|
% (type(co).__name__,))
|
|
1154
1165
|
# Initialize local constants for fast access.
|
|
1155
|
-
from opcode import
|
|
1166
|
+
from opcode import EXTENDED_ARG, opmap
|
|
1156
1167
|
|
|
1157
1168
|
LOAD_ATTR = opmap['LOAD_ATTR']
|
|
1169
|
+
# LOAD_METHOD is _supposed_ to be removed in 3.12 but still present in opmap
|
|
1170
|
+
# if sys.version_info < (3, 12):
|
|
1158
1171
|
LOAD_METHOD = opmap['LOAD_METHOD']
|
|
1172
|
+
# endif
|
|
1159
1173
|
LOAD_GLOBAL = opmap['LOAD_GLOBAL']
|
|
1160
1174
|
LOAD_NAME = opmap['LOAD_NAME']
|
|
1161
1175
|
STORE_ATTR = opmap['STORE_ATTR']
|
|
@@ -1255,12 +1269,11 @@ def _find_loads_without_stores_in_code(co, loads_without_stores):
|
|
|
1255
1269
|
earliest_backjump_label = _find_earliest_backjump_label(bytecode)
|
|
1256
1270
|
# Loop through bytecode.
|
|
1257
1271
|
while i < n:
|
|
1258
|
-
|
|
1259
|
-
op = _op(c)
|
|
1272
|
+
op = bytecode[i]
|
|
1260
1273
|
i += 1
|
|
1261
1274
|
if op == CACHE:
|
|
1262
1275
|
continue
|
|
1263
|
-
if op
|
|
1276
|
+
if take_arg(op):
|
|
1264
1277
|
oparg = bytecode[i] | extended_arg
|
|
1265
1278
|
extended_arg = 0
|
|
1266
1279
|
if op == EXTENDED_ARG:
|
|
@@ -1277,9 +1290,40 @@ def _find_loads_without_stores_in_code(co, loads_without_stores):
|
|
|
1277
1290
|
stores.add(fullname)
|
|
1278
1291
|
continue
|
|
1279
1292
|
if op in [LOAD_ATTR, LOAD_METHOD]:
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1293
|
+
if sys.version_info >= (3,12):
|
|
1294
|
+
# from the docs:
|
|
1295
|
+
#
|
|
1296
|
+
# If the low bit of namei is not set, this replaces
|
|
1297
|
+
# STACK[-1] with getattr(STACK[-1], co_names[namei>>1]).
|
|
1298
|
+
#
|
|
1299
|
+
# If the low bit of namei is set, this will attempt to load
|
|
1300
|
+
# a method named co_names[namei>>1] from the STACK[-1]
|
|
1301
|
+
# object. STACK[-1] is popped. This bytecode distinguishes
|
|
1302
|
+
# two cases: if STACK[-1] has a method with the correct
|
|
1303
|
+
# name, the bytecode pushes the unbound method and
|
|
1304
|
+
# STACK[-1]. STACK[-1] will be used as the first argument
|
|
1305
|
+
# (self) by CALL when calling the unbound method. Otherwise,
|
|
1306
|
+
# NULL and the object returned by the attribute lookup are
|
|
1307
|
+
# pushed.
|
|
1308
|
+
#
|
|
1309
|
+
# Changed in version 3.12: If the low bit of namei is set,
|
|
1310
|
+
# then a NULL or self is pushed to the stack before the
|
|
1311
|
+
# attribute or unbound method respectively.
|
|
1312
|
+
#
|
|
1313
|
+
# Implication for Pyflyby
|
|
1314
|
+
#
|
|
1315
|
+
# In our case I think it means we are always looking at
|
|
1316
|
+
# oparg>>1 as the name of the names we need to load,
|
|
1317
|
+
# Though we don't keep track of the stack, and so we may get
|
|
1318
|
+
# wrong results ?
|
|
1319
|
+
#
|
|
1320
|
+
# In any case this seem to match what load_method was doing
|
|
1321
|
+
# before.
|
|
1322
|
+
pending.append(co.co_names[oparg>>1])
|
|
1323
|
+
else:
|
|
1324
|
+
# {LOAD_GLOBAL|LOAD_NAME} {LOAD_ATTR}* so far;
|
|
1325
|
+
# possibly more LOAD_ATTR/STORE_ATTR will follow
|
|
1326
|
+
pending.append(co.co_names[oparg])
|
|
1283
1327
|
continue
|
|
1284
1328
|
# {LOAD_GLOBAL|LOAD_NAME} {LOAD_ATTR}* (and no more
|
|
1285
1329
|
# LOAD_ATTR/STORE_ATTR)
|
|
@@ -1366,10 +1410,14 @@ def _find_loads_without_stores_in_code(co, loads_without_stores):
|
|
|
1366
1410
|
if isinstance(arg, types.CodeType):
|
|
1367
1411
|
_find_loads_without_stores_in_code(arg, loads_without_stores)
|
|
1368
1412
|
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1413
|
+
if sys.version_info >= (3,12):
|
|
1414
|
+
from dis import hasarg
|
|
1415
|
+
def take_arg(op):
|
|
1416
|
+
return op in hasarg
|
|
1417
|
+
else:
|
|
1418
|
+
def take_arg(op):
|
|
1419
|
+
from opcode import HAVE_ARGUMENT
|
|
1420
|
+
return op >= HAVE_ARGUMENT
|
|
1373
1421
|
|
|
1374
1422
|
def _find_earliest_backjump_label(bytecode):
|
|
1375
1423
|
"""
|
|
@@ -1411,19 +1459,20 @@ def _find_earliest_backjump_label(bytecode):
|
|
|
1411
1459
|
The earliest target of a backward jump, as an offset into the bytecode.
|
|
1412
1460
|
"""
|
|
1413
1461
|
# Code based on dis.findlabels().
|
|
1414
|
-
from opcode import
|
|
1462
|
+
from opcode import hasjrel, hasjabs
|
|
1415
1463
|
if not isinstance(bytecode, bytes):
|
|
1416
1464
|
raise TypeError
|
|
1417
1465
|
n = len(bytecode)
|
|
1418
1466
|
earliest_backjump_label = n
|
|
1419
1467
|
i = 0
|
|
1420
1468
|
while i < n:
|
|
1421
|
-
|
|
1422
|
-
op = _op(c)
|
|
1469
|
+
op = bytecode[i]
|
|
1423
1470
|
i += 1
|
|
1424
|
-
if op
|
|
1471
|
+
if not take_arg(op):
|
|
1425
1472
|
continue
|
|
1426
|
-
|
|
1473
|
+
if i+1 >= len(bytecode):
|
|
1474
|
+
break
|
|
1475
|
+
oparg = bytecode[i] + bytecode[i+1]*256
|
|
1427
1476
|
i += 2
|
|
1428
1477
|
label = None
|
|
1429
1478
|
if op in hasjrel:
|
|
@@ -1605,7 +1654,7 @@ def get_known_import(fullname, db=None):
|
|
|
1605
1654
|
return None
|
|
1606
1655
|
|
|
1607
1656
|
|
|
1608
|
-
_IMPORT_FAILED = set()
|
|
1657
|
+
_IMPORT_FAILED:Set[Any] = set()
|
|
1609
1658
|
"""
|
|
1610
1659
|
Set of imports we've already attempted and failed.
|
|
1611
1660
|
"""
|
|
@@ -415,8 +415,8 @@ def process_actions(filenames:List[str], actions, modify_function,
|
|
|
415
415
|
continue
|
|
416
416
|
if errors:
|
|
417
417
|
msg = "\n%s: encountered the following problems:\n" % (sys.argv[0],)
|
|
418
|
-
for
|
|
419
|
-
lines =
|
|
418
|
+
for er in errors:
|
|
419
|
+
lines = er.splitlines()
|
|
420
420
|
msg += " " + lines[0] + '\n'.join(
|
|
421
421
|
(" %s"%line for line in lines[1:]))
|
|
422
422
|
raise SystemExit(msg)
|
|
@@ -9,6 +9,8 @@ from pyflyby._imports2s import (SourceToSourceFileImportsTransformation,
|
|
|
9
9
|
from pyflyby._importstmt import Import
|
|
10
10
|
from pyflyby._log import logger
|
|
11
11
|
|
|
12
|
+
from typing import Dict, Any
|
|
13
|
+
|
|
12
14
|
# These are comm targets that the frontend (lab/notebook) is expected to
|
|
13
15
|
# open. At this point, we handle only missing imports and
|
|
14
16
|
# formatting imports
|
|
@@ -23,7 +25,7 @@ PYFLYBY_END_MSG = "# END AUTO-GENERATED BLOCK\n"
|
|
|
23
25
|
pyflyby_comm_targets= [MISSING_IMPORTS, FORMATTING_IMPORTS, TIDY_IMPORTS]
|
|
24
26
|
|
|
25
27
|
# A map of the comms opened with a given target name.
|
|
26
|
-
comms = {}
|
|
28
|
+
comms:Dict[str, Any] = {}
|
|
27
29
|
|
|
28
30
|
# TODO: Document the expected contract for the different
|
|
29
31
|
# custom comm messages
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
# pyflyby/_file.py.
|
|
2
2
|
# Copyright (C) 2011, 2012, 2013, 2014, 2015, 2018 Karl Chen.
|
|
3
3
|
# License: MIT http://opensource.org/licenses/MIT
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
from __future__ import annotations
|
|
6
5
|
|
|
7
6
|
from functools import total_ordering, cached_property
|
|
8
7
|
import io
|
|
9
8
|
import os
|
|
10
9
|
import re
|
|
11
10
|
import sys
|
|
12
|
-
from typing import Optional, Tuple
|
|
11
|
+
from typing import Optional, Tuple, ClassVar
|
|
13
12
|
|
|
14
13
|
from pyflyby._util import cmp, memoize
|
|
15
14
|
|
|
@@ -30,6 +29,7 @@ class Filename(object):
|
|
|
30
29
|
|
|
31
30
|
"""
|
|
32
31
|
_filename: str
|
|
32
|
+
STDIN: Filename
|
|
33
33
|
|
|
34
34
|
def __new__(cls, arg):
|
|
35
35
|
if isinstance(arg, cls):
|
|
@@ -236,6 +236,8 @@ class FilePos(object):
|
|
|
236
236
|
lineno: int
|
|
237
237
|
colno: int
|
|
238
238
|
|
|
239
|
+
_ONE_ONE: ClassVar[FilePos]
|
|
240
|
+
|
|
239
241
|
def __new__(cls, *args):
|
|
240
242
|
if len(args) == 0:
|
|
241
243
|
return cls._ONE_ONE
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module contain utility functions to sort imports in a Python Block.
|
|
3
|
+
"""
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from collections import Counter
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from itertools import groupby
|
|
9
|
+
from pyflyby._importstmt import ImportStatement, PythonStatement
|
|
10
|
+
from pyflyby._parse import PythonBlock
|
|
11
|
+
from typing import List, Tuple, Union
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# @dataclass
|
|
15
|
+
# class ImportSection:
|
|
16
|
+
# """
|
|
17
|
+
# This represent an Import
|
|
18
|
+
# """
|
|
19
|
+
# sections: List[ImportGroup]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class ImportGroup:
|
|
24
|
+
"""
|
|
25
|
+
Typically at the top of a file, the first import will be part
|
|
26
|
+
of an ImportGroup, and subsequent imports
|
|
27
|
+
will be other import groups which.
|
|
28
|
+
|
|
29
|
+
Import sorting will affect only the imports in the same group,
|
|
30
|
+
as we want import sorting to be indempotent. If import sorting
|
|
31
|
+
were migrating imports between groups, then imports could be
|
|
32
|
+
moved to sections were comments would nto be relevant.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
imports: List[ImportStatement]
|
|
36
|
+
|
|
37
|
+
@classmethod
|
|
38
|
+
def from_statements(cls, statements: List[PythonStatement]) -> ImportGroup:
|
|
39
|
+
return ImportGroup([ImportStatement(s) for s in statements])
|
|
40
|
+
|
|
41
|
+
def sorted(self) -> ImportGroup:
|
|
42
|
+
"""
|
|
43
|
+
return an ImportGroup with import sorted lexicographically.
|
|
44
|
+
"""
|
|
45
|
+
return ImportGroup(sorted(self.imports, key=lambda x: x._cmp()))
|
|
46
|
+
|
|
47
|
+
def sorted_subgroups(self) -> List[Tuple[bool, List[ImportStatement]]]:
|
|
48
|
+
"""
|
|
49
|
+
Return a list of subgroup keyed by module and whether they are the sole import
|
|
50
|
+
from this module.
|
|
51
|
+
|
|
52
|
+
From issue #13, we will want to sort import from the same package together when
|
|
53
|
+
there is more than one import and separat with a blank line
|
|
54
|
+
|
|
55
|
+
We also group all imports from a package that appear only once together.
|
|
56
|
+
|
|
57
|
+
For this we need both to know if the import is from a single import (the boolean).
|
|
58
|
+
|
|
59
|
+
Returns
|
|
60
|
+
-------
|
|
61
|
+
bool:
|
|
62
|
+
wether from a single import
|
|
63
|
+
List[ImportStatement]
|
|
64
|
+
The actual import.
|
|
65
|
+
"""
|
|
66
|
+
c = Counter(imp.module[0] for imp in self.imports)
|
|
67
|
+
return [
|
|
68
|
+
(c[k] > 1, list(v)) for k, v in groupby(self.imports, lambda x: x.module[0])
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def split_import_groups(
|
|
73
|
+
statements: Tuple[PythonStatement],
|
|
74
|
+
) -> List[Union[ImportGroup, PythonStatement]]:
|
|
75
|
+
"""
|
|
76
|
+
Given a list of statements split into import groups.
|
|
77
|
+
|
|
78
|
+
One of the question is how to treat split with comments.
|
|
79
|
+
|
|
80
|
+
- Do blank lines after groups with comments start a new block
|
|
81
|
+
- Does the comment line create a complete new block.
|
|
82
|
+
|
|
83
|
+
In particular because we want this to be indempotent,
|
|
84
|
+
we can't move imports between groups.
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
# these are the import groups we'll
|
|
88
|
+
groups: List[PythonStatement | ImportGroup] = []
|
|
89
|
+
|
|
90
|
+
current_group: List[PythonStatement] = []
|
|
91
|
+
statemt_iterator = iter(statements)
|
|
92
|
+
for statement in statemt_iterator:
|
|
93
|
+
if statement.is_blank and current_group:
|
|
94
|
+
pass
|
|
95
|
+
# currently do nothing with whitespace while in import groups.
|
|
96
|
+
elif statement.is_import:
|
|
97
|
+
# push on top of current_comment.
|
|
98
|
+
current_group.append(statement)
|
|
99
|
+
else:
|
|
100
|
+
if current_group:
|
|
101
|
+
groups.append(ImportGroup.from_statements(current_group))
|
|
102
|
+
current_group = []
|
|
103
|
+
groups.append(statement)
|
|
104
|
+
|
|
105
|
+
# we should break of and populate rest
|
|
106
|
+
# We can't do anything if we encounter any non-import statement,
|
|
107
|
+
# as we do no know if it can be a conditional.
|
|
108
|
+
# technically I guess we coudl find another import block, and reorder these.
|
|
109
|
+
if current_group:
|
|
110
|
+
groups.append(ImportGroup.from_statements(current_group))
|
|
111
|
+
|
|
112
|
+
# this is an iterator, not an iterable, we exaust it to reify the rest.
|
|
113
|
+
|
|
114
|
+
# first group may be empty if the first line is a comment.
|
|
115
|
+
# We filter and sort relevant statements.
|
|
116
|
+
groups = [g for g in groups if groups]
|
|
117
|
+
sorted_groups: List[PythonStatement | ImportGroup] = [
|
|
118
|
+
g.sorted() if isinstance(g, ImportGroup) else g for g in groups
|
|
119
|
+
]
|
|
120
|
+
return sorted_groups
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def regroup(groups: List[ImportGroup | PythonStatement]) -> PythonBlock:
|
|
124
|
+
"""
|
|
125
|
+
given import groups and list of statement, return an Python block with sorted import
|
|
126
|
+
"""
|
|
127
|
+
res: str = ""
|
|
128
|
+
in_single = False
|
|
129
|
+
for group in groups:
|
|
130
|
+
if isinstance(group, ImportGroup):
|
|
131
|
+
# the subgroup here will be responsible for groups reordering.
|
|
132
|
+
for mult, subgroup in group.sorted_subgroups():
|
|
133
|
+
if mult:
|
|
134
|
+
if in_single:
|
|
135
|
+
res += "\n"
|
|
136
|
+
if not res.endswith("\n"):
|
|
137
|
+
res += "\n"
|
|
138
|
+
for x in subgroup:
|
|
139
|
+
res += x.pretty_print(import_column=30).rstrip() + "\n"
|
|
140
|
+
if not res.endswith("\n\n"):
|
|
141
|
+
res += "\n"
|
|
142
|
+
in_single = False
|
|
143
|
+
else:
|
|
144
|
+
assert len(subgroup) == 1
|
|
145
|
+
in_single = True
|
|
146
|
+
for sub in subgroup:
|
|
147
|
+
res += str(sub).rstrip() + "\n"
|
|
148
|
+
|
|
149
|
+
if not res.endswith("\n\n"):
|
|
150
|
+
res += "\n"
|
|
151
|
+
else:
|
|
152
|
+
if in_single and not res.endswith("\n\n"):
|
|
153
|
+
res += "\n"
|
|
154
|
+
in_single = False
|
|
155
|
+
res += str(PythonBlock(group))
|
|
156
|
+
|
|
157
|
+
return PythonBlock.concatenate([PythonBlock(res)])
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def sort_imports(block: PythonBlock) -> PythonBlock:
|
|
161
|
+
assert isinstance(block, PythonBlock)
|
|
162
|
+
# we ignore below that block.statement can be a List[Unknown]
|
|
163
|
+
gs = split_import_groups(block.statements) # type: ignore
|
|
164
|
+
# TODO: math the other fileds like filename....
|
|
165
|
+
return regroup(gs)
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
# Copyright (C) 2011, 2012, 2013, 2014 Karl Chen.
|
|
3
3
|
# License: MIT http://opensource.org/licenses/MIT
|
|
4
4
|
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
5
7
|
|
|
6
8
|
|
|
7
9
|
from collections import defaultdict
|
|
@@ -16,7 +18,8 @@ from pyflyby._parse import PythonBlock
|
|
|
16
18
|
from pyflyby._util import (cached_attribute, cmp, partition,
|
|
17
19
|
stable_unique)
|
|
18
20
|
|
|
19
|
-
from
|
|
21
|
+
from typing import (ClassVar, Dict, FrozenSet, List,
|
|
22
|
+
Sequence, Union)
|
|
20
23
|
|
|
21
24
|
|
|
22
25
|
class NoSuchImportError(ValueError):
|
|
@@ -46,6 +49,9 @@ class ImportSet(object):
|
|
|
46
49
|
An ``ImportSet`` is an immutable data structure.
|
|
47
50
|
"""
|
|
48
51
|
|
|
52
|
+
_EMPTY : ClassVar[ImportSet]
|
|
53
|
+
_importset : FrozenSet[Import]
|
|
54
|
+
|
|
49
55
|
def __new__(cls, arg, ignore_nonimports=False, ignore_shadowed=False):
|
|
50
56
|
"""
|
|
51
57
|
Return as an `ImportSet`.
|
|
@@ -66,7 +72,7 @@ class ImportSet(object):
|
|
|
66
72
|
"""
|
|
67
73
|
if isinstance(arg, cls):
|
|
68
74
|
if ignore_shadowed:
|
|
69
|
-
return cls._from_imports(arg._importset, ignore_shadowed=True)
|
|
75
|
+
return cls._from_imports(list(arg._importset), ignore_shadowed=True)
|
|
70
76
|
else:
|
|
71
77
|
return arg
|
|
72
78
|
return cls._from_args(
|
|
@@ -75,7 +81,7 @@ class ImportSet(object):
|
|
|
75
81
|
ignore_shadowed=ignore_shadowed)
|
|
76
82
|
|
|
77
83
|
@classmethod
|
|
78
|
-
def _from_imports(cls, imports, ignore_shadowed=False):
|
|
84
|
+
def _from_imports(cls, imports:List[Import], ignore_shadowed:bool=False):
|
|
79
85
|
"""
|
|
80
86
|
:type imports:
|
|
81
87
|
Sequence of `Import` s
|
|
@@ -85,26 +91,30 @@ class ImportSet(object):
|
|
|
85
91
|
`ImportSet`
|
|
86
92
|
"""
|
|
87
93
|
# Canonicalize inputs.
|
|
88
|
-
|
|
94
|
+
by_import_as: Dict[Union[str, Import], Import]
|
|
95
|
+
filtered_imports: Sequence[Import]
|
|
96
|
+
for imp in imports:
|
|
97
|
+
assert isinstance(imp, Import)
|
|
98
|
+
_imports = [Import(imp) for imp in imports]
|
|
89
99
|
if ignore_shadowed:
|
|
90
100
|
# Filter by overshadowed imports. Later imports take precedence.
|
|
91
101
|
by_import_as = {}
|
|
92
|
-
for imp in
|
|
102
|
+
for imp in _imports:
|
|
93
103
|
if imp.import_as == "*":
|
|
94
104
|
# Keep all unique star imports.
|
|
95
105
|
by_import_as[imp] = imp
|
|
96
106
|
else:
|
|
97
107
|
by_import_as[imp.import_as] = imp
|
|
98
|
-
filtered_imports = by_import_as.values()
|
|
108
|
+
filtered_imports = list(by_import_as.values())
|
|
99
109
|
else:
|
|
100
|
-
filtered_imports =
|
|
110
|
+
filtered_imports = _imports
|
|
101
111
|
# Construct and return.
|
|
102
112
|
self = object.__new__(cls)
|
|
103
113
|
self._importset = frozenset(filtered_imports)
|
|
104
114
|
return self
|
|
105
115
|
|
|
106
116
|
@classmethod
|
|
107
|
-
def _from_args(cls, args, ignore_nonimports=False, ignore_shadowed=False):
|
|
117
|
+
def _from_args(cls, args, ignore_nonimports:bool=False, ignore_shadowed=False):
|
|
108
118
|
"""
|
|
109
119
|
:type args:
|
|
110
120
|
``tuple`` or ``list`` of `ImportStatement` s, `PythonStatement` s,
|
|
@@ -139,7 +149,10 @@ class ImportSet(object):
|
|
|
139
149
|
elif isinstance(arg, str) and is_identifier(arg, dotted=True):
|
|
140
150
|
imports.append(Import(arg))
|
|
141
151
|
else: # PythonBlock, PythonStatement, Filename, FileText, str
|
|
142
|
-
|
|
152
|
+
if not isinstance(arg, PythonBlock):
|
|
153
|
+
block = PythonBlock(arg)
|
|
154
|
+
else:
|
|
155
|
+
block = arg
|
|
143
156
|
for statement in block.statements:
|
|
144
157
|
# Ignore comments/blanks.
|
|
145
158
|
if statement.is_comment_or_blank:
|
|
@@ -170,7 +183,7 @@ class ImportSet(object):
|
|
|
170
183
|
`ImportSet`
|
|
171
184
|
"""
|
|
172
185
|
other = ImportSet(other)
|
|
173
|
-
return type(self)._from_imports(self._importset | other._importset)
|
|
186
|
+
return type(self)._from_imports(list(self._importset | other._importset))
|
|
174
187
|
|
|
175
188
|
def without_imports(self, removals):
|
|
176
189
|
"""
|
|
@@ -468,17 +481,17 @@ class ImportSet(object):
|
|
|
468
481
|
% (type(params.align_imports).__name__,))
|
|
469
482
|
return ''.join(pp(statement, import_column) for statement in statements)
|
|
470
483
|
|
|
471
|
-
def __contains__(self, x):
|
|
484
|
+
def __contains__(self, x) -> bool:
|
|
472
485
|
return x in self._importset
|
|
473
486
|
|
|
474
|
-
def __eq__(self, other):
|
|
487
|
+
def __eq__(self, other) -> bool:
|
|
475
488
|
if self is other:
|
|
476
489
|
return True
|
|
477
490
|
if not isinstance(other, ImportSet):
|
|
478
491
|
return NotImplemented
|
|
479
492
|
return self._importset == other._importset
|
|
480
493
|
|
|
481
|
-
def __ne__(self, other):
|
|
494
|
+
def __ne__(self, other) -> bool:
|
|
482
495
|
return not (self == other)
|
|
483
496
|
|
|
484
497
|
# The rest are defined by total_ordering
|
|
@@ -497,7 +510,7 @@ class ImportSet(object):
|
|
|
497
510
|
def __hash__(self):
|
|
498
511
|
return hash(self._importset)
|
|
499
512
|
|
|
500
|
-
def __len__(self):
|
|
513
|
+
def __len__(self) -> int:
|
|
501
514
|
return len(self.imports)
|
|
502
515
|
|
|
503
516
|
def __iter__(self):
|
|
@@ -519,6 +532,7 @@ class ImportMap(object):
|
|
|
519
532
|
"""
|
|
520
533
|
|
|
521
534
|
_data: Dict
|
|
535
|
+
_EMPTY : ClassVar[ImportSet]
|
|
522
536
|
|
|
523
537
|
def __new__(cls, arg):
|
|
524
538
|
if isinstance(arg, cls):
|