Dependency Analysis and Visualization

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

Data Models


source

ModuleDependency

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

Represents a dependency between modules


source

DependencyGraph

 DependencyGraph
                  (modules:Dict[str,cjm_nbdev_overview.parsers.ModuleInfo]
                  =<factory>,
                  dependencies:List[__main__.ModuleDependency]=<factory>)

Dependency graph for a project

Import Analysis


source

extract_project_imports

 extract_project_imports (import_str:str, project_name:str)

Extract project-internal imports from an import statement

Type Details
import_str str Import statement
project_name str Project package name
Returns Optional Dependency if internal

source

analyze_module_dependencies

 analyze_module_dependencies
                              (module:cjm_nbdev_overview.parsers.ModuleInf
                              o, project_name:str)

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

Type Details
module ModuleInfo Module to analyze
project_name str Project package name
Returns List Dependencies found

Building Dependency Graph


source

build_dependency_graph

 build_dependency_graph (path:pathlib.Path=None,
                         project_name:Optional[str]=None)

Build a dependency graph for all modules in a project

Type Default Details
path Path None Project path
project_name Optional None Override project name
Returns DependencyGraph Dependency graph

Mermaid.js Diagram Generation


source

generate_mermaid_diagram

 generate_mermaid_diagram (graph:__main__.DependencyGraph,
                           direction:str='TD', show_imports:bool=False)

Generate a Mermaid.js dependency diagram from a dependency graph

Type Default Details
graph DependencyGraph Dependency graph
direction str TD Diagram direction (TD/LR)
show_imports bool False Show imported names
Returns str Mermaid diagram code

source

generate_dependency_matrix

 generate_dependency_matrix (graph:__main__.DependencyGraph)

Generate a dependency matrix showing which modules depend on which

Type Details
graph DependencyGraph Dependency graph
Returns str Markdown table

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 |   |   | ✓ |   |   |   |   |