Skip to content

Commit

Permalink
Add detection of alternative imports
Browse files Browse the repository at this point in the history
  • Loading branch information
zz1874 committed Oct 16, 2023
1 parent 4e20ed7 commit de55726
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 4 deletions.
85 changes: 85 additions & 0 deletions PyPI_analysis/detect_imports.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,12 +98,97 @@ def conditional_imports(parsed_code: ast.Module):
)
}

def alternative_imports(parsed_code: ast.Module):
for node in ast.walk(parsed_code):
if isinstance(node, ast.Try):
if isinstance(node.handlers, list) and len(node.handlers) == 1:
handler = node.handlers[0]
if (
isinstance(handler.type, ast.Name)
and handler.type.id == "ImportError"
and isinstance(handler.body, list)
and all(
isinstance(handler_body, ast.Import)
for handler_body in handler.body
)
):
if isinstance(node.body, list):
for node_import in node.body:
if isinstance(node_import, ast.Import):
for alias in node_import.names:
name = alias.name.split(".", 1)[0]
if is_external_import(name):
yield {
"Alternative imports": ParsedImport(
name=name,
source=source.supply(
lineno=node.lineno
),
)
}

elif isinstance(node_import, ast.ImportFrom):
# Relative imports are always relative to the current package, and
# will therefore not resolve to a third-party package.
# They are therefore uninteresting to us.
if (
node_import.level == 0
and node_import.module is not None
):
name = node_import.module.split(".", 1)[
0
]
if is_external_import(name):
yield {
"Alternative imports": ParsedImport(
name=name,
source=source.supply(
lineno=node.lineno
),
)
}
for node_import in handler.body:
if isinstance(node_import, ast.Import):
for alias in node_import.names:
name = alias.name.split(".", 1)[0]
if is_external_import(name):
yield {
"Alternative imports": ParsedImport(
name=name,
source=source.supply(
lineno=node.lineno
),
)
}

elif isinstance(node_import, ast.ImportFrom):
# Relative imports are always relative to the current package, and
# will therefore not resolve to a third-party package.
# They are therefore uninteresting to us.
if (
node_import.level == 0
and node_import.module is not None
):
name = node_import.module.split(".", 1)[
0
]
if is_external_import(name):
yield {
"Alternative imports": ParsedImport(
name=name,
source=source.supply(
lineno=node.lineno
),
)
}

try:
parsed_code = ast.parse(code, filename=str(source.path))
except SyntaxError as exc:
logger.error(f"Could not parse code from {source}: {exc}")
return
yield from conditional_imports(parsed_code)
yield from alternative_imports(parsed_code)


def parse_notebook_file(
Expand Down
8 changes: 4 additions & 4 deletions PyPI_analysis/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,11 +148,11 @@ def render_sources() -> Iterator[str]:
def render_imports() -> Iterator[str]:
if detailed:
for imp in self.imports:
yield f"Conditional imports: {imp['Conditional imports'].source}: {imp['Conditional imports'].name}"
yield f"{list(imp.keys())[0]}: {imp[list(imp.keys())[0]].source}: {imp[list(imp.keys())[0]].name}"
else:
unique_imports = {
"Conditional imports: " + i["Conditional imports"].name
for i in self.imports
list(imp.keys())[0] + ": " + imp[list(imp.keys())[0]].name
for imp in self.imports
}
yield from sorted(unique_imports)

Expand All @@ -165,7 +165,7 @@ def output(lines: Iterator[str]) -> None:
@staticmethod
def success_message(check_undeclared: bool, check_unused: bool) -> Optional[str]:
"""Returns the message to print when the analysis finds no errors."""
return "No conditional imports detected."
return "No conditional or alternative imports detected."


def assign_exit_code(analysis: Analysis) -> int:
Expand Down

0 comments on commit de55726

Please sign in to comment.