-
Notifications
You must be signed in to change notification settings - Fork 52
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add infrastructure to lazily define variable via autoimport
This should cover the feature request on IPython to have the ability to lazily define variable that can take some time to be created when first access in IPython; It does so by utilizing the autoimport machinery of pyflyby and creating runtime modules via a import hook. This has many advantage: - we do not use a proxy object. - we do not have to deal with multiple variable being potentially defined together and thik of re-excution.
- Loading branch information
Showing
10 changed files
with
277 additions
and
24 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
""" | ||
Virtual module to create dynamic import at runtime. | ||
It is sometime desirable to have auto import which are define only during | ||
a session and never exist on a on-disk file. | ||
This is injects a Dict module loader as well as a dictionary registry of in | ||
memory module. | ||
This is mostly use in IPython for lazy variable initialisation without having | ||
to use proxy objects. | ||
To use, put the following in your IPython startup files | ||
(``~/.ipython/profile_default/startup/autoimp.py`), or in your IPython | ||
configuration file: | ||
.. code:: python | ||
from pyflyby._dynimp import add_import | ||
add_import("foo", "foo = 1") | ||
add_import( | ||
"df, data", | ||
''' | ||
import pandas as pd | ||
data = [1,2,3] | ||
df = pd.DataFrame(data) | ||
''', | ||
) | ||
Now at the IPython prompt, if the pyflyby extension is loaded (either because | ||
you started using the ``py`` cli, or some configuration options like ``ipython | ||
--TerminalIPythonApp.extra_extensions=pyflyby``. When trying to use an undefined | ||
variable like ``foo``, ``df`` or ``data``, the corresponding module will be | ||
executed and the relevant variable imported. | ||
""" | ||
import importlib.abc | ||
import importlib.util | ||
import sys | ||
|
||
from textwrap import dedent | ||
from typing import FrozenSet | ||
|
||
from pyflyby._importclns import ImportSet, Import | ||
|
||
module_dict = {} | ||
|
||
|
||
def add_import(names: str, code: str, *, strict: bool = True): | ||
""" | ||
Add a runtime generated import module | ||
Parameters | ||
---------- | ||
names: str | ||
name, or comma separated list variable names that should be created by | ||
executing and importing `code`. | ||
code: str | ||
potentially multiline string that will be turned into a module, | ||
executed and from which variables listed in names can be imported. | ||
strict: bool | ||
Raise in case of problem loading IPython of if pyflyby extension not installed. | ||
otherwise just ignore error | ||
Examples | ||
-------- | ||
>>> add_import('pd, df', ''' | ||
... import pandas a pd | ||
... | ||
... df = pd.DataFrame([[1,2], [3,4]]) | ||
... ''', strict=False) # don't fail doctest | ||
""" | ||
try: | ||
ip = _raise_if_problem() | ||
except Exception: | ||
if strict: | ||
raise | ||
else: | ||
return | ||
return _add_import(ip, names, code) | ||
|
||
|
||
def _raise_if_problem(): | ||
try: | ||
import IPython | ||
except ModuleNotFoundError as e: | ||
raise ImportError("Dynamic autoimport requires IPython to be installed") from e | ||
|
||
ip = IPython.get_ipython() | ||
if ip is None: | ||
raise ImportError("Dynamic autoimport only work from within IPython") | ||
|
||
if not hasattr(ip, "_auto_importer"): | ||
raise ValueError( | ||
"IPython needs to be loaded with pyflyby extension for lazy variable to work" | ||
) | ||
return ip | ||
|
||
|
||
def _add_import(ip, names: str, code: str) -> None: | ||
""" | ||
private version of add_import | ||
""" | ||
assert ip is not None | ||
mang = "pyflyby_autoimport_" + names.replace(",", "_").replace(" ", "_") | ||
a: FrozenSet[Import] = ImportSet(f"from {mang} import {names}")._importset | ||
b: FrozenSet[Import] = ip._auto_importer.db.known_imports._importset | ||
s_import: FrozenSet[Import] = a | b | ||
|
||
ip._auto_importer.db.known_imports = ImportSet._from_imports(list(s_import)) | ||
module_dict[mang] = dedent(code) | ||
|
||
class DictLoader(importlib.abc.Loader): | ||
""" | ||
A dict based loader for in-memory module definition. | ||
""" | ||
def __init__(self, module_name, module_code): | ||
self.module_name = module_name | ||
self.module_code = module_code | ||
|
||
def create_module(self, spec): | ||
return None # Use default module creation semantics | ||
|
||
def exec_module(self, module): | ||
""" | ||
we exec module code directly in memory | ||
""" | ||
exec(self.module_code, module.__dict__) | ||
|
||
|
||
class DictFinder(importlib.abc.MetaPathFinder): | ||
""" | ||
A meta path finder for abode DictLoader | ||
""" | ||
def find_spec(self, fullname, path, target=None): | ||
if fullname in module_dict: | ||
module_code = module_dict[fullname] | ||
loader = DictLoader(fullname, module_code) | ||
return importlib.util.spec_from_loader(fullname, loader) | ||
return None | ||
|
||
|
||
def inject(): | ||
sys.meta_path.insert(0, DictFinder()) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.