Free — no signup required

Advanced Patterns: Detecting Attribute Calls and Imports

2 min read

Beyond Simple Name Checks

The eval() and print() examples work because those functions are called by their bare name. Real-world linting rules are often more complex. You may need to detect:

  • Attribute calls: os.system("rm -rf /") — the function is accessed as an attribute of a module.
  • Forbidden imports: import pickle — the violation is the import itself, not a call.
  • Structural patterns: Any function that is async def but does not have a timeout parameter.

Detecting attribute calls requires checking ast.Attribute nodes instead of ast.Name:

def visit_Call(self, node):
    # Detect os.system() calls
    if (
        isinstance(node.func, ast.Attribute)
        and node.func.attr == 'system'
        and isinstance(node.func.value, ast.Name)
        and node.func.value.id == 'os'
    ):
        print(f"Line {node.lineno}: Forbidden call to os.system()")
    self.generic_visit(node)

Detecting forbidden imports uses visit_Import and visit_ImportFrom:

FORBIDDEN_MODULES = {'pickle', 'shelve', 'marshal'}

def visit_Import(self, node):
    for alias in node.names:
        if alias.name in FORBIDDEN_MODULES:
            print(f"Line {node.lineno}: Forbidden import '{alias.name}'")

def visit_ImportFrom(self, node):
    if node.module in FORBIDDEN_MODULES:
        print(f"Line {node.lineno}: Forbidden import from '{node.module}'")

A note on robustness: The attribute call check above (node.func.value.id == 'os') only catches the case where os is used directly. It would miss import os as operating_system; operating_system.system(...). Full alias tracking requires scope analysis, which is where Pylint's Astroid library (with its type inference) becomes significantly more powerful than the bare ast module. For simple rules, ast is sufficient. For rules that need to follow variable assignments and aliases, use Astroid.

Interview Tip

If an interviewer asks "what are the limitations of AST-based linting?", the strong answer covers two areas: (1) alias tracking — an AST linter checking for os.system won't catch import os as o; o.system(...) without scope analysis, and (2) dynamic code — getattr(module, func_name)() is a call that cannot be statically analyzed because the function name is a runtime value. These limitations are why AST linting is complementary to, not a replacement for, runtime security controls.

This is one of 18 chapters

Every chapter is free — Kubernetes, Terraform, SRE, distributed systems, and more — with fast daily review built in.

Sign up free