code: def build_datatype_py(node):
      """
      Build datatype (graph_objs) class source code string for a datatype
      PlotlyNode
  
      Parameters
      ----------
      node : PlotlyNode
          The datatype node (node.is_datatype must evaluate to true) for which
          to build the datatype class
      Returns
      -------
      str
          String containing source code for the datatype class definition
      """
  
      # Validate inputs
      # ---------------
      assert node.is_compound
  
      # Handle template traces
      # ----------------------
      # We want template trace/layout classes like
      # plotly.graph_objs.layout.template.data.Scatter to map to the
      # corresponding trace/layout class (e.g. plotly.graph_objs.Scatter).
      # So rather than generate a class definition, we just import the
      # corresponding trace/layout class
      if node.parent_path_str == "layout.template.data":
          return f"from plotly.graph_objs import {node.name_datatype_class}"
      elif node.path_str == "layout.template.layout":
          return "from plotly.graph_objs import Layout"
  
      # Extract node properties
      # -----------------------
      undercase = node.name_undercase
      datatype_class = node.name_datatype_class
      literal_nodes = [
          n for n in node.child_literals if n.plotly_name in ["type"]
      ]
  
      # Initialze source code buffer
      # ----------------------------
      buffer = StringIO()
  
      # Imports
      # -------
      buffer.write(
          f"from plotly.basedatatypes "
          f"import {node.name_base_datatype} as _{node.name_base_datatype}\n"
      )
      buffer.write(f"import copy as _copy\n")
  
      # Write class definition
      # ----------------------
      buffer.write(
          f"""
  
  class {datatype_class}(_{node.name_base_datatype}):\n"""
      )
  
      # ### Layout subplot properties ###
      if datatype_class == "Layout":
          subplot_nodes = [
              node
              for node in node.child_compound_datatypes
              if node.node_data.get("_isSubplotObj", False)
          ]
          subplot_names = [n.name_property for n in subplot_nodes]
          buffer.write(
              f"""
      _subplotid_prop_names = {repr(subplot_names)}
  
      import re
      _subplotid_prop_re = re.compile(
          '^(' + '|'.join(_subplotid_prop_names) + r')(\d+)$')
  """
          )
  
          subplot_validator_names = [
              n.name_validator_class for n in subplot_nodes
          ]
  
          validator_csv = ", ".join(subplot_validator_names)
          subplot_dict_str = (
              "{"
              + ", ".join(
                  f"'{subname}': {valname}"
                  for subname, valname in zip(
                      subplot_names, subplot_validator_names
                  )
              )
              + "}"
          )
  
          buffer.write(
              f"""
      @property
      def _subplotid_validators(self):
          \"\"\"
          dict of validator classes for each subplot type
  
          Returns
          -------
          dict
          \"\"\"
          from plotly.validators.layout import ({validator_csv})
  
          return {subplot_dict_str}
  
      def _subplot_re_match(self, prop):
          return self._subplotid_prop_re.match(prop)
  """
          )
  
      child_datatype_nodes = node.child_datatypes
      subtype_nodes = child_datatype_nodes
      valid_props_list = sorted(
          [node.name_property for node in subtype_nodes + literal_nodes]
      )
      buffer.write(
          f"""
      # class properties
      # --------------------
      _parent_path_str = '{node.parent_path_str}'
      _path_str = '{node.path_str}'
      _valid_props = {{"{'", "'.join(valid_props_list)}"}}
  """
      )
  
      # ### Property definitions ###
      for subtype_node in subtype_nodes:
          if subtype_node.is_array_element:
              prop_type = (
                  f"tuple[plotly.graph_objs{node.dotpath_str}."
                  + f"{subtype_node.name_datatype_class}]"
              )
  
          elif subtype_node.is_compound:
              prop_type = (
                  f"plotly.graph_objs{node.dotpath_str}."
                  + f"{subtype_node.name_datatype_class}"
              )
  
              # remap template traces to regular traces
              prop_type = prop_type.replace("layout.template.data", "")
          elif subtype_node.is_mapped:
              prop_type = ""
          else:
              prop_type = get_typing_type(
                  subtype_node.datatype, subtype_node.is_array_ok
              )
  
          # #### Get property description ####
          raw_description = subtype_node.description
          property_description = "\n".join(
              textwrap.wrap(
                  raw_description,
                  initial_indent=" " * 8,
                  subsequent_indent=" " * 8,
                  width=79 - 8,
              )
          )
  
          # # #### Get validator description ####
          validator = subtype_node.get_validator_instance()
          if validator:
              validator_description = reindent_validator_description(
                  validator, 4
              )
  
              # #### Combine to form property docstring ####
              if property_description.strip():
                  property_docstring = f"""{property_description}
  
          {validator_description}"""
              else:
                  property_docstring = (
                      f"        {validator_description}"
                  )
          else:
              property_docstring = property_description
  
          # #### Write get property ####
          buffer.write(
              f"""\
  
      # {subtype_node.name_property}
      # {'-' * len(subtype_node.name_property)}
      @property
      def {subtype_node.name_property}(self):
          \"\"\"
  {property_docstring}
  
          Returns
          -------
          {prop_type}
          \"\"\"
          return self['{subtype_node.name_property}']"""
          )
  
          # #### Write set property ####
          buffer.write(
              f"""
  
      @{subtype_node.name_property}.setter
      def {subtype_node.name_property}(self, val):
          self['{subtype_node.name_property}'] = val\n"""
          )
  
          # ### Literals ###
      for literal_node in literal_nodes:
          buffer.write(
              f"""\
  
      # {literal_node.name_property}
      # {'-' * len(literal_node.name_property)}
      @property
      def {literal_node.name_property}(self):
          return self._props['{literal_node.name_property}']\n"""
          )
  
      # ### Private properties descriptions ###
      valid_props = {node.name_property for node in subtype_nodes}
      buffer.write(
          f"""
      # Self properties description
      # ---------------------------
      @property
      def _prop_descriptions(self):
          return \"\"\"\\"""
      )
  
      buffer.write(node.get_constructor_params_docstring(indent=8))
  
      buffer.write(
          f"""
          \"\"\""""
      )
  
      mapped_nodes = [n for n in subtype_nodes if n.is_mapped]
      mapped_properties = {
          n.plotly_name: n.relative_path for n in mapped_nodes
      }
      if mapped_properties:
          buffer.write(
              f"""
  
      _mapped_properties = {repr(mapped_properties)}"""
          )
  
      # ### Constructor ###
      buffer.write(
          f"""
      def __init__(self"""
      )
  
      add_constructor_params(
          buffer, subtype_nodes, prepend_extras=["arg"]
      )
  
      # ### Constructor Docstring ###
      header = f"Construct a new {datatype_class} object"
      class_name = (
          f"plotly.graph_objs"
          f"{node.parent_dotpath_str}."
          f"{node.name_datatype_class}"
      )
  
      extras = [
          (
              f"arg",
              f"dict of properties compatible with this constructor "
              f"or an instance of :class:`{class_name}`",
          )
      ]
  
      add_docstring(
          buffer,
          node,
          header=header,
          prepend_extras=extras,
          return_type=node.name_datatype_class,
      )
  
      buffer.write(
          f"""
          super({datatype_class}, self).__init__('{node.name_property}')
  
          if '_parent' in kwargs:
              self._parent = kwargs['_parent']
              return
  """
      )
  
      if datatype_class == "Layout":
          buffer.write(
              f"""
          # Override _valid_props for instance so that instance can mutate set
          # to support subplot properties (e.g. xaxis2)
          self._valid_props = {{"{'", "'.join(valid_props_list)}"}}
  """
          )
  
      buffer.write(
          f"""
          # Validate arg
          # ------------
          if arg is None:
              arg = {{}}
          elif isinstance(arg, self.__class__):
              arg = arg.to_plotly_json()
          elif isinstance(arg, dict):
              arg = _copy.copy(arg)
          else:
              raise ValueError(\"\"\"\\
  The first argument to the {class_name}
  constructor must be a dict or
  an instance of :class:`{class_name}`\"\"\")
  
          # Handle skip_invalid
          # -------------------
          self._skip_invalid = kwargs.pop('skip_invalid', False)
          self._validate = kwargs.pop('_validate', True)
          """
      )
  
      buffer.write(
          f"""
  
          # Populate data dict with properties
          # ----------------------------------"""
      )
      for subtype_node in subtype_nodes:
          name_prop = subtype_node.name_property
          buffer.write(
              f"""
          _v = arg.pop('{name_prop}', None)
          _v = {name_prop} if {name_prop} is not None else _v
          if _v is not None:
              self['{name_prop}'] = _v"""
          )
  
      # ### Literals ###
      if literal_nodes:
          buffer.write(
              f"""
  
          # Read-only literals
          # ------------------
  """
          )
          for literal_node in literal_nodes:
              lit_name = literal_node.name_property
              lit_val = repr(literal_node.node_data)
              buffer.write(
                  f"""
          self._props['{lit_name}'] = {lit_val}
          arg.pop('{lit_name}', None)"""
              )
  
      buffer.write(
          f"""
  
          # Process unknown kwargs
          # ----------------------
          self._process_kwargs(**dict(arg, **kwargs))
  
          # Reset skip_invalid
          # ------------------
          self._skip_invalid = False
  """
      )
  
      # Return source string
      # --------------------
      return buffer.getvalue()
  
file_path: plotly.py/packages/python/plotly/codegen/datatypes.py
imports: import os.path
  import textwrap
  from io import StringIO
  from codegen.utils import PlotlyNode, write_source_py
label_desc: Use image node_id 2 for calling a global function with example usage: build_datatype_py(node) and returns: buffer, str, node, str