Skip to content

Bug: is_submodule uses substring match instead of prefix match, causing spurious DAG nodes #1484

@spock-yh

Description

@spock-yh

is_submodule() in hamilton/graph_utils.py (line 24) uses a substring match (in) instead of a proper prefix match, causing imported functions to be incorrectly included as DAG nodes when the user module's name is a substring of the imported function's module path.

Current behavior

is_submodule() uses parent.__name__ in child.__name__ (substring check). When a user module's name appears anywhere within an imported function's fully-qualified module path, find_functions() incorrectly treats that imported function as part of the user's DAG.

For example, a module named modifiers that imports source and value from hamilton.function_modifiers produces 3 nodes instead of 1:

my_func (from modifiers)
source (from hamilton.function_modifiers.dependencies)
value (from hamilton.function_modifiers.dependencies)

This happens because "modifiers" in "hamilton.function_modifiers.dependencies" evaluates to True.

The buggy code at hamilton/graph_utils.py:24:

def is_submodule(child: ModuleType, parent: ModuleType):
    return parent.__name__ in child.__name__

Stack Traces

N/A — no exception is raised; the bug silently adds spurious nodes.

Screenshots

N/A

Steps to replicate behavior

  1. Create a user module named modifiers.py:
    from hamilton.function_modifiers import source, value
    
    def my_func(input_data: int) -> int:
        return input_data * 2
  2. Inspect what find_functions discovers:
    import modifiers
    from hamilton.graph_utils import find_functions
    
    functions = find_functions(modifiers)
    for name, fn in functions:
        print(f'{name} (from {fn.__module__})')
  3. Observe that source and value are incorrectly included as DAG nodes.

This affects any user module whose name is a substring of an imported function's module path (e.g. function, ton, ilton, etc.).

Library & System Information

  • Python version: 3.10+
  • Hamilton version: 1.89.0 (current main branch)
  • OS: all (bug is in pure Python logic)

Expected behavior

Only my_func should appear as a DAG node. Imported functions like source and value should be excluded because hamilton.function_modifiers.dependencies is not a submodule of modifiers.

A correct implementation would use a prefix check instead of a substring check:

def is_submodule(child: ModuleType, parent: ModuleType):
    return child.__name__ == parent.__name__ or child.__name__.startswith(parent.__name__ + ".")

Additional context

The bug exists because in tests for substring containment, not hierarchical module relationships. The fix is a one-line change replacing the in operator with a startswith prefix check plus an equality check for the same-module case.

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions