content: import collections
  
  
  def get_named_tuple(name, dict):
      return collections.namedtuple(name, dict.keys())(*dict.values())
  
  
  Align = get_named_tuple(
      "align",
      {"default": "", "left": "<", "right": ">", "center": "^", "right_sign": "="},
  )
  
  Group = get_named_tuple("group", {"no": "", "yes": ","})
  
  Padding = get_named_tuple("padding", {"no": "", "yes": "0"})
  
  Prefix = get_named_tuple(
      "prefix",
      {
          "yocto": 10**-24,
          "zepto": 10**-21,
          "atto": 10**-18,
          "femto": 10**-15,
          "pico": 10**-12,
          "nano": 10**-9,
          "micro": 10**-6,
          "milli": 10**-3,
          "none": None,
          "kilo": 10**3,
          "mega": 10**6,
          "giga": 10**9,
          "tera": 10**12,
          "peta": 10**15,
          "exa": 10**18,
          "zetta": 10**21,
          "yotta": 10**24,
      },
  )
  
  Scheme = get_named_tuple(
      "scheme",
      {
          "default": "",
          "decimal": "r",
          "decimal_integer": "d",
          "decimal_or_exponent": "g",
          "decimal_si_prefix": "s",
          "exponent": "e",
          "fixed": "f",
          "percentage": "%",
          "percentage_rounded": "p",
          "binary": "b",
          "octal": "o",
          "lower_case_hex": "x",
          "upper_case_hex": "X",
          "unicode": "c",
      },
  )
  
  Sign = get_named_tuple(
      "sign",
      {"default": "", "negative": "-", "positive": "+", "parantheses": "(", "space": " "},
  )
  
  Symbol = get_named_tuple(
      "symbol", {"no": "", "yes": "$", "binary": "#b", "octal": "#o", "hex": "#x"}
  )
  
  Trim = get_named_tuple("trim", {"no": "", "yes": "~"})
  
  
  class Format:
      def __init__(self, **kwargs):
          self._locale = {}
          self._nully = ""
          self._prefix = Prefix.none
          self._specifier = {
              "align": Align.default,
              "fill": "",
              "group": Group.no,
              "width": "",
              "padding": Padding.no,
              "precision": "",
              "sign": Sign.default,
              "symbol": Symbol.no,
              "trim": Trim.no,
              "type": Scheme.default,
          }
  
          valid_methods = [
              m for m in dir(self.__class__) if m[0] != "_" and m != "to_plotly_json"
          ]
  
          for kw, val in kwargs.items():
              if kw not in valid_methods:
                  raise TypeError(
                      "{0} is not a format method. Expected one of".format(kw),
                      str(list(valid_methods)),
                  )
  
              getattr(self, kw)(val)
  
      def _validate_char(self, value):
          self._validate_string(value)
  
          if len(value) != 1:
              raise ValueError("expected value to a string of length one")
  
      def _validate_non_negative_integer_or_none(self, value):
          if value is None:
              return
  
          if not isinstance(value, int):
              raise TypeError("expected value to be an integer")
  
          if value < 0:
              raise ValueError("expected value to be non-negative", str(value))
  
      def _validate_named(self, value, named_values):
          if value not in named_values:
              raise TypeError("expected value to be one of", str(list(named_values)))
  
      def _validate_string(self, value):
          if not isinstance(value, (str, "".__class__)):
              raise TypeError("expected value to be a string")
  
      # Specifier
      def align(self, value):
          self._validate_named(value, Align)
  
          self._specifier["align"] = value
          return self
  
      def fill(self, value):
          self._validate_char(value)
  
          self._specifier["fill"] = value
          return self
  
      def group(self, value):
          if isinstance(value, bool):
              value = Group.yes if value else Group.no
  
          self._validate_named(value, Group)
  
          self._specifier["group"] = value
          return self
  
      def padding(self, value):
          if isinstance(value, bool):
              value = Padding.yes if value else Padding.no
  
          self._validate_named(value, Padding)
  
          self._specifier["padding"] = value
          return self
  
      def padding_width(self, value):
          self._validate_non_negative_integer_or_none(value)
  
          self._specifier["width"] = value if value is not None else ""
          return self
  
      def precision(self, value):
          self._validate_non_negative_integer_or_none(value)
  
          self._specifier["precision"] = ".{0}".format(value) if value is not None else ""
          return self
  
      def scheme(self, value):
          self._validate_named(value, Scheme)
  
          self._specifier["type"] = value
          return self
  
      def sign(self, value):
          self._validate_named(value, Sign)
  
          self._specifier["sign"] = value
          return self
  
      def symbol(self, value):
          self._validate_named(value, Symbol)
  
          self._specifier["symbol"] = value
          return self
  
      def trim(self, value):
          if isinstance(value, bool):
              value = Trim.yes if value else Trim.no
  
          self._validate_named(value, Trim)
  
          self._specifier["trim"] = value
          return self
  
      # Locale
      def symbol_prefix(self, value):
          self._validate_string(value)
  
          if "symbol" not in self._locale:
              self._locale["symbol"] = [value, ""]
          else:
              self._locale["symbol"][0] = value
  
          return self
  
      def symbol_suffix(self, value):
          self._validate_string(value)
  
          if "symbol" not in self._locale:
              self._locale["symbol"] = ["", value]
          else:
              self._locale["symbol"][1] = value
  
          return self
  
      def decimal_delimiter(self, value):
          self._validate_char(value)
  
          self._locale["decimal"] = value
          return self
  
      def group_delimiter(self, value):
          self._validate_char(value)
  
          self._locale["group"] = value
          return self
  
      def groups(self, groups):
          groups = (
              groups
              if isinstance(groups, list)
              else [groups]
              if isinstance(groups, int)
              else None
          )
  
          if not isinstance(groups, list):
              raise TypeError("expected groups to be an integer or a list of integers")
          if len(groups) == 0:
              raise ValueError(
                  "expected groups to be an integer or a list of " "one or more integers"
              )
  
          for group in groups:
              if not isinstance(group, int):
                  raise TypeError("expected entry to be an integer")
  
              if group <= 0:
                  raise ValueError("expected entry to be a non-negative integer")
  
          self._locale["grouping"] = groups
          return self
  
      # Nully
      def nully(self, value):
          self._nully = value
          return self
  
      # Prefix
      def si_prefix(self, value):
          self._validate_named(value, Prefix)
  
          self._prefix = value
          return self
  
      def to_plotly_json(self):
          f = {}
          f["locale"] = self._locale.copy()
          f["nully"] = self._nully
          f["prefix"] = self._prefix
          aligned = self._specifier["align"] != Align.default
          f["specifier"] = "{}{}{}{}{}{}{}{}{}{}".format(
              self._specifier["fill"] if aligned else "",
              self._specifier["align"],
              self._specifier["sign"],
              self._specifier["symbol"],
              self._specifier["padding"],
              self._specifier["width"],
              self._specifier["group"],
              self._specifier["precision"],
              self._specifier["trim"],
              self._specifier["type"],
          )
  
          return f
  