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)