def _CreateObjectWithProperties(cls, props, api, name, def_dict, wire_name,
                                    parent):
        properties = []
        schema = cls(api, name, def_dict, parent=parent)
        if wire_name:
            schema.SetTemplateValue('wireName', wire_name)
        for prop_name in sorted(props):
            prop_dict = props[prop_name]
            _LOGGER.debug('  adding prop: %s to %s', prop_name, name)
            properties.append(Property(api, schema, prop_name, prop_dict))
            # Some APIs express etag directly in the response, others don't.
            # Knowing that we have it explicitly makes special case code generation
            # easier
            if prop_name == 'etag':
                schema.SetTemplateValue('hasEtagProperty', True)
        schema.SetTemplateValue('properties', properties)

        # check for @ clashing. E.g. No 'foo' and '@foo' in the same object.
        names = set()
        for p in properties:
            wire_name = p.GetTemplateValue('wireName')
            no_at_sign = wire_name.replace('@', '')
            if no_at_sign in names:
                raise ApiException('Property name clash in schema %s:'
                                   ' %s conflicts with another property' %
                                   (name, wire_name))
            names.add(no_at_sign)

        return schema
Beispiel #2
0
 def _CreateArrayType(cls, api, def_dict, wire_name,
                      class_name, schema_id, parent):
   items = def_dict.get('items')
   if not items:
     raise ApiException('array without items in: %s' % def_dict)
   tentative_class_name = class_name
   # TODO(user): THIS IS STUPID. We should not rename things items.
   # if we have an anonymous type within a map or array, it should be
   # called 'Item', and let the namespacing sort it out.
   if schema_id:
     _LOGGER.debug('Top level schema %s is an array', class_name)
     tentative_class_name += 'Items'
   base_type = api.DataTypeFromJson(items, tentative_class_name,
                                    parent=parent, wire_name=wire_name)
   _LOGGER.debug('  %s is ArrayOf<%s>', class_name, base_type.class_name)
   array_type = data_types.ArrayDataType(tentative_class_name, base_type,
                                         wire_name=wire_name,
                                         parent=parent)
   if schema_id:
     array_type.SetTemplateValue('className', schema_id)
   return array_type
Beispiel #3
0
    def __init__(self, api, name, def_dict, parent=None):
        """Construct a method.

    Methods in REST discovery are inside of a resource. Note that the method
    name and id are calculable from each other. id will always be equal to
    api_name.resource_name[.sub_resource...].method_name.  At least it should
    be, as that is the transformation Discovery makes from the API definition,
    which is essentially a flat list of methods, into a hierarchy of resources.

    Args:
      api: (Api) The Api which owns this Method.
      name: (string) The discovery name of the Method.
      def_dict: (dict) The discovery dictionary for this Method.
      parent: (CodeObject) The resource containing this Method, if any.

    Raises:
      ApiException: If the httpMethod type is not one we know how to
          handle.
    """
        super(Method, self).__init__(def_dict, api, parent=(parent or api))
        # TODO(user): Fix java templates to name vs. wireName correctly. Then
        # change the __init__ to have wire_name=def_dict.get('id') or name
        # then eliminate this line.
        self.SetTemplateValue('wireName', name)
        self.ValidateName(name)
        class_name = api.ToClassName(name, self, element_type='method')
        if parent and class_name == parent.values['className']:
            # Some languages complain when the collection name is the same as the
            # method name.
            class_name = '%sRequest' % class_name
        # The name is the key of the dict defining use. The id field is what you
        # have to use to call the method via RPC. That is unique, name might not be.
        self.SetTemplateValue('name', name)
        # Fix up very old discovery, which does not have an id.
        if 'id' not in self.values:
            self.values['id'] = name
        self.SetTemplateValue('className', class_name)
        http_method = def_dict.get('httpMethod', 'POST').upper()
        self.SetTemplateValue('httpMethod', http_method)
        self.SetTemplateValue('rpcMethod',
                              def_dict.get('rpcMethod') or def_dict['id'])
        rest_path = def_dict.get('path') or def_dict.get('restPath')
        # TODO(user): if rest_path is not set, raise a good error and fail fast.
        self.SetTemplateValue('restPath', rest_path)

        # Figure out the input and output types and schemas for this method.
        expected_request = self.values.get('request')
        if expected_request:
            # TODO(user): RequestBody is only used if the schema is anonymous.
            # When we go to nested models, this could be a nested class off the
            # Method, making it unique without the silly name.  Same for ResponseBody.
            request_schema = api.DataTypeFromJson(expected_request,
                                                  '%sRequestContent' % name,
                                                  parent=self)
            self.SetTemplateValue('requestType', request_schema)

        expected_response = def_dict.get('response') or def_dict.get('returns')
        if expected_response:
            response_schema = api.DataTypeFromJson(expected_response,
                                                   '%sResponse' % name,
                                                   parent=self)
            if self.values['wireName'] == 'get':
                response_schema.values['associatedResource'] = parent
            self.SetTemplateValue('responseType', response_schema)
        else:
            self.SetTemplateValue('responseType', api.void_type)
        # Make sure we can handle this method type and do any fixups.
        if http_method not in [
                'DELETE', 'GET', 'OPTIONS', 'PATCH', 'POST', 'PUT', 'PROPFIND',
                'PROPPATCH', 'REPORT'
        ]:
            raise ApiException('Unknown HTTP method: %s' % http_method,
                               def_dict)
        if http_method == 'GET':
            self.SetTemplateValue('requestType', None)

        # Replace parameters dict with Parameters.  We try to order them by their
        # position in the request path so that the generated code can track the
        # more human readable definition, rather than the order of the parameters
        # in the discovery doc.
        order = self.values.get('parameterOrder', [])
        req_parameters = []
        opt_parameters = []
        for name, def_dict in self.values.get('parameters', {}).iteritems():
            param = Parameter(api, name, def_dict, self)
            if name == 'alt':
                # Treat the alt parameter differently
                self.SetTemplateValue('alt', param)
                continue

            # Standard params are part of the generic request class
            # We want to push all parameters that aren't declared inside
            # parameterOrder after those that are.
            if param.values['wireName'] in order:
                req_parameters.append(param)
            else:
                # optional parameters are appended in the order they're declared.
                opt_parameters.append(param)
        # pylint: disable=g-long-lambda
        req_parameters.sort(lambda x, y: cmp(order.index(x.values[
            'wireName']), order.index(y.values['wireName'])))
        req_parameters.extend(opt_parameters)
        self.SetTemplateValue('parameters', req_parameters)

        self._InitMediaUpload(parent)
        self._InitPageable(api)
        api.AddMethod(self)
    def Create(cls, api, default_name, def_dict, wire_name, parent=None):
        """Construct a Schema or DataType from a discovery dictionary.

    Schemas contain either object declarations, simple type declarations, or
    references to other Schemas.  Object declarations conceptually map to real
    classes.  Simple types will map to a target language built-in type.
    References should effectively be replaced by the referenced Schema.

    Args:
      api: (Api) the Api instance owning the Schema
      default_name: (str) the default name of the Schema. If there is an 'id'
        member in the definition, that is used for the name instead.
      def_dict: (dict) a discovery dictionary
      wire_name: The name which will identify objects of this type in data on
        the wire. The path of wire_names can trace an item back through
        discovery.
      parent: (Schema) The containing schema. To be used to establish nesting
        for anonymous sub-schemas.

    Returns:
      A Schema or DataType.

    Raises:
      ApiException: If the definition dict is not correct.
    """

        schema_id = def_dict.get('id')
        if schema_id:
            name = schema_id
        else:
            name = default_name
        class_name = api.ToClassName(name, None, element_type='schema')

        _LOGGER.debug(
            'Create: %s, parent=%s', name,
            parent.values.get('wireName', '<anon>') if parent else 'None')

        # Schema objects come in several patterns.
        #
        # 1. Simple objects
        #    { type: object, properties: { "foo": {schema} ... }}
        #
        # 2. Maps of objects
        #    { type: object, additionalProperties: { "foo": {inner_schema} ... }}
        #
        #    What we want is a data type which is Map<string, {inner_schema}>
        #    The schema we create here is essentially a built in type which we
        #    don't want to generate a class for.
        #
        # 3. Arrays of objects
        #    { type: array, items: { inner_schema }}
        #
        #    Same kind of issue as the map, but with List<{inner_schema}>
        #
        # 4. Primitive data types, described by type and format.
        #    { type: string, format: int32 }
        #    { type: string, enum: ["value", ...], enumDescriptions: ["desc", ...]}
        #
        # 5. Refs to another schema.
        #    { $ref: name }
        #
        # 6. Variant schemas
        #    { type: object, variant: { discriminant: "prop", map:
        #             [ { 'type_value': value, '$ref': wireName }, ... ] } }
        #
        #    What we do is map the variant schema to a schema with a single
        #    property for the discriminant. To that property, we attach
        #    the variant map which specifies which discriminator values map
        #    to which schema references. We also collect variant information
        #    in the api so we can later associate discriminator value and
        #    base type with the generated variant subtypes.

        if 'type' in def_dict:
            # The 'type' field of the schema can either be 'array', 'object', or a
            # base json type.
            json_type = def_dict['type']
            if json_type == 'object':

                # Look for variants
                variant = def_dict.get('variant')
                if variant:
                    return cls._CreateVariantType(variant, api, name, def_dict,
                                                  wire_name, parent)

                # Look for full object definition.  You can have properties or
                # additionalProperties, but it does not  do anything useful to have
                # both.

                # Replace properties dict with Property's
                props = def_dict.get('properties')
                if props:
                    # This case 1 from above
                    return cls._CreateObjectWithProperties(
                        props, api, name, def_dict, wire_name, parent)

                # Look for case 2
                additional_props = def_dict.get(_ADDITIONAL_PROPERTIES)
                if additional_props:
                    return cls._CreateMapType(additional_props, api, name,
                                              wire_name, class_name, parent)

                # no properties
                return cls._CreateSchemaWithoutProperties(
                    api, name, def_dict, wire_name, parent)

            elif json_type == 'array':
                # Case 3: Look for array definition
                return cls._CreateArrayType(api, def_dict, wire_name,
                                            class_name, schema_id, parent)
            else:
                # Case 4: This must be a basic type.  Create a DataType for it.
                return data_types.CreatePrimitiveDataType(def_dict,
                                                          api,
                                                          wire_name,
                                                          parent=parent)

        referenced_schema = def_dict.get('$ref')
        if referenced_schema:
            # Case 5: Reference to another Schema.
            #
            # There are 4 ways you can see '$ref' in discovery.
            # 1. In a property of a schema or a method request/response, pointing
            #    back to a previously defined schema
            # 2. As above, pointing to something not defined yet.
            # 3. In a method request or response or property of a schema pointing to
            #    something undefined.
            #
            # For case 1, the schema will be in the API name to schema map.
            #
            # For case 2, just creating this placeholder here is fine.  When the
            # actual schema is hit in the loop in _BuildSchemaDefinitions, we will
            # replace the entry and DataTypeFromJson will resolve the to the new def.
            #
            # For case 3, we will end up with a dangling reference and fail later.
            schema = api.SchemaByName(referenced_schema)
            # The stored "schema" may not be an instance of Schema, but rather a
            # data_types.PrimitiveDataType, which has no 'wireName' value.
            if schema:
                _LOGGER.debug('Schema.Create: %s => %s', default_name,
                              schema.values.get('wireName', '<unknown>'))
                return schema
            return data_types.SchemaReference(referenced_schema, api)

        raise ApiException('Cannot decode JSON Schema for: %s' % def_dict)
Beispiel #5
0
 def testExceptionStr(self):
   e = ApiException('foo')
   self.assertEquals('foo', str(e))
   e = ApiException('foo', {'bar': 1})
   self.assertEquals("""foo: {'bar': 1}""", str(e))