1import json
2from importlib import resources
3from typing import TextIO
4
5from colander_data_converter.base.models import ColanderFeed
6from colander_data_converter.exporters.exporter import BaseExporter
7from colander_data_converter.exporters.template import TemplateExporter
8
9resource_package = __name__
10
11
[docs]
12class GraphvizExporter(BaseExporter):
13 """
14 Exporter for generating Graphviz_ DOT format files from Colander data feeds.
15
16 This exporter creates Graphviz-compatible DOT format output that can be used to
17 visualize entity relationships and hierarchies. It uses Jinja2_ templates for
18 rendering and supports customizable themes for styling the generated graphs.
19
20 The exporter automatically loads a default theme if none is provided, ensuring
21 consistent visual output. The theme controls various visual aspects like colors,
22 shapes, and styling attributes for different entity types.
23
24 .. _Graphviz: https://graphviz.org/
25 .. _Jinja2: https://jinja.palletsprojects.com/
26 """
27
[docs]
28 def __init__(self, feed: ColanderFeed, theme: dict = None):
29 """
30 Initialize the GraphvizExporter with feed data and optional theme configuration.
31
32 Sets up the exporter with the provided data feed and theme. If no theme is
33 provided, loads the default theme from the package resources. Initializes
34 the internal :py:class:`~colander_data_converter.exporters.template.TemplateExporter` with the Graphviz_ template.
35
36 Args:
37 feed (~colander_data_converter.base.models.ColanderFeed): The data feed containing entities to
38 be exported. This feed will be processed and converted to Graphviz DOT format.
39 theme (dict, optional): Theme configuration dictionary that controls the
40 visual styling of the generated graph. If None, the
41 default theme will be loaded automatically.
42 """
43 self.feed = feed
44 self.theme = theme
45 if not self.theme:
46 self.load_default_theme()
47 template_name = "graphviz.jinja2"
48 template_source_dir = resources.files(resource_package).joinpath("..").joinpath("data").joinpath("templates")
49 self.template_exporter = TemplateExporter(feed, str(template_source_dir), template_name)
50 self.feed.resolve_references()
51
[docs]
52 def load_default_theme(self):
53 """
54 Load the default theme configuration from the package resources.
55
56 Reads the default theme JSON file from the package's data/themes directory
57 and loads it into the theme attribute. This method is automatically called
58 during initialization if no custom theme is provided.
59 """
60 theme_file = (
61 resources.files(resource_package)
62 .joinpath("..")
63 .joinpath("data")
64 .joinpath("themes")
65 .joinpath("default.json")
66 )
67 with theme_file.open() as f:
68 self.theme = json.load(f)
69
[docs]
70 def export(self, output: TextIO, **kwargs):
71 """
72 Export the feed data as Graphviz DOT format to the specified output stream.
73
74 Renders the Colander data feed using the configured Jinja2_ template and theme
75 to produce Graphviz_ DOT format output. The theme is passed to the template
76 as a context variable, allowing the template to apply consistent styling.
77
78 Args:
79 output (TextIO): A text-based output stream where the DOT format content
80 will be written. This can be a file object, StringIO, or
81 any object implementing the TextIO interface.
82 **kwargs: Additional keyword arguments that will be passed to the underlying
83 :py:class:`~colander_data_converter.exporters.template.TemplateExporter`. These can be used to
84 provide additional context variables to the Jinja2 template.
85
86 Raises:
87 jinja2.TemplateError: If there are errors in the template syntax or rendering
88 jinja2.TemplateNotFound: If the Graphviz template file cannot be found
89 IOError: If there are issues writing to the output stream
90
91 Note:
92 The generated DOT format can be processed by Graphviz_ tools (dot, neato, etc.)
93 to create visual representations in various formats (PNG, SVG, PDF, etc.).
94 The theme dictionary is automatically passed to the template as the 'theme'
95 variable.
96 """
97 self.template_exporter.export(output, theme=self.theme)