Пример #1
0
 def testSnakeToCamelWorksOnStringsWithMultipleUnderscoresBetweenWords(self):
   self.assertEqual("aBCD", casing.SnakeToCamel("a__b__c__d"))
   self.assertEqual("aBCD", casing.SnakeToCamel("a____b____c____d"))
   self.assertEqual("aBCD", casing.SnakeToCamel("___a___b___c___d___"))
   self.assertEqual("aaBbCcDd", casing.SnakeToCamel("___aa___bb___cc___dd___"))
   self.assertEqual("aaaBbbCccDdd",
                    casing.SnakeToCamel("___aaa___bbb___ccc___ddd___"))
Пример #2
0
 def testSnakeToCamelWorksOnStringsWithPrefixOrSuffixUnderscores(self):
   self.assertEqual("a", casing.SnakeToCamel("_a"))
   self.assertEqual("a", casing.SnakeToCamel("a_"))
   self.assertEqual("a", casing.SnakeToCamel("_a_"))
   self.assertEqual("a", casing.SnakeToCamel("___a___"))
   self.assertEqual("abcd", casing.SnakeToCamel("___abcd___"))
   self.assertEqual("aBCD", casing.SnakeToCamel("_a_b_c_d_"))
   self.assertEqual("aBCD", casing.SnakeToCamel("____a_b_c_d____"))
   self.assertEqual("aaBbCcDd", casing.SnakeToCamel("____aa_bb_cc_dd____"))
   self.assertEqual("aaaBbbCccDdd",
                    casing.SnakeToCamel("____aaa_bbb_ccc_ddd____"))
Пример #3
0
  def _SeparateFieldsIntoParams(
      self,
      http_method: str,
      path: str,
      args_type: Type[rdf_structs.RDFProtoStruct],
  ) -> Tuple[List[FieldDescriptor], List[FieldDescriptor],
             List[FieldDescriptor]]:
    """Group `FieldDescriptors` of a protobuf message by http param types."""
    field_descriptors = []
    if args_type:
      if not (inspect.isclass(args_type) and
              issubclass(args_type, rdf_structs.RDFProtoStruct)):
        raise TypeError("Router method args type is not a RDFProtoStruct "
                        "subclass.")
      field_descriptors = args_type.protobuf.DESCRIPTOR.fields

    # Separate fields into params: path, query and part of the request body.
    path_param_names = set(_GetPathParamsFromPath(path))
    path_params = []
    query_params = []
    body_params = []
    for field_d in field_descriptors:
      if casing.SnakeToCamel(field_d.name) in path_param_names:
        path_params.append(field_d)
      elif http_method.upper() in ("GET", "HEAD"):
        query_params.append(field_d)
      else:
        body_params.append(field_d)

    return path_params, query_params, body_params
Пример #4
0
  def _GetParameters(
      self,
      required_path_params: Iterable[FieldDescriptor],
      optional_path_params: Iterable[FieldDescriptor],
      query_params: Iterable[FieldDescriptor],
  ) -> List[Dict[str, Union[str, bool, SchemaReference, ArraySchema,
                            DescribedSchema]]]:
    """Create the OpenAPI description of the parameters of a route."""
    parameters = []

    req_path_params_set = set(required_path_params)
    opt_path_params_set = set(optional_path_params)
    query_params_set = set(query_params)
    for field_d in req_path_params_set | opt_path_params_set | query_params_set:
      parameter_obj = {"name": casing.SnakeToCamel(field_d.name)}
      if field_d in req_path_params_set:
        parameter_obj["in"] = "path"
        parameter_obj["required"] = True
      elif field_d in opt_path_params_set:
        parameter_obj["in"] = "path"
      else:
        parameter_obj["in"] = "query"

      parameter_obj["schema"] = self._GetDescribedSchema(field_d)

      parameters.append(parameter_obj)

    return parameters
Пример #5
0
def _NormalizePathComponent(component: str) -> str:
  """Normalize the given path component to be used in a valid OpenAPI path."""
  if _IsPathParameter(component):
    component = component[1:-1]
    component = component.split(":")[-1]
    component = casing.SnakeToCamel(component)
    component = f"{{{component}}}"

  return component
Пример #6
0
  def _CreateMessageSchema(
      self,
      descriptor: Descriptor,
      visiting: Set[str],
  ) -> None:
    """Creates the OpenAPI schema of a protobuf message.

    This method should generally not be called directly, but rather through
    `_CreateSchema` which takes care of error-verifications and caching.
    Args:
      descriptor: The protobuf `Descriptor` associated with a protobuf message.
      visiting: A set of type names that are in the process of having their
        OpenAPI schemas constructed and have their associated `_Create*Schema`
        call in the current call stack.

    Returns:
      Nothing, the schema is stored in `self.schema_objs`.
    """
    if self.schema_objs is None:  # Check required by mypy.
      raise AssertionError("OpenAPI type schemas not initialized.")

    type_name = _GetTypeName(descriptor)

    properties = dict()
    visiting.add(type_name)

    # Create schemas for the fields' types.
    for field_descriptor in descriptor.fields:
      self._CreateSchema(field_descriptor, visiting)
      field_name = casing.SnakeToCamel(field_descriptor.name)

      properties[field_name] = self._GetDescribedSchema(field_descriptor)

    visiting.remove(type_name)

    self.schema_objs[type_name] = cast(MessageSchema, {
        "type": "object",
        "properties": properties,
    })
Пример #7
0
  def _GetRequestBody(
      self,
      body_params: Iterable[FieldDescriptor],
  ) -> Dict[str, Dict[str, Any]]:
    """Create the OpenAPI description of the request body of a route."""
    if not body_params:
      return {}

    properties: (Dict[str, Union[SchemaReference, ArraySchema,
                                 DescribedSchema]]) = dict()
    for field_d in body_params:
      field_name = casing.SnakeToCamel(field_d.name)
      properties[field_name] = self._GetDescribedSchema(field_d)

    return {
        "content": {
            "application/json": {
                "schema": {
                    "type": "object",
                    "properties": properties,
                },
            },
        },
    }
Пример #8
0
 def testSnakeToCamelWorksOnEmptyString(self):
     self.assertEqual("", casing.SnakeToCamel(""))
Пример #9
0
 def testSnakeToCamelWorksOnStringsWithUnicodeCharacters(self):
     self.assertEqual("ąĆĘ", casing.SnakeToCamel("ą_ć_ę"))
     self.assertEqual("ąąąaĆććaĘęęa", casing.SnakeToCamel("ąąąa_ććća_ęęęa"))
     self.assertEqual("ąąąaĆććaĘęęa", casing.SnakeToCamel("ĄąĄA_ĆćĆa_ĘĘĘa"))
Пример #10
0
 def testSnakeToCamelWorksOnStringsWithUppercaseLetters(self):
     self.assertEqual("a", casing.SnakeToCamel("A"))
     self.assertEqual("aaaaaa", casing.SnakeToCamel("Aaaaaa"))
     self.assertEqual("aaaaBbbbCccc", casing.SnakeToCamel("AaAa_bBbB_CcCc"))
Пример #11
0
 def testSnakeToCamelWorksOnStringsWithUnderscoresOnly(self):
     self.assertEqual("", casing.SnakeToCamel("_"))
     self.assertEqual("", casing.SnakeToCamel("__"))
     self.assertEqual("", casing.SnakeToCamel("_________"))
Пример #12
0
 def testSnakeToCamelWorksOnRegularStrings(self):
     self.assertEqual("thisIsACamel",
                      casing.SnakeToCamel("this_is_a_camel"))
     self.assertEqual("aCamelThisIs",
                      casing.SnakeToCamel("a_camel_this_is"))
     self.assertEqual("aBCD", casing.SnakeToCamel("a_b_c_d"))
Пример #13
0
 def testSnakeToCamelWorksOnOneWordStrings(self):
     self.assertEqual("abcd", casing.SnakeToCamel("abcd"))
     self.assertEqual("a", casing.SnakeToCamel("a"))
Пример #14
0
  def _GetPaths(self) -> Dict[str, Dict[Any, Any]]:
    """Create the OpenAPI description of all the routes exposed by the API."""

    # The `Paths Object` `paths` field of the root `OpenAPI Object`.
    paths_obj: DefaultDict[str, Dict[Any, Any]] = collections.defaultdict(dict)

    router_methods = self.router.__class__.GetAnnotatedMethods()
    for router_method in router_methods.values():
      # To extract optional path parameters, all the routes associated with this
      # router method must be analysed and grouped.
      ungrouped_routes = []
      for http_method, path, _ in router_method.http_methods:
        path_components = path.split("/")
        # Remove any empty strings from the list of path components.
        path_components = [comp for comp in path_components if comp]

        ungrouped_routes.append([http_method] + path_components)

      grouped_routes = _GetGroupedRoutes(ungrouped_routes)
      for route_info in grouped_routes:
        # Components (comps) are URL components, including Werkzeug path
        # arguments such as `<client_id>` or `<path:file_path>`.
        route_comps, req_path_param_comps, opt_path_param_comps = route_info
        http_method = route_comps[0]
        path = "/".join(route_comps[1:])

        # Separate the route parameters into path params, query params and
        # request body params.
        path_params, query_params, body_params = self._SeparateFieldsIntoParams(
            http_method, path, router_method.args_type)

        # Separate the path params into required and optional path params.
        # First, extract path param names by normalizing the Werkzeug path arg
        # components to OpenAPI path args and remove the surrounding brackets.
        req_path_param_names = [
            _NormalizePathComponent(comp)[1:-1] for comp in req_path_param_comps
        ]
        opt_path_param_names = [
            _NormalizePathComponent(comp)[1:-1] for comp in opt_path_param_comps
        ]
        req_path_params = []
        opt_path_params = []
        for path_param in path_params:
          path_param_name = casing.SnakeToCamel(path_param.name)
          if path_param_name in req_path_param_names:
            req_path_params.append(path_param)
          elif path_param_name in opt_path_param_names:
            opt_path_params.append(path_param)
          else:
            raise AssertionError(
                f"Path parameter {path_param_name} was not classified as "
                f"required/optional.")

        normalized_path = _NormalizePath(path)
        path_obj = paths_obj[normalized_path]
        path_obj[http_method.lower()] = (
            self._GetOperationDescription(router_method, req_path_params,
                                          opt_path_params, query_params,
                                          body_params))

    return paths_obj