text: """This module sets up type classes like DictType and ListType that
  define types for arbitrary objects.  The main use case is to specify
  the types of Zulip events that come from send_event calls for
  verification in test_events.py; in most other code paths we'll want to
  use a TypedDict.
  
  This module consists of workarounds for cases where we cannot express
  the level of detail we desire or do comparison with OpenAPI types
  easily with the native Python type system.
  """
  
  from dataclasses import dataclass
  from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple
  
  from django.core.exceptions import ValidationError
  from django.core.validators import URLValidator
  
  
  def indent(s: str) -> str:
      padding = "    "
      parts = s.split("\n")
      return "\n".join(padding + part for part in parts)
  
  
  @dataclass
  class DictType:
      """Dictionary is validated as having all required keys, all keys
      accounted for in required_keys and optional_keys, and recursive
      validation of types of fields.
      """
      def __init__(
          self,
          required_keys: Sequence[Tuple[str, Any]],
          optional_keys: Sequence[Tuple[str, Any]] = [],
      ) -> None:
          self.required_keys = required_keys
          self.optional_keys = optional_keys
  
      def check_data(self, var_name: str, val: Dict[str, Any]) -> None:
          if not isinstance(val, dict):
              raise AssertionError(f"{var_name} is not a dict")
  
          for k in val:
              if not isinstance(k, str):
                  raise AssertionError(f"{var_name} has non-string key {k}")
  
          for k, data_type in self.required_keys:
              if k not in val:
                  raise AssertionError(f"{k} key is missing from {var_name}")
              vname = f"{var_name}['{k}']"
              check_data(data_type, vname, val[k])
  
          for k, data_type in self.optional_keys:
              if k in val:
                  vname = f"{var_name}['{k}']"
                  check_data(data_type, vname, val[k])
  
          rkeys = {tup[0] for tup in self.required_keys}
          okeys = {tup[0] for tup in self.optional_keys}
          keys = rkeys | okeys
          for k in val:
              if k not in keys:
                  raise AssertionError(f"Unknown key {k} in {var_name}")
  
      def schema(self, var_name: str) -> str:
          # Our current schema is lossy, since our OpenAPI configs
          # aren't rigorous about "required" fields yet.
          keys = sorted(list(self.required_keys) + list(self.optional_keys))
  
          sub_schema = "\n".join(schema(name, data_type) for name, data_type in keys)
          return f"{var_name} (dict):\n{indent(sub_schema)}"
  
  
  @dataclass
  class EnumType:
      """An enum with the set of valid values declared."""
      valid_vals: Sequence[Any]
  
      def check_data(self, var_name: str, val: Dict[str, Any]) -> None:
          if val not in self.valid_vals:
              raise AssertionError(f"{var_name} is not in {self.valid_vals}")
  
      def schema(self, var_name: str) -> str:
          return f"{var_name} in {sorted(self.valid_vals)}"
  
  
  class Equals:
      """Type requiring a specific value."""
      def __init__(self, expected_value: Any) -> None:
          self.expected_value = expected_value
  
          # super hack for OpenAPI workaround
          if self.expected_value is None:
              self.equalsNone = True
  
      def check_data(self, var_name: str, val: Dict[str, Any]) -> None:
          if val != self.expected_value:
              raise AssertionError(f"{var_name} should be equal to {self.expected_value}")
  
      def schema(self, var_name: str) -> str:
          # Treat Equals as the degenerate case of EnumType, which
          # matches how we do things with OpenAPI.
          return f"{var_name} in {repr([self.expected_value])}"
  
  
  class NumberType:
      """A Union[float, int]; needed to align with the `number` type in
      OpenAPI, because isinstance(4, float) == False"""
      def check_data(self, var_name: str, val: Optional[Any]) -> None:
          if isinstance(val, int) or isinstance(val, float):
              return
          raise AssertionError(f"{var_name} is not a number")
  
      def schema(self, var_name: str) -> str:
          return f"{var_name}: number"
  
  
  class ListType:
      """List with every object having the declared sub_type."""
      def __init__(self, sub_type: Any, length: Optional[int] = None) -> None:
          self.sub_type = sub_type
          self.length = length
  
      def check_data(self, var_name: str, val: List[Any]) -> None:
          if not isinstance(val, list):
              raise AssertionError(f"{var_name} is not a list")
  
          for i, sub_val in enumerate(val):
              vname = f"{var_name}[{i}]"
              check_data(self.sub_type, vname, sub_val)
  
      def schema(self, var_name: str) -> str:
          sub_schema = schema("type", self.sub_type)
          return f"{var_name} (list):\n{indent(sub_schema)}"
  
  
  @dataclass
  class StringDictType:
      """Type that validates an object is a Dict[str, str]"""
      value_type: Any
  
      def check_data(self, var_name: str, val: Dict[Any, Any]) -> None:
          if not isinstance(val, dict):
              raise AssertionError(f"{var_name} is not a dictionary")
  
          for key, value in val.items():
              if not isinstance(key, str):
                  raise AssertionError(f"{var_name} has a non-string key")
  
              check_data(self.value_type, f"{var_name}[{key}]", value)
  
      def schema(self, var_name: str) -> str:
          sub_schema = schema("value", self.value_type)
          return f"{var_name} (string_dict):\n{indent(sub_schema)}"
  
  
  @dataclass
  class OptionalType:
      sub_type: Any
  
      def check_data(self, var_name: str, val: Optional[Any]) -> None:
          if val is None:
              return
          check_data(self.sub_type, var_name, val)
  
      def schema(self, var_name: str) -> str:
          # our OpenAPI spec doesn't support optional types very well yet,
          # so we just return the schema for our subtype
          return schema(var_name, self.sub_type)
  
  
  @dataclass
  class TupleType:
      """Deprecated; we'd like to avoid using tuples in our API.  Validates
      the tuple has the sequence of sub_types."""
      sub_types: Sequence[Any]
  
      def check_data(self, var_name: str, val: Any) -> None:
          if not (isinstance(val, list) or isinstance(val, tuple)):
              raise AssertionError(f"{var_name} is not a list/tuple")
  
          if len(val) != len(self.sub_types):
              raise AssertionError(f"{var_name} should have {len(self.sub_types)} items")
  
          for i, sub_type in enumerate(self.sub_types):
              vname = f"{var_name}[{i}]"
              check_data(sub_type, vname, val[i])
  
      def schema(self, var_name: str) -> str:
          sub_schemas = "\n".join(
              sorted(
                  schema(str(i), sub_type) for i, sub_type in enumerate(self.sub_types)
              )
          )
          return f"{var_name} (tuple):\n{indent(sub_schemas)}"
  
  
  @dataclass
  class UnionType:
      sub_types: Sequence[Any]
  
      def check_data(self, var_name: str, val: Any) -> None:
          for sub_type in self.sub_types:
              try:
                  check_data(sub_type, var_name, val)
              except AssertionError:
                  pass
  
              # We matched on one of our sub_types, so return
              return
  
          raise AssertionError(f"{var_name} does not pass the union type check")
  
      def schema(self, var_name: str) -> str:
          # We hack around our OpenAPI specs not accounting for None.
          sub_schemas = "\n".join(
              sorted(
                  schema("type", sub_type)
                  for sub_type in self.sub_types
                  if not hasattr(sub_type, "equalsNone")
              )
          )
          return f"{var_name} (union):\n{indent(sub_schemas)}"
  
  
  class UrlType:
      def check_data(self, var_name: str, val: Any) -> None:
          try:
              URLValidator()(val)
          except ValidationError:  # nocoverage
              raise AssertionError(f"{var_name} is not a URL")
  
      def schema(self, var_name: str) -> str:
          # just report str to match OpenAPI
          return f"{var_name}: str"
  
  
  def event_dict_type(
      required_keys: Sequence[Tuple[str, Any]],
      optional_keys: Sequence[Tuple[str, Any]] = [],
  ) -> DictType:
  
      """
      This is just a tiny wrapper on DictType, but it provides
      some minor benefits:
  
          - mark clearly that the schema is for a Zulip event
          - make sure there's a type field
          - add id field automatically
          - sanity check that we have no duplicate keys
  
      """
      rkeys = [key[0] for key in required_keys]
      okeys = [key[0] for key in optional_keys]
      keys = rkeys + okeys
      assert len(keys) == len(set(keys))
      assert "type" in rkeys
      assert "id" not in keys
      return DictType(
          required_keys=list(required_keys) + [("id", int)], optional_keys=optional_keys,
      )
  
  
  def make_checker(data_type: DictType,) -> Callable[[str, Dict[str, object]], None]:
      def f(var_name: str, event: Dict[str, Any]) -> None:
          check_data(data_type, var_name, event)
  
      return f
  
  
  def schema(
      var_name: str,
      data_type: Any,
  ) -> str:
      """Returns a YAML-like string for our data type; these are used for
      pretty-printing and comparison between the OpenAPI type
      definitions and these Python data types, as part of
  
      schema is a glorified repr of a data type, but it also includes a
      var_name you pass in, plus we dumb things down a bit to match our
      current OpenAPI spec.
      """
      if hasattr(data_type, "schema"):
          return data_type.schema(var_name)
      if data_type in [bool, dict, int, float, list, str]:
          return f"{var_name}: {data_type.__name__}"
      raise AssertionError(f"unknown type {data_type}")
  
  
  def check_data(
      data_type: Any,
      var_name: str,
      val: Any,
  ) -> None:
      """Check that val conforms to our data_type"""
      if hasattr(data_type, "check_data"):
          data_type.check_data(var_name, val)
          return
      if not isinstance(val, (data_type)):
          raise AssertionError(f"{var_name} is not type {data_type}")
      