pyflyby 1.9.4__tar.gz → 1.9.6__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.
Potentially problematic release.
This version of pyflyby might be problematic. Click here for more details.
- {pyflyby-1.9.4/lib/python/pyflyby.egg-info → pyflyby-1.9.6}/PKG-INFO +31 -4
- {pyflyby-1.9.4 → pyflyby-1.9.6}/README.rst +30 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/bin/pyflyby/__init__.py +1 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/bin/pyflyby/_autoimp.py +51 -5
- {pyflyby-1.9.4/lib/python → pyflyby-1.9.6/bin}/pyflyby/_cmdline.py +6 -3
- {pyflyby-1.9.4 → pyflyby-1.9.6}/bin/pyflyby/_dbg.py +12 -11
- pyflyby-1.9.6/bin/pyflyby/_dynimp.py +152 -0
- {pyflyby-1.9.4/lib/python → pyflyby-1.9.6/bin}/pyflyby/_file.py +37 -20
- {pyflyby-1.9.4/lib/python → pyflyby-1.9.6/bin}/pyflyby/_importclns.py +30 -14
- {pyflyby-1.9.4/lib/python → pyflyby-1.9.6/bin}/pyflyby/_importdb.py +133 -45
- {pyflyby-1.9.4 → pyflyby-1.9.6}/bin/pyflyby/_interactive.py +23 -146
- {pyflyby-1.9.4 → pyflyby-1.9.6}/bin/pyflyby/_modules.py +13 -15
- {pyflyby-1.9.4/lib/python → pyflyby-1.9.6/bin}/pyflyby/_parse.py +20 -12
- {pyflyby-1.9.4/lib/python → pyflyby-1.9.6/bin}/pyflyby/_py.py +16 -5
- {pyflyby-1.9.4/lib/python → pyflyby-1.9.6/bin}/pyflyby/_version.py +1 -1
- {pyflyby-1.9.4 → pyflyby-1.9.6}/lib/python/pyflyby/__init__.py +1 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/lib/python/pyflyby/_autoimp.py +51 -5
- {pyflyby-1.9.4/bin → pyflyby-1.9.6/lib/python}/pyflyby/_cmdline.py +6 -3
- {pyflyby-1.9.4 → pyflyby-1.9.6}/lib/python/pyflyby/_dbg.py +12 -11
- pyflyby-1.9.6/lib/python/pyflyby/_dynimp.py +152 -0
- {pyflyby-1.9.4/bin → pyflyby-1.9.6/lib/python}/pyflyby/_file.py +37 -20
- {pyflyby-1.9.4/bin → pyflyby-1.9.6/lib/python}/pyflyby/_importclns.py +30 -14
- {pyflyby-1.9.4/bin → pyflyby-1.9.6/lib/python}/pyflyby/_importdb.py +133 -45
- {pyflyby-1.9.4 → pyflyby-1.9.6}/lib/python/pyflyby/_interactive.py +23 -146
- {pyflyby-1.9.4 → pyflyby-1.9.6}/lib/python/pyflyby/_modules.py +13 -15
- {pyflyby-1.9.4/bin → pyflyby-1.9.6/lib/python}/pyflyby/_parse.py +20 -12
- {pyflyby-1.9.4/bin → pyflyby-1.9.6/lib/python}/pyflyby/_py.py +16 -5
- {pyflyby-1.9.4/bin → pyflyby-1.9.6/lib/python}/pyflyby/_version.py +1 -1
- {pyflyby-1.9.4 → pyflyby-1.9.6/lib/python/pyflyby.egg-info}/PKG-INFO +31 -4
- {pyflyby-1.9.4 → pyflyby-1.9.6}/lib/python/pyflyby.egg-info/SOURCES.txt +3 -5
- pyflyby-1.9.6/lib/python/pyflyby.egg-info/requires.txt +6 -0
- pyflyby-1.9.6/pyproject.toml +26 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/setup.py +6 -1
- {pyflyby-1.9.4 → pyflyby-1.9.6}/tests/test_autoimp.py +64 -5
- {pyflyby-1.9.4 → pyflyby-1.9.6}/tests/test_cmdline.py +46 -14
- {pyflyby-1.9.4 → pyflyby-1.9.6}/tests/test_importclns.py +6 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/tests/test_interactive.py +29 -17
- {pyflyby-1.9.4 → pyflyby-1.9.6}/tests/test_livepatch.py +1 -1
- {pyflyby-1.9.4 → pyflyby-1.9.6}/tests/test_parse.py +12 -1
- {pyflyby-1.9.4 → pyflyby-1.9.6}/tests/test_py.py +7 -19
- pyflyby-1.9.4/bin/pyflyby/.DS_Store +0 -0
- 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.4/lib/python/pyflyby.egg-info/requires.txt +0 -3
- {pyflyby-1.9.4 → pyflyby-1.9.6}/.pyflyby +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/LICENSE.txt +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/MANIFEST.in +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/bin/autoipython +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/bin/autopython +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/bin/collect-exports +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/bin/collect-imports +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/bin/create-imports +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/bin/find-import +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/bin/list-bad-xrefs +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/bin/prune-broken-imports +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/bin/py +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/bin/pyflyby/__main__.py +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/bin/pyflyby/_comms.py +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/bin/pyflyby/_docxref.py +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/bin/pyflyby/_flags.py +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/bin/pyflyby/_format.py +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/bin/pyflyby/_idents.py +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/bin/pyflyby/_import_sorting.py +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/bin/pyflyby/_imports2s.py +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/bin/pyflyby/_importstmt.py +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/bin/pyflyby/_livepatch.py +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/bin/pyflyby/_log.py +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/bin/pyflyby/_util.py +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/bin/pyflyby/autoimport.py +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/bin/pyflyby/importdb.py +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/bin/pyflyby-diff +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/bin/reformat-imports +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/bin/replace-star-imports +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/bin/tidy-imports +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/bin/transform-imports +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/doc/LICENSE.txt +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/doc/Makefile +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/doc/TODO.txt +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/doc/__init__.py +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/doc/api/api.rst +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/doc/api/autoimp.rst +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/doc/api/cmdline.rst +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/doc/api/comms.rst +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/doc/api/dbg.rst +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/doc/api/file.rst +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/doc/api/flags.rst +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/doc/api/format.rst +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/doc/api/idents.rst +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/doc/api/importclns.rst +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/doc/api/importdb.rst +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/doc/api/imports2s.rst +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/doc/api/importstmt.rst +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/doc/api/interactive.rst +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/doc/api/livepatch.rst +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/doc/api/log.rst +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/doc/api/modules.rst +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/doc/api/parse.rst +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/doc/api/py.rst +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/doc/api/util.rst +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/doc/cli/autoipython.rst +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/doc/cli/cli.rst +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/doc/cli/collect_exports.rst +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/doc/cli/collect_imports.rst +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/doc/cli/find_import.rst +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/doc/cli/prune_broken_imports.rst +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/doc/cli/py.rst +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/doc/cli/pyflyby_diff.rst +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/doc/cli/reformat_imports.rst +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/doc/cli/replace_star_imports.rst +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/doc/cli/tidy_imports.rst +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/doc/cli/transform_imports.rst +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/doc/conf.py +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/doc/index.rst +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/doc/make.bat +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/doc/testing.txt +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/etc/pyflyby/canonical.py +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/etc/pyflyby/common.py +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/etc/pyflyby/forget.py +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/etc/pyflyby/mandatory.py +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/etc/pyflyby/numpy.py +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/etc/pyflyby/std.py +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/lib/emacs/pyflyby.el +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/lib/python/pyflyby/__main__.py +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/lib/python/pyflyby/_comms.py +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/lib/python/pyflyby/_docxref.py +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/lib/python/pyflyby/_flags.py +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/lib/python/pyflyby/_format.py +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/lib/python/pyflyby/_idents.py +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/lib/python/pyflyby/_import_sorting.py +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/lib/python/pyflyby/_imports2s.py +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/lib/python/pyflyby/_importstmt.py +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/lib/python/pyflyby/_livepatch.py +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/lib/python/pyflyby/_log.py +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/lib/python/pyflyby/_util.py +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/lib/python/pyflyby/autoimport.py +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/lib/python/pyflyby/importdb.py +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/lib/python/pyflyby.egg-info/dependency_links.txt +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/lib/python/pyflyby.egg-info/entry_points.txt +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/lib/python/pyflyby.egg-info/top_level.txt +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/libexec/pyflyby/colordiff +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/libexec/pyflyby/diff-colorize +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/setup.cfg +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/tests/__init__.py +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/tests/test_0testconfig.py +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/tests/test_docxref.py +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/tests/test_file.py +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/tests/test_flags.py +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/tests/test_format.py +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/tests/test_idents.py +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/tests/test_importdb.py +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/tests/test_imports2s.py +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/tests/test_importstmt.py +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/tests/test_jupyterlab_pyflyby.py +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/tests/test_modules.py +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/tests/test_util.py +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/tests/tests_sorts.py +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/tests/xrefs.py +0 -0
- {pyflyby-1.9.4 → pyflyby-1.9.6}/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.6
|
|
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
|
|
@@ -18,9 +18,6 @@ Classifier: License :: OSI Approved :: MIT License
|
|
|
18
18
|
Classifier: Programming Language :: Python
|
|
19
19
|
Requires-Python: >3.8, <4
|
|
20
20
|
License-File: LICENSE.txt
|
|
21
|
-
Requires-Dist: six
|
|
22
|
-
Requires-Dist: toml
|
|
23
|
-
Requires-Dist: black
|
|
24
21
|
|
|
25
22
|
#########
|
|
26
23
|
Pyflyby
|
|
@@ -105,6 +102,36 @@ or
|
|
|
105
102
|
[PYFLYBY] from base64 import b64decode
|
|
106
103
|
Out[1]: 'hello'
|
|
107
104
|
|
|
105
|
+
Auto importer lazy variables
|
|
106
|
+
----------------------------
|
|
107
|
+
|
|
108
|
+
It is possible to use the autoimporter to lazily define variables.
|
|
109
|
+
|
|
110
|
+
To use, put the following in your IPython startup files
|
|
111
|
+
(``~/.ipython/profile_default/startup/autoimp.py``), or in your IPython
|
|
112
|
+
configuration file:
|
|
113
|
+
|
|
114
|
+
.. code:: python
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
from pyflyby import add_import
|
|
118
|
+
|
|
119
|
+
add_import("foo", "foo = 1")
|
|
120
|
+
|
|
121
|
+
add_import(
|
|
122
|
+
"df, data as dd",
|
|
123
|
+
'''
|
|
124
|
+
import pandas as pd
|
|
125
|
+
data = [1,2,3]
|
|
126
|
+
df = pd.DataFrame(data)
|
|
127
|
+
''')
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
You can add the keyword ``strict=False`` to not fail if not in IPython or of the
|
|
131
|
+
pyflyby extensions is not loaded.
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
|
|
108
135
|
|
|
109
136
|
Quick start: ``py`` command-line multi-tool
|
|
110
137
|
===========================================
|
|
@@ -81,6 +81,36 @@ or
|
|
|
81
81
|
[PYFLYBY] from base64 import b64decode
|
|
82
82
|
Out[1]: 'hello'
|
|
83
83
|
|
|
84
|
+
Auto importer lazy variables
|
|
85
|
+
----------------------------
|
|
86
|
+
|
|
87
|
+
It is possible to use the autoimporter to lazily define variables.
|
|
88
|
+
|
|
89
|
+
To use, put the following in your IPython startup files
|
|
90
|
+
(``~/.ipython/profile_default/startup/autoimp.py``), or in your IPython
|
|
91
|
+
configuration file:
|
|
92
|
+
|
|
93
|
+
.. code:: python
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
from pyflyby import add_import
|
|
97
|
+
|
|
98
|
+
add_import("foo", "foo = 1")
|
|
99
|
+
|
|
100
|
+
add_import(
|
|
101
|
+
"df, data as dd",
|
|
102
|
+
'''
|
|
103
|
+
import pandas as pd
|
|
104
|
+
data = [1,2,3]
|
|
105
|
+
df = pd.DataFrame(data)
|
|
106
|
+
''')
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
You can add the keyword ``strict=False`` to not fail if not in IPython or of the
|
|
110
|
+
pyflyby extensions is not loaded.
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
|
|
84
114
|
|
|
85
115
|
Quick start: ``py`` command-line multi-tool
|
|
86
116
|
===========================================
|
|
@@ -19,7 +19,7 @@ from pyflyby._importstmt import Import
|
|
|
19
19
|
from pyflyby._log import logger
|
|
20
20
|
from pyflyby._modules import ModuleHandle
|
|
21
21
|
from pyflyby._parse import (PythonBlock, _is_ast_str,
|
|
22
|
-
infer_compile_mode)
|
|
22
|
+
infer_compile_mode, MatchAs)
|
|
23
23
|
|
|
24
24
|
from six import reraise
|
|
25
25
|
import sys
|
|
@@ -491,8 +491,11 @@ class _MissingImportFinder(object):
|
|
|
491
491
|
logger.debug(
|
|
492
492
|
"_MissingImportFinder has no method %r, using generic_visit", method
|
|
493
493
|
)
|
|
494
|
-
|
|
495
|
-
|
|
494
|
+
if hasattr(self, method):
|
|
495
|
+
visitor = getattr(self, method)
|
|
496
|
+
else:
|
|
497
|
+
logger.debug("No method `%s`, using `generic_visit`", method)
|
|
498
|
+
visitor = self.generic_visit
|
|
496
499
|
return visitor(node)
|
|
497
500
|
else:
|
|
498
501
|
raise TypeError("unexpected %s" % (type(node).__name__,))
|
|
@@ -827,6 +830,47 @@ class _MissingImportFinder(object):
|
|
|
827
830
|
self._visit_StoreImport(node, modulename)
|
|
828
831
|
self.generic_visit(node)
|
|
829
832
|
|
|
833
|
+
if sys.version_info >= (3,10):
|
|
834
|
+
def visit_match_case(self, node:ast.match_case):
|
|
835
|
+
logger.debug("visit_match_case(%r)", node)
|
|
836
|
+
return self.generic_visit(node)
|
|
837
|
+
|
|
838
|
+
def visit_Match(self, node:ast.Match):
|
|
839
|
+
logger.debug("visit_Match(%r)", node)
|
|
840
|
+
return self.generic_visit(node)
|
|
841
|
+
|
|
842
|
+
def visit_MatchMapping(self, node:ast.MatchMapping):
|
|
843
|
+
logger.debug("visit_MatchMapping(%r)", node)
|
|
844
|
+
return self.generic_visit(node)
|
|
845
|
+
|
|
846
|
+
def visit_MatchAs(self, node:MatchAs):
|
|
847
|
+
logger.debug("visit_MatchAs(%r)", node)
|
|
848
|
+
if node.name is None:
|
|
849
|
+
return
|
|
850
|
+
isinstance(node.name, str), node.name
|
|
851
|
+
return self._visit_Store(node.name)
|
|
852
|
+
|
|
853
|
+
def visit_Call(self, node:ast.Call):
|
|
854
|
+
logger.debug("visit_Call(%r)", node)
|
|
855
|
+
return self.generic_visit(node)
|
|
856
|
+
|
|
857
|
+
def visit_Pass(self, node:ast.Pass):
|
|
858
|
+
logger.debug("visit_Pass(%r)", node)
|
|
859
|
+
return self.generic_visit(node)
|
|
860
|
+
|
|
861
|
+
def visit_Constant(self, node:ast.Constant):
|
|
862
|
+
logger.debug("visit_Constant(%r)", node)
|
|
863
|
+
return self.generic_visit(node)
|
|
864
|
+
|
|
865
|
+
def visit_Module(self, node:ast.Module):
|
|
866
|
+
logger.debug("visit_Module(%r)", node)
|
|
867
|
+
return self.generic_visit(node)
|
|
868
|
+
|
|
869
|
+
def visit_Expr(self, node:ast.Expr):
|
|
870
|
+
logger.debug("visit_Expr(%r)", node)
|
|
871
|
+
return self.generic_visit(node)
|
|
872
|
+
|
|
873
|
+
|
|
830
874
|
def visit_Name(self, node):
|
|
831
875
|
logger.debug("visit_Name(%r)", node.id)
|
|
832
876
|
self._visit_fullname(node.id, node.ctx)
|
|
@@ -884,7 +928,7 @@ class _MissingImportFinder(object):
|
|
|
884
928
|
value = _UseChecker(name, imp, self._lineno)
|
|
885
929
|
self._visit_Store(name, value)
|
|
886
930
|
|
|
887
|
-
def _visit_Store(self, fullname, value=None):
|
|
931
|
+
def _visit_Store(self, fullname:str, value=None):
|
|
888
932
|
logger.debug("_visit_Store(%r)", fullname)
|
|
889
933
|
if fullname is None:
|
|
890
934
|
return
|
|
@@ -1860,7 +1904,7 @@ def auto_import_symbol(fullname, namespaces, db=None, autoimported=None, post_im
|
|
|
1860
1904
|
return True
|
|
1861
1905
|
|
|
1862
1906
|
|
|
1863
|
-
def auto_import(arg, namespaces, db=None, autoimported=None, post_import_hook=None):
|
|
1907
|
+
def auto_import(arg, namespaces, db=None, autoimported=None, post_import_hook=None, *, extra_db=None):
|
|
1864
1908
|
"""
|
|
1865
1909
|
Parse ``arg`` for symbols that need to be imported and automatically import
|
|
1866
1910
|
them.
|
|
@@ -1914,6 +1958,8 @@ def auto_import(arg, namespaces, db=None, autoimported=None, post_import_hook=No
|
|
|
1914
1958
|
if autoimported is None:
|
|
1915
1959
|
autoimported = {}
|
|
1916
1960
|
db = ImportDB.interpret_arg(db, target_filename=filename)
|
|
1961
|
+
if extra_db:
|
|
1962
|
+
db = db|extra_db
|
|
1917
1963
|
ok = True
|
|
1918
1964
|
for fullname in fullnames:
|
|
1919
1965
|
ok &= auto_import_symbol(fullname, namespaces, db, autoimported, post_import_hook=post_import_hook)
|
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
|
|
7
|
+
from builtins import input
|
|
7
8
|
import optparse
|
|
8
9
|
import os
|
|
9
10
|
import signal
|
|
10
|
-
from builtins import input
|
|
11
11
|
import sys
|
|
12
12
|
from textwrap import dedent
|
|
13
13
|
import traceback
|
|
@@ -285,7 +285,8 @@ def parse_args(
|
|
|
285
285
|
def _default_on_error(filename):
|
|
286
286
|
raise SystemExit("bad filename %s" % (filename,))
|
|
287
287
|
|
|
288
|
-
|
|
288
|
+
|
|
289
|
+
def filename_args(args: List[str], on_error=_default_on_error):
|
|
289
290
|
"""
|
|
290
291
|
Return list of filenames given command-line arguments.
|
|
291
292
|
|
|
@@ -293,7 +294,9 @@ def filename_args(args, on_error=_default_on_error):
|
|
|
293
294
|
``list`` of `Filename`
|
|
294
295
|
"""
|
|
295
296
|
if args:
|
|
296
|
-
|
|
297
|
+
for a in args:
|
|
298
|
+
assert isinstance(a, str)
|
|
299
|
+
return expand_py_files_from_args([Filename(f) for f in args], on_error)
|
|
297
300
|
elif not os.isatty(0):
|
|
298
301
|
return [Filename.STDIN]
|
|
299
302
|
else:
|
|
@@ -909,23 +909,24 @@ def enable_faulthandler():
|
|
|
909
909
|
faulthandler.enable()
|
|
910
910
|
|
|
911
911
|
|
|
912
|
-
def add_debug_functions_to_builtins():
|
|
913
|
-
|
|
912
|
+
def add_debug_functions_to_builtins(*, add_deprecated: bool):
|
|
913
|
+
"""
|
|
914
914
|
Install debugger(), etc. in the builtin global namespace.
|
|
915
|
-
|
|
915
|
+
"""
|
|
916
916
|
functions_to_add = [
|
|
917
917
|
'debugger',
|
|
918
918
|
'debug_on_exception',
|
|
919
919
|
'print_traceback',
|
|
920
920
|
]
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
921
|
+
if add_deprecated:
|
|
922
|
+
# DEPRECATED: In the future, the following will not be added to builtins.
|
|
923
|
+
# Use debugger() instead.
|
|
924
|
+
functions_to_add += [
|
|
925
|
+
"breakpoint",
|
|
926
|
+
"debug_exception",
|
|
927
|
+
"debug_statement",
|
|
928
|
+
"waitpoint",
|
|
929
|
+
]
|
|
929
930
|
for name in functions_to_add:
|
|
930
931
|
setattr(builtins, name, globals()[name])
|
|
931
932
|
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Virtual module to create dynamic import at runtime.
|
|
3
|
+
|
|
4
|
+
It is sometime desirable to have auto import which are define only during
|
|
5
|
+
a session and never exist on a on-disk file.
|
|
6
|
+
|
|
7
|
+
This is injects a Dict module loader as well as a dictionary registry of in
|
|
8
|
+
memory module.
|
|
9
|
+
|
|
10
|
+
This is mostly use in IPython for lazy variable initialisation without having
|
|
11
|
+
to use proxy objects.
|
|
12
|
+
|
|
13
|
+
To use, put the following in your IPython startup files
|
|
14
|
+
(``~/.ipython/profile_default/startup/autoimp.py`), or in your IPython
|
|
15
|
+
configuration file:
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
.. code:: python
|
|
19
|
+
|
|
20
|
+
from pyflyby._dynimp import add_import
|
|
21
|
+
|
|
22
|
+
add_import("foo", "foo = 1")
|
|
23
|
+
|
|
24
|
+
add_import(
|
|
25
|
+
"df, data",
|
|
26
|
+
'''
|
|
27
|
+
import pandas as pd
|
|
28
|
+
data = [1,2,3]
|
|
29
|
+
df = pd.DataFrame(data)
|
|
30
|
+
''',
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
Now at the IPython prompt, if the pyflyby extension is loaded (either because
|
|
34
|
+
you started using the ``py`` cli, or some configuration options like ``ipython
|
|
35
|
+
--TerminalIPythonApp.extra_extensions=pyflyby``. When trying to use an undefined
|
|
36
|
+
variable like ``foo``, ``df`` or ``data``, the corresponding module will be
|
|
37
|
+
executed and the relevant variable imported.
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
"""
|
|
41
|
+
import importlib.abc
|
|
42
|
+
import importlib.util
|
|
43
|
+
import sys
|
|
44
|
+
|
|
45
|
+
from textwrap import dedent
|
|
46
|
+
from typing import FrozenSet
|
|
47
|
+
|
|
48
|
+
from pyflyby._importclns import ImportSet, Import
|
|
49
|
+
|
|
50
|
+
module_dict = {}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def add_import(names: str, code: str, *, strict: bool = True):
|
|
54
|
+
"""
|
|
55
|
+
Add a runtime generated import module
|
|
56
|
+
|
|
57
|
+
Parameters
|
|
58
|
+
----------
|
|
59
|
+
names: str
|
|
60
|
+
name, or comma separated list variable names that should be created by
|
|
61
|
+
executing and importing `code`.
|
|
62
|
+
code: str
|
|
63
|
+
potentially multiline string that will be turned into a module,
|
|
64
|
+
executed and from which variables listed in names can be imported.
|
|
65
|
+
strict: bool
|
|
66
|
+
Raise in case of problem loading IPython of if pyflyby extension not installed.
|
|
67
|
+
otherwise just ignore error
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
Examples
|
|
72
|
+
--------
|
|
73
|
+
|
|
74
|
+
>>> add_import('pd, df', '''
|
|
75
|
+
... import pandas a pd
|
|
76
|
+
...
|
|
77
|
+
... df = pd.DataFrame([[1,2], [3,4]])
|
|
78
|
+
... ''', strict=False) # don't fail doctest
|
|
79
|
+
|
|
80
|
+
"""
|
|
81
|
+
try:
|
|
82
|
+
ip = _raise_if_problem()
|
|
83
|
+
except Exception:
|
|
84
|
+
if strict:
|
|
85
|
+
raise
|
|
86
|
+
else:
|
|
87
|
+
return
|
|
88
|
+
return _add_import(ip, names, code)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _raise_if_problem():
|
|
92
|
+
try:
|
|
93
|
+
import IPython
|
|
94
|
+
except ModuleNotFoundError as e:
|
|
95
|
+
raise ImportError("Dynamic autoimport requires IPython to be installed") from e
|
|
96
|
+
|
|
97
|
+
ip = IPython.get_ipython()
|
|
98
|
+
if ip is None:
|
|
99
|
+
raise ImportError("Dynamic autoimport only work from within IPython")
|
|
100
|
+
|
|
101
|
+
if not hasattr(ip, "_auto_importer"):
|
|
102
|
+
raise ValueError(
|
|
103
|
+
"IPython needs to be loaded with pyflyby extension for lazy variable to work"
|
|
104
|
+
)
|
|
105
|
+
return ip
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _add_import(ip, names: str, code: str) -> None:
|
|
109
|
+
"""
|
|
110
|
+
private version of add_import
|
|
111
|
+
"""
|
|
112
|
+
assert ip is not None
|
|
113
|
+
mang = "pyflyby_autoimport_" + names.replace(",", "_").replace(" ", "_")
|
|
114
|
+
a: FrozenSet[Import] = ImportSet(f"from {mang} import {names}")._importset
|
|
115
|
+
b: FrozenSet[Import] = ip._auto_importer.db.known_imports._importset
|
|
116
|
+
s_import: FrozenSet[Import] = a | b
|
|
117
|
+
|
|
118
|
+
ip._auto_importer.db.known_imports = ImportSet._from_imports(list(s_import))
|
|
119
|
+
module_dict[mang] = dedent(code)
|
|
120
|
+
|
|
121
|
+
class DictLoader(importlib.abc.Loader):
|
|
122
|
+
"""
|
|
123
|
+
A dict based loader for in-memory module definition.
|
|
124
|
+
"""
|
|
125
|
+
def __init__(self, module_name, module_code):
|
|
126
|
+
self.module_name = module_name
|
|
127
|
+
self.module_code = module_code
|
|
128
|
+
|
|
129
|
+
def create_module(self, spec):
|
|
130
|
+
return None # Use default module creation semantics
|
|
131
|
+
|
|
132
|
+
def exec_module(self, module):
|
|
133
|
+
"""
|
|
134
|
+
we exec module code directly in memory
|
|
135
|
+
"""
|
|
136
|
+
exec(self.module_code, module.__dict__)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class DictFinder(importlib.abc.MetaPathFinder):
|
|
140
|
+
"""
|
|
141
|
+
A meta path finder for abode DictLoader
|
|
142
|
+
"""
|
|
143
|
+
def find_spec(self, fullname, path, target=None):
|
|
144
|
+
if fullname in module_dict:
|
|
145
|
+
module_code = module_dict[fullname]
|
|
146
|
+
loader = DictLoader(fullname, module_code)
|
|
147
|
+
return importlib.util.spec_from_loader(fullname, loader)
|
|
148
|
+
return None
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def inject():
|
|
152
|
+
sys.meta_path.insert(0, DictFinder())
|
|
@@ -3,15 +3,20 @@
|
|
|
3
3
|
# License: MIT http://opensource.org/licenses/MIT
|
|
4
4
|
from __future__ import annotations
|
|
5
5
|
|
|
6
|
-
from functools import
|
|
6
|
+
from functools import cached_property, total_ordering
|
|
7
7
|
import io
|
|
8
8
|
import os
|
|
9
9
|
import re
|
|
10
10
|
import sys
|
|
11
|
-
from typing import Optional, Tuple,
|
|
11
|
+
from typing import ClassVar, List, Optional, Tuple, Union
|
|
12
12
|
|
|
13
13
|
from pyflyby._util import cmp, memoize
|
|
14
14
|
|
|
15
|
+
if sys.version_info < (3,10):
|
|
16
|
+
NoneType = type(None)
|
|
17
|
+
else:
|
|
18
|
+
from types import NoneType
|
|
19
|
+
|
|
15
20
|
|
|
16
21
|
class UnsafeFilenameError(ValueError):
|
|
17
22
|
pass
|
|
@@ -33,24 +38,27 @@ class Filename(object):
|
|
|
33
38
|
|
|
34
39
|
def __new__(cls, arg):
|
|
35
40
|
if isinstance(arg, cls):
|
|
36
|
-
|
|
41
|
+
# TODO make this assert False
|
|
42
|
+
return cls._from_filename(arg._filename)
|
|
37
43
|
if isinstance(arg, str):
|
|
38
44
|
return cls._from_filename(arg)
|
|
39
45
|
raise TypeError
|
|
40
46
|
|
|
41
47
|
@classmethod
|
|
42
|
-
def _from_filename(cls, filename):
|
|
48
|
+
def _from_filename(cls, filename: str):
|
|
43
49
|
if not isinstance(filename, str):
|
|
44
50
|
raise TypeError
|
|
45
|
-
filename =
|
|
51
|
+
filename = os.path.abspath(filename)
|
|
46
52
|
if not filename:
|
|
47
53
|
raise UnsafeFilenameError("(empty string)")
|
|
48
|
-
|
|
49
|
-
|
|
54
|
+
# we only allow filename with given character set
|
|
55
|
+
match = re.search("[^a-zA-Z0-9_=+{}/.,~@-]", filename)
|
|
56
|
+
if match:
|
|
57
|
+
raise UnsafeFilenameError((filename, match))
|
|
50
58
|
if re.search("(^|/)~", filename):
|
|
51
59
|
raise UnsafeFilenameError(filename)
|
|
52
60
|
self = object.__new__(cls)
|
|
53
|
-
self._filename =
|
|
61
|
+
self._filename = filename
|
|
54
62
|
return self
|
|
55
63
|
|
|
56
64
|
def __str__(self):
|
|
@@ -373,6 +381,8 @@ class FileText:
|
|
|
373
381
|
:rtype:
|
|
374
382
|
``FileText``
|
|
375
383
|
"""
|
|
384
|
+
if isinstance(filename, str):
|
|
385
|
+
filename = Filename(filename)
|
|
376
386
|
if isinstance(arg, cls):
|
|
377
387
|
if filename is startpos is None:
|
|
378
388
|
return arg
|
|
@@ -387,8 +397,8 @@ class FileText:
|
|
|
387
397
|
else:
|
|
388
398
|
raise TypeError("%s: unexpected %s"
|
|
389
399
|
% (cls.__name__, type(arg).__name__))
|
|
390
|
-
|
|
391
|
-
|
|
400
|
+
|
|
401
|
+
assert isinstance(filename, (Filename, NoneType))
|
|
392
402
|
startpos = FilePos(startpos)
|
|
393
403
|
self.filename = filename
|
|
394
404
|
self.startpos = startpos
|
|
@@ -439,9 +449,9 @@ class FileText:
|
|
|
439
449
|
def from_filename(cls, filename):
|
|
440
450
|
return cls.from_lines(Filename(filename))
|
|
441
451
|
|
|
442
|
-
def alter(self, filename=None, startpos=None):
|
|
452
|
+
def alter(self, filename: Optional[Filename] = None, startpos=None):
|
|
443
453
|
if filename is not None:
|
|
444
|
-
filename
|
|
454
|
+
assert isinstance(filename, Filename)
|
|
445
455
|
else:
|
|
446
456
|
filename = self.filename
|
|
447
457
|
if startpos is not None:
|
|
@@ -652,8 +662,8 @@ class FileText:
|
|
|
652
662
|
return h
|
|
653
663
|
|
|
654
664
|
|
|
655
|
-
def read_file(filename):
|
|
656
|
-
filename
|
|
665
|
+
def read_file(filename: Filename) -> FileText:
|
|
666
|
+
assert isinstance(filename, Filename)
|
|
657
667
|
if filename == Filename.STDIN:
|
|
658
668
|
data = sys.stdin.read()
|
|
659
669
|
else:
|
|
@@ -661,14 +671,15 @@ def read_file(filename):
|
|
|
661
671
|
data = f.read()
|
|
662
672
|
return FileText(data, filename=filename)
|
|
663
673
|
|
|
664
|
-
|
|
665
|
-
|
|
674
|
+
|
|
675
|
+
def write_file(filename: Filename, data):
|
|
676
|
+
assert isinstance(filename, Filename)
|
|
666
677
|
data = FileText(data)
|
|
667
678
|
with open(str(filename), 'w') as f:
|
|
668
679
|
f.write(data.joined)
|
|
669
680
|
|
|
670
|
-
def atomic_write_file(filename, data):
|
|
671
|
-
filename
|
|
681
|
+
def atomic_write_file(filename: Filename, data):
|
|
682
|
+
assert isinstance(filename, Filename)
|
|
672
683
|
data = FileText(data)
|
|
673
684
|
temp_filename = Filename("%s.tmp.%s" % (filename, os.getpid(),))
|
|
674
685
|
write_file(temp_filename, data)
|
|
@@ -680,7 +691,10 @@ def atomic_write_file(filename, data):
|
|
|
680
691
|
pass
|
|
681
692
|
os.rename(str(temp_filename), str(filename))
|
|
682
693
|
|
|
683
|
-
|
|
694
|
+
|
|
695
|
+
def expand_py_files_from_args(
|
|
696
|
+
pathnames: Union[List[Filename], Filename], on_error=lambda filename: None
|
|
697
|
+
):
|
|
684
698
|
"""
|
|
685
699
|
Enumerate ``*.py`` files, recursively.
|
|
686
700
|
|
|
@@ -698,8 +712,11 @@ def expand_py_files_from_args(pathnames, on_error=lambda filename: None):
|
|
|
698
712
|
``list`` of `Filename` s
|
|
699
713
|
"""
|
|
700
714
|
if not isinstance(pathnames, (tuple, list)):
|
|
715
|
+
# July 2024 DeprecationWarning
|
|
716
|
+
# this seem to be used only internally, maybe deprecate not passing a list.
|
|
701
717
|
pathnames = [pathnames]
|
|
702
|
-
|
|
718
|
+
for f in pathnames:
|
|
719
|
+
assert isinstance(f, Filename)
|
|
703
720
|
result = []
|
|
704
721
|
# Check for problematic arguments. Note that we intentionally only do
|
|
705
722
|
# this for directly specified arguments, not for recursively traversed
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
from __future__ import annotations
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
import sys
|
|
8
8
|
|
|
9
9
|
from collections import defaultdict
|
|
10
10
|
from functools import total_ordering
|
|
@@ -18,8 +18,18 @@ from pyflyby._parse import PythonBlock
|
|
|
18
18
|
from pyflyby._util import (cached_attribute, cmp, partition,
|
|
19
19
|
stable_unique)
|
|
20
20
|
|
|
21
|
-
from
|
|
22
|
-
|
|
21
|
+
from typing import (
|
|
22
|
+
ClassVar,
|
|
23
|
+
Dict,
|
|
24
|
+
FrozenSet,
|
|
25
|
+
Sequence,
|
|
26
|
+
Union,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
if sys.version_info < (3, 12):
|
|
30
|
+
from typing_extensions import Self
|
|
31
|
+
else:
|
|
32
|
+
from typing import Self
|
|
23
33
|
|
|
24
34
|
|
|
25
35
|
class NoSuchImportError(ValueError):
|
|
@@ -30,7 +40,7 @@ class ConflictingImportsError(ValueError):
|
|
|
30
40
|
pass
|
|
31
41
|
|
|
32
42
|
@total_ordering
|
|
33
|
-
class ImportSet
|
|
43
|
+
class ImportSet:
|
|
34
44
|
r"""
|
|
35
45
|
Representation of a set of imports organized into import statements.
|
|
36
46
|
|
|
@@ -49,8 +59,8 @@ class ImportSet(object):
|
|
|
49
59
|
An ``ImportSet`` is an immutable data structure.
|
|
50
60
|
"""
|
|
51
61
|
|
|
52
|
-
_EMPTY
|
|
53
|
-
_importset
|
|
62
|
+
_EMPTY: ClassVar[ImportSet]
|
|
63
|
+
_importset: FrozenSet[Import]
|
|
54
64
|
|
|
55
65
|
def __new__(cls, arg, ignore_nonimports=False, ignore_shadowed=False):
|
|
56
66
|
"""
|
|
@@ -80,8 +90,11 @@ class ImportSet(object):
|
|
|
80
90
|
ignore_nonimports=ignore_nonimports,
|
|
81
91
|
ignore_shadowed=ignore_shadowed)
|
|
82
92
|
|
|
93
|
+
def __or__(self: Self, other: Self) -> Self:
|
|
94
|
+
return type(self)._from_imports(list(self._importset | other._importset))
|
|
95
|
+
|
|
83
96
|
@classmethod
|
|
84
|
-
def _from_imports(cls, imports:
|
|
97
|
+
def _from_imports(cls, imports: Sequence[Import], ignore_shadowed: bool = False):
|
|
85
98
|
"""
|
|
86
99
|
:type imports:
|
|
87
100
|
Sequence of `Import` s
|
|
@@ -114,7 +127,7 @@ class ImportSet(object):
|
|
|
114
127
|
return self
|
|
115
128
|
|
|
116
129
|
@classmethod
|
|
117
|
-
def _from_args(cls, args, ignore_nonimports:bool=False, ignore_shadowed=False):
|
|
130
|
+
def _from_args(cls, args, ignore_nonimports:bool=False, ignore_shadowed=False) -> Self:
|
|
118
131
|
"""
|
|
119
132
|
:type args:
|
|
120
133
|
``tuple`` or ``list`` of `ImportStatement` s, `PythonStatement` s,
|
|
@@ -133,7 +146,7 @@ class ImportSet(object):
|
|
|
133
146
|
# more often.
|
|
134
147
|
args = [a for a in args if a]
|
|
135
148
|
if not args:
|
|
136
|
-
return cls._EMPTY
|
|
149
|
+
return cls._EMPTY # type: ignore[return-value]
|
|
137
150
|
# If we only got one ``ImportSet``, just return it.
|
|
138
151
|
if len(args) == 1 and type(args[0]) is cls and not ignore_shadowed:
|
|
139
152
|
return args[0]
|
|
@@ -543,9 +556,12 @@ class ImportMap(object):
|
|
|
543
556
|
if not len(arg):
|
|
544
557
|
return cls._EMPTY
|
|
545
558
|
return cls._from_map(arg)
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
559
|
+
raise TypeError("ImportMap: expected a dict, not a %s" % (type(arg).__name__,))
|
|
560
|
+
|
|
561
|
+
def __or__(self, other):
|
|
562
|
+
assert isinstance(other, ImportMap)
|
|
563
|
+
assert set(self._data.keys()).intersection(other._data.keys()) == set(), set(self._data.keys()).intersection(other._data.keys())
|
|
564
|
+
return self._merge([self, other])
|
|
549
565
|
|
|
550
566
|
@classmethod
|
|
551
567
|
def _from_map(cls, arg):
|
|
@@ -562,8 +578,8 @@ class ImportMap(object):
|
|
|
562
578
|
if not maps:
|
|
563
579
|
return cls._EMPTY
|
|
564
580
|
data = {}
|
|
565
|
-
for
|
|
566
|
-
data.update(
|
|
581
|
+
for map_ in maps:
|
|
582
|
+
data.update(map_._data)
|
|
567
583
|
return cls(data)
|
|
568
584
|
|
|
569
585
|
def __getitem__(self, k):
|