Dependency Analysis and Visualization

Analyze cross-notebook imports and generate Mermaid.js dependency diagrams

Data Models


ModuleDependency


def ModuleDependency(
    source:str, target:str, import_type:str, imported_names:List[str]=<factory>
)->None:

Represents a dependency between modules


DependencyGraph


def DependencyGraph(
    modules:Dict[str, ModuleInfo]=<factory>, dependencies:List[ModuleDependency]=<factory>
)->None:

Dependency graph for a project

Import Analysis


extract_project_imports


def extract_project_imports(
    import_str:str, # Import statement
    project_name:str, # Project package name
)->Optional[ModuleDependency]: # Dependency if internal

Extract project-internal imports from an import statement


analyze_module_dependencies


def analyze_module_dependencies(
    module:ModuleInfo, # Module to analyze
    project_name:str, # Project package name
)->List[ModuleDependency]: # Dependencies found

Analyze a module’s imports to find project-internal dependencies

Building Dependency Graph


build_dependency_graph


def build_dependency_graph(
    path:Path=None, # Project path
    project_name:Optional[str]=None, # Override project name
)->DependencyGraph: # Dependency graph

Build a dependency graph for all modules in a project

Mermaid.js Diagram Generation


generate_mermaid_diagram


def generate_mermaid_diagram(
    graph:DependencyGraph, # Dependency graph
    direction:str='TD', # Diagram direction (TD/LR)
    show_imports:bool=False, # Show imported names
)->str: # Mermaid diagram code

Generate a Mermaid.js dependency diagram from a dependency graph


generate_dependency_matrix


def generate_dependency_matrix(
    graph:DependencyGraph, # Dependency graph
)->str: # Markdown table

Generate a dependency matrix showing which modules depend on which

Testing

Let’s test the dependency analysis on our project:

# Build dependency graph
graph = build_dependency_graph()
print(f"Found {len(graph.modules)} modules with {len(graph.dependencies)} dependencies")
Found 7 modules with 19 dependencies
# Show dependencies for each module
for module_name in sorted(graph.modules.keys()):
    deps = graph.get_module_dependencies(module_name)
    if deps:
        print(f"\n{module_name} depends on:")
        for dep in deps:
            print(f"  - {dep.target}: {', '.join(dep.imported_names)}")

api_docs depends on:
  - parsers: *
  - dependencies: *
  - tree: *
  - core: *

cli depends on:
  - parsers: *
  - tree: *
  - api_docs: *
  - dependencies: *

dependencies depends on:
  - dependencies: ModuleDependency
  - parsers: *
  - parsers: ModuleInfo
  - dependencies: generate_mermaid_diagram
  - core: *
  - dependencies: DependencyGraph

generators depends on:
  - tree: *
  - core: *

parsers depends on:
  - tree: extract_notebook_info
  - core: *

tree depends on:
  - core: *
# Test with a sample graph that includes reserved keywords
from cjm_nbdev_overview.dependencies import DependencyGraph, ModuleDependency, generate_mermaid_diagram
from cjm_nbdev_overview.parsers import ModuleInfo
from pathlib import Path

# Create test graph with reserved keywords
test_graph = DependencyGraph()

# Add modules including ones with reserved keywords
modules_data = [
    ("colors", "Colors"),
    ("core", "Core"),
    ("examples", "Practical Usage Examples"),
    ("layout", "Layout"),
    ("modern", "Modern"),
    ("style", "Style"),  # This should trigger the reserved keyword handling
    ("types", "Type Definitions"),
    ("validation", "Validation"),
    ("variants", "Variants")
]

for name, title in modules_data:
    module = ModuleInfo(
        path=Path(f"nbs/{name}.ipynb"),
        name=name,
        title=title,
        description=None,
        functions=[],
        classes=[],
        variables=[],
        imports=[]
    )
    test_graph.add_module(module)

# Add some test dependencies
test_dependencies = [
    ("colors", "validation"),
    ("colors", "types"),
    ("core", "types"),
    ("core", "validation"),
    ("examples", "validation"),
    ("examples", "variants"),
    ("examples", "core"),
    ("examples", "modern"),
    ("examples", "colors"),
    ("examples", "layout"),
    ("examples", "style"),  # This should work with escaped style
    ("layout", "core"),
    ("layout", "types"),
    ("layout", "validation"),
    ("modern", "core"),
    ("style", "core"),      # This should work with escaped style
    ("style", "types"),     # This should work with escaped style
    ("style", "colors"),    # This should work with escaped style
    ("style", "validation"),# This should work with escaped style
    ("variants", "core")
]

for source, target in test_dependencies:
    dep = ModuleDependency(source=source, target=target, import_type="from", imported_names=["*"])
    test_graph.add_dependency(dep)

# Generate diagram that should now work without parse errors
print("## Test Diagram with Reserved Keywords Handled\n")
print(generate_mermaid_diagram(test_graph, direction="LR"))
## Test Diagram with Reserved Keywords Handled

```mermaid
graph LR
    colors[colors<br/>Colors]
    core[core<br/>Core]
    examples[examples<br/>Practical Usage Examples]
    layout[layout<br/>Layout]
    modern[modern<br/>Modern]
    style_mod[style<br/>Style]
    types[types<br/>Type Definitions]
    validation[validation<br/>Validation]
    variants[variants<br/>Variants]

    colors --> validation
    colors --> types
    core --> types
    core --> validation
    examples --> validation
    examples --> variants
    examples --> core
    examples --> modern
    examples --> colors
    examples --> layout
    examples --> style_mod
    layout --> core
    layout --> types
    layout --> validation
    modern --> core
    style_mod --> core
    style_mod --> types
    style_mod --> colors
    style_mod --> validation
    variants --> core
```
# Test nested module imports
test_imports = [
    "from cjm_fasthtml_daisyui.core.base import DaisyComponent, DaisySize",
    "from cjm_fasthtml_daisyui.core.colors import SemanticColor",
    "from cjm_fasthtml_daisyui.core.behaviors import InteractiveMixin, FormControlMixin",
    "from cjm_fasthtml_daisyui.core.modifiers import StyleType, HasStyles",
    "from cjm_fasthtml_daisyui.core.htmx import HTMXComponent, HTMXAttrs",
    "from cjm_fasthtml_daisyui.core import *"
]

print("Testing nested module import extraction:")
print("-" * 50)
for import_str in test_imports:
    dep = extract_project_imports(import_str, "cjm_fasthtml_daisyui")
    if dep:
        print(f"Import: {import_str}")
        print(f"  Target module: {dep.target}")
        print(f"  Imported names: {', '.join(dep.imported_names)}")
        print()
Testing nested module import extraction:
--------------------------------------------------
Import: from cjm_fasthtml_daisyui.core.base import DaisyComponent, DaisySize
  Target module: core.base
  Imported names: DaisyComponent, DaisySize

Import: from cjm_fasthtml_daisyui.core.colors import SemanticColor
  Target module: core.colors
  Imported names: SemanticColor

Import: from cjm_fasthtml_daisyui.core.behaviors import InteractiveMixin, FormControlMixin
  Target module: core.behaviors
  Imported names: InteractiveMixin, FormControlMixin

Import: from cjm_fasthtml_daisyui.core.modifiers import StyleType, HasStyles
  Target module: core.modifiers
  Imported names: StyleType, HasStyles

Import: from cjm_fasthtml_daisyui.core.htmx import HTMXComponent, HTMXAttrs
  Target module: core.htmx
  Imported names: HTMXComponent, HTMXAttrs

Import: from cjm_fasthtml_daisyui.core import *
  Target module: core
  Imported names: *
# Test with a graph that simulates the nested module structure from the bug report
nested_test_graph = DependencyGraph()

# Add nested modules like in the bug report
nested_modules = [
    ("actions.button", "Button"),
    ("core.animation", "Animation & Transitions"),
    ("core.base", "Core Base Classes"),
    ("core.behaviors", "Behavior States"),
    ("core.colors", "Colors"),
    ("core.config", "Configuration"),
    ("core.factory", "Component Factory"),
    ("core.htmx", "HTMX Integration"),
    ("core.modifiers", "Style Modifiers"),
    ("core.parts", "Component Parts"),
    ("core.placement", "Placement & Direction"),
    ("core.resources", "Resources"),
    ("core.testing", "Testing"),
    ("core.validation", "Validation"),
    ("core.variants", "Variant System")
]

for name, title in nested_modules:
    module = ModuleInfo(
        path=Path(f"nbs/{name.replace('.', '/')}.ipynb"),
        name=name,
        title=title,
        description=None,
        functions=[],
        classes=[],
        variables=[],
        imports=[]
    )
    nested_test_graph.add_module(module)

# Add dependencies from actions.button to various core modules
dependencies_to_add = [
    ("actions.button", "core.base", ["DaisyComponent", "DaisySize"]),
    ("actions.button", "core.colors", ["SemanticColor"]),
    ("actions.button", "core.behaviors", ["InteractiveMixin", "FormControlMixin"]),
    ("actions.button", "core.modifiers", ["StyleType", "HasStyles"]),
    ("actions.button", "core.htmx", ["HTMXComponent", "HTMXAttrs"]),
    # Self-dependencies within core
    ("core.base", "core.colors", ["*"]),
    ("core.factory", "core.base", ["*"]),
    ("core.htmx", "core.base", ["*"]),
    ("core.testing", "core.validation", ["*"]),
    ("core.validation", "core.config", ["*"])
]

for source, target, imported in dependencies_to_add:
    dep = ModuleDependency(source=source, target=target, import_type="from", imported_names=imported)
    nested_test_graph.add_dependency(dep)

# Generate the corrected diagram
print("## Fixed Mermaid Diagram with Nested Modules\n")
print(generate_mermaid_diagram(nested_test_graph, direction="LR"))
print(f"\n*{len(nested_test_graph.dependencies)} cross-module dependencies detected*")
## Fixed Mermaid Diagram with Nested Modules

```mermaid
graph LR
    actions_button[actions.button<br/>Button]
    core_animation[core.animation<br/>Animation & Transitions]
    core_base[core.base<br/>Core Base Classes]
    core_behaviors[core.behaviors<br/>Behavior States]
    core_colors[core.colors<br/>Colors]
    core_config[core.config<br/>Configuration]
    core_factory[core.factory<br/>Component Factory]
    core_htmx[core.htmx<br/>HTMX Integration]
    core_modifiers[core.modifiers<br/>Style Modifiers]
    core_parts[core.parts<br/>Component Parts]
    core_placement[core.placement<br/>Placement & Direction]
    core_resources[core.resources<br/>Resources]
    core_testing[core.testing<br/>Testing]
    core_validation[core.validation<br/>Validation]
    core_variants[core.variants<br/>Variant System]

    actions_button --> core_base
    actions_button --> core_colors
    actions_button --> core_behaviors
    actions_button --> core_modifiers
    actions_button --> core_htmx
    core_base --> core_colors
    core_factory --> core_base
    core_htmx --> core_base
    core_testing --> core_validation
    core_validation --> core_config
```

*10 cross-module dependencies detected*
# Generate dependency matrix
print("\n## Dependency Matrix\n")
print(generate_dependency_matrix(graph))

## Dependency Matrix

| Module | api_docs | cli | core | dependencies | generators | parsers | tree |
|--------|----|----|----|----|----|----|----|
| api_docs |   |   | ✓ | ✓ |   | ✓ | ✓ |
| cli | ✓ |   |   | ✓ |   | ✓ | ✓ |
| core |   |   |   |   |   |   |   |
| dependencies |   |   | ✓ | ✓ |   | ✓ |   |
| generators |   |   | ✓ |   |   |   | ✓ |
| parsers |   |   | ✓ |   |   |   | ✓ |
| tree |   |   | ✓ |   |   |   |   |