github.com/apache/beam/sdks/v2@v2.48.2/python/apache_beam/runners/interactive/display/pipeline_graph_renderer.py (about) 1 # 2 # Licensed to the Apache Software Foundation (ASF) under one or more 3 # contributor license agreements. See the NOTICE file distributed with 4 # this work for additional information regarding copyright ownership. 5 # The ASF licenses this file to You under the Apache License, Version 2.0 6 # (the "License"); you may not use this file except in compliance with 7 # the License. You may obtain a copy of the License at 8 # 9 # http://www.apache.org/licenses/LICENSE-2.0 10 # 11 # Unless required by applicable law or agreed to in writing, software 12 # distributed under the License is distributed on an "AS IS" BASIS, 13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 # See the License for the specific language governing permissions and 15 # limitations under the License. 16 # 17 18 """For rendering pipeline graph in HTML-compatible format. 19 20 This module is experimental. No backwards-compatibility guarantees. 21 """ 22 23 # pytype: skip-file 24 25 import abc 26 import os 27 import subprocess 28 from typing import TYPE_CHECKING 29 from typing import Optional 30 from typing import Type 31 32 from apache_beam.utils.plugin import BeamPlugin 33 34 if TYPE_CHECKING: 35 from apache_beam.runners.interactive.display.pipeline_graph import PipelineGraph 36 37 38 class PipelineGraphRenderer(BeamPlugin, metaclass=abc.ABCMeta): 39 """Abstract class for renderers, who decide how pipeline graphs are rendered. 40 """ 41 @classmethod 42 @abc.abstractmethod 43 def option(cls): 44 # type: () -> str 45 46 """The corresponding rendering option for the renderer. 47 """ 48 raise NotImplementedError 49 50 @abc.abstractmethod 51 def render_pipeline_graph(self, pipeline_graph): 52 # type: (PipelineGraph) -> str 53 54 """Renders the pipeline graph in HTML-compatible format. 55 56 Args: 57 pipeline_graph: (pipeline_graph.PipelineGraph) the graph to be rendererd. 58 59 Returns: 60 unicode, str or bytes that can be expressed as HTML. 61 """ 62 raise NotImplementedError 63 64 65 class MuteRenderer(PipelineGraphRenderer): 66 """Use this renderer to mute the pipeline display. 67 """ 68 @classmethod 69 def option(cls): 70 # type: () -> str 71 return 'mute' 72 73 def render_pipeline_graph(self, pipeline_graph): 74 # type: (PipelineGraph) -> str 75 return '' 76 77 78 class TextRenderer(PipelineGraphRenderer): 79 """This renderer simply returns the dot representation in text format. 80 """ 81 @classmethod 82 def option(cls): 83 # type: () -> str 84 return 'text' 85 86 def render_pipeline_graph(self, pipeline_graph): 87 # type: (PipelineGraph) -> str 88 return pipeline_graph.get_dot() 89 90 91 class PydotRenderer(PipelineGraphRenderer): 92 """This renderer renders the graph using pydot. 93 94 It depends on 95 1. The software Graphviz: https://www.graphviz.org/ 96 2. The python module pydot: https://pypi.org/project/pydot/ 97 """ 98 @classmethod 99 def option(cls): 100 # type: () -> str 101 return 'graph' 102 103 def render_pipeline_graph(self, pipeline_graph): 104 # type: (PipelineGraph) -> str 105 return pipeline_graph._get_graph().create_svg().decode("utf-8") # pylint: disable=protected-access 106 107 108 def get_renderer(option=None): 109 # type: (Optional[str]) -> Type[PipelineGraphRenderer] 110 111 """Get an instance of PipelineGraphRenderer given rendering option. 112 113 Args: 114 option: (str) the rendering option. 115 116 Returns: 117 (PipelineGraphRenderer) 118 """ 119 if option is None: 120 if os.name == 'nt': 121 exists = subprocess.call(['where', 'dot.exe']) == 0 122 else: 123 exists = subprocess.call(['which', 'dot']) == 0 124 125 if exists: 126 option = 'graph' 127 else: 128 option = 'text' 129 130 renderer = [ 131 r for r in PipelineGraphRenderer.get_all_subclasses() 132 if option == r.option() 133 ] 134 if len(renderer) == 0: 135 raise ValueError() 136 elif len(renderer) == 1: 137 return renderer[0]() 138 else: 139 raise ValueError('Found more than one renderer for option: %s', option)