Usage:
regs = [container.registry]+list(container.child_registries)
x = render(regs, type(container))
print(x)
from collections import defaultdict | |
from collections.abc import Iterable | |
from dataclasses import dataclass | |
from uuid import uuid4 | |
from dishka.dependency_source.factory import Factory | |
from dishka.entities.key import DependencyKey | |
from dishka.entities.scope import BaseScope | |
from dishka.registry import Registry | |
@dataclass | |
class Class: | |
id: str | |
name: str | |
component: str | |
scope: BaseScope | |
dependencies: list[str] | |
final: bool = False | |
auto: bool = False | |
def factory_to_class(factory: Factory, keys: dict[DependencyKey, str], | |
container: type): | |
hint = factory.provides.type_hint | |
scope = factory.scope | |
if name := getattr(hint, "__name__", None): | |
name = getattr(hint, "__module__", None) + "\n" + name | |
else: | |
name = str(hint) | |
return Class( | |
id=keys[factory.provides], | |
name=name, | |
scope=scope, | |
component=factory.provides.component, | |
dependencies=[ | |
keys[dep] for dep in factory.dependencies | |
], | |
auto=hint==container, | |
) | |
def render_class(cls: Class): | |
if cls.final: | |
return f""" {cls.id}(["⛳ {cls.name}"])\n""" | |
else: | |
return f""" {cls.id}["🧩 {cls.name}"]\n""" | |
def render_relations(cls: Class): | |
return "".join( | |
f" {dep} --> {cls.id}\n" for dep in cls.dependencies | |
) | |
def render_component(component: str, scope: BaseScope, classes: list[Class]): | |
res = "\n" | |
comp_id = component_id(scope, component) | |
res += f"style {comp_id} {COMPONENT_COLOR}\n" | |
res += f"""subgraph {comp_id}["Component {component!r}"]\n""" | |
for cls in classes: | |
if cls.auto: | |
continue | |
res +=render_class(cls) | |
res += "end\n" | |
return res | |
def component_id(scope: BaseScope, component: str): | |
return f"{component}_{scope.name}" | |
def render_scope(prev_scope: BaseScope | None, scope: BaseScope, | |
components: Iterable[str], color: str): | |
res = f"style {scope.name} {color}\n" | |
res += f"""subgraph {scope.name}["{scope}"]\n""" | |
for component in components: | |
comp_id = component_id(scope, component) | |
res += f" subgraph {comp_id}\n" | |
res += " end\n" | |
res += "end\n" | |
if prev_scope is not None: | |
res += f"{prev_scope.name} -.-> {scope.name}\n\n" | |
return res | |
SCOPE_COLORS = [ | |
"fill:#FFCDD2,stroke:#EF9A9A", | |
"fill:#E1BEE7,stroke:#CE93D8", | |
"fill:#C5CAE9,stroke:#9FA8DA", | |
"fill:#B3E5FC,stroke:#81D4FA", | |
] | |
COMPONENT_COLOR = "fill:#ECEFF1,stroke:#B0BEC5" | |
def render(registries: list[Registry], container): | |
keys: dict[DependencyKey, str] = {} | |
all_classes: dict[Factory, Class] = {} | |
requested = set() | |
components: dict[tuple[str, BaseScope], list[Class]] = defaultdict(list) | |
for registry in registries: | |
for factory in registry.factories.values(): | |
keys[factory.provides] = str(uuid4()).replace("-", "_") | |
res = "flowchart LR\n" | |
for registry in registries: | |
for factory in registry.factories.values(): | |
cls = factory_to_class(factory, keys, container) | |
all_classes[factory] = cls | |
for dep in factory.dependencies: | |
requested.add(dep) | |
res += render_relations(cls) | |
component_key = factory.provides.component, factory.scope | |
components[component_key].append(cls) | |
for registry in registries: | |
for factory in registry.factories.values(): | |
if factory.provides not in requested: | |
all_classes[factory].final = True | |
for (component, scope), classes in components.items(): | |
res += render_component(component, scope, classes) | |
scopes: dict[BaseScope, list[str]] = defaultdict(list) | |
for component, scope in components: | |
scopes[scope].append(component) | |
prev_scope = None | |
for color, (scope, scomponents) in zip(SCOPE_COLORS, scopes.items(), strict=False): | |
res += render_scope(prev_scope, scope, scomponents, color) | |
prev_scope = scope | |
return res |