Skip to content

Commit fb3caaf

Browse files
committed
Added python_package_imports functioN
1 parent 4e5e817 commit fb3caaf

File tree

2 files changed

+77
-1
lines changed

2 files changed

+77
-1
lines changed

d8s_python/python_data.py

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import argparse
22
import re
33
import sys
4-
from typing import Any, Iterator, List, Union
4+
from ast import Import, ImportFrom
5+
from typing import Any, Dict, Iterator, List, Union
6+
7+
from .ast_data import python_ast_objects_of_type
58

69

710
# @decorators.map_firstp_arg
@@ -359,3 +362,44 @@ def python_type_name(python_type: type) -> str:
359362
def python_object_type_to_word(python_object: Any) -> str:
360363
"""Convert the given python type to a string."""
361364
return python_type_name(type(python_object))
365+
366+
367+
def _get_importfrom_module_name(node: ImportFrom) -> str:
368+
"""Extract the module name from an ast.ImportFrom node.
369+
370+
The module name on the ast.ImportFrom node can be None for relative imports
371+
In this case, this function will return the name as the dots from the import statement.
372+
A few examples:
373+
"from requests import get" -> "get"
374+
"from . import *" -> "."
375+
"from .. import *" -> ".."
376+
"from .foo import bar" -> "foo"
377+
"""
378+
if node.module is None:
379+
module_name = "." * node.level
380+
else:
381+
module_name = node.module
382+
383+
return module_name
384+
385+
386+
def python_package_imports(code: str) -> Dict[str, List[str]]:
387+
"""Return a dictionary containing the names of all imported modules."""
388+
# Start with the Import nodes.
389+
# These will always have an empty list of submodules
390+
# so we can just overwrite them without losing any data
391+
modules = dict()
392+
nodes = python_ast_objects_of_type(code, Import)
393+
for node in nodes:
394+
for alias in node.names:
395+
modules[alias.name] = []
396+
397+
# Now for the ImportFrom nodes
398+
importfrom_nodes = python_ast_objects_of_type(code, ImportFrom)
399+
for node in importfrom_nodes:
400+
module_name = _get_importfrom_module_name(node)
401+
402+
for alias in node.names:
403+
modules.setdefault(module_name, []).append(alias.name)
404+
405+
return modules

tests/test_python_data.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
python_object_source_code,
2929
python_object_source_file,
3030
python_object_type_to_word,
31+
python_package_imports,
3132
python_sort_type_list_by_name,
3233
python_stack_local_data,
3334
python_todos,
@@ -482,3 +483,34 @@ def test_python_function_blocks__async_functions():
482483
'def foo(n):\n """Foo."""\n return n',
483484
'async def bar(n):\n """Some async func."""\n return n',
484485
]
486+
487+
488+
def test_python_package_imports():
489+
s = ''
490+
assert python_package_imports(s) == {}
491+
492+
s = '''import requests'''
493+
assert python_package_imports(s) == {'requests': []}
494+
495+
s = '''from math import sqrt'''
496+
assert python_package_imports(s) == {'math': ['sqrt']}
497+
498+
s = '''
499+
import os as _
500+
import sys,random'''
501+
assert python_package_imports(s) == {'os': [], 'sys': [], 'random': []}
502+
503+
s = '''
504+
from democritus_dates import date_parse, date_now
505+
import requests
506+
507+
print(date_parse('2 days from now'))
508+
'''
509+
assert python_package_imports(s) == {'democritus_dates': ['date_parse', 'date_now'], 'requests': []}
510+
511+
s = '''
512+
from . import everything
513+
from ..foo.bar import *
514+
'''
515+
516+
assert python_package_imports(s) == {'.': ['everything'], 'foo.bar': ['*']}

0 commit comments

Comments
 (0)