importnb
contains a questionable design choice that allows for fuzzy file finding. importnb
imports notebooks and those notebook file names contains can characters like or
-
or perhaps they start with numbers like a blog post. These files names would confuse the start import mechanisms.
The biggest trouble with the Fuzzy Finder is that imports are non deterministic.
importnb
allows fuzzy search patterns using _
and __
to replace the names with ?
and ??
respectively.
Our new completer will consider our prior blog posts on creating IPython completers and Markdown. Our completer should predict modules even when the code is indented.
We have a hypothesis that fuzzy completion will assist the user in using Fuzzy patterns.
Below is a pattern to discover this blog post.
pattern = '__Fuzzy_import__weird__'
The underscores in the pattern are replaced in the following manner
pattern.replace('__', '*').replace('_', '?')
'*Fuzzy?import*weird*'
Using the importnb
fuzzy find logic we will discover this post.
from fnmatch import fnmatch
from pathlib import Path
import string
from importnb.finder import fuzzy_file_search
files = fuzzy_file_search('', pattern)
files
[WindowsPath('2018-07-10-Fuzzy-importing-files-with-weird-characters.ipynb')]
def fuzzify_string(str, *, fuzzified = ''):
return str[0] in string.ascii_letters + '_' and str[0] or '_' \
+ ''.join('_' if letter in ' -' else letter for letter in str[1:])
event.symbol
. align_match
will replace the beginning portion with the required prefix as applied to our fuzzy search criterion. def align_match(match, prefix, *, i=0):
pattern = prefix.replace('__',' *').replace('_', '?').strip()
for i in range(len(match)):
if fnmatch(match[:i], pattern): break
return prefix + match[i:]
predict_fuzzy
will take a fully qualified fuzzy name completions. def predict_fuzzy(fullname):
package, paths, specs = '', [], []
if '.' in fullname:
package, fullname = fullname.rsplit('.', 1)
fullname = fullname.strip()
try:
module = __import__('importlib').import_module(package)
paths.append(Path(module.__file__).parent)
except: ...
else: paths = map(Path, __import__('sys').path)
query_name = fullname
while not query_name.endswith('__'): query_name += '_'
for path in paths: specs.extend(
str(object.relative_to(path).with_suffix(''))
for object in fuzzy_file_search(path, query_name))
return set((package and package + '.' or '') + align_match(fuzzify_string(spec), fullname) for spec in specs)
Ø = __name__ == '__main__';
if Ø: ip = get_ipython()
from pathlib import Path
def event(self, event):
event.line = event.line.lstrip()
symbol = event.symbol
if event.line.startswith('from'):
if ' import' in event.line:
package = event.line.split(' import', 1)[0].lstrip().lstrip('from').lstrip()
return [object.lstrip(package).lstrip('.') for object in predict_fuzzy('.'.join((package, symbol)))]
return predict_fuzzy(symbol)
def load_ipython_extension(ip):
ip.set_hook('complete_command', event, str_key="aimport", priority=25)
ip.set_hook('complete_command', event, str_key="import", priority=25)
ip.set_hook('complete_command', event, str_key="%reload_ext", priority=25)
ip.set_hook('complete_command', event, str_key="%load_ext", priority=25)
ip.set_hook('complete_command', event, str_key="from", priority=25)
Ø and print(
ip.complete('deathbeds.__complete', 'import deathbeds.__complete'), ip.complete('__complete', 'import __complete'), ip.complete('req', '\timport req'))
('deathbeds.__complete', []) ('__complete', []) ('req', [])
if Ø:
# load_ipython_extension(get_ipython())
%reload_ext deathbeds._018_07_03_Custom_IPython_Completer_for_Indented_Code
Ø and print(
ip.complete('deathbeds.__complete', 'import deathbeds.__complete'),
ip.complete('__complete', 'import __complete'),
ip.complete('req', '\timport req'),
ip.complete('__complete', 'from deathbeds import __complete'))
('deathbeds.__complete', ['deathbeds.__completer_for_Indented_Code']) ('__complete', []) ('req', ['requests', 'requests_cache']) ('__complete', ['__completer_for_Indented_Code'])
if Ø: import disqus