def __init__(self, api, name, def_dict, method): super(Parameter, self).__init__(def_dict, api, parent=method, wire_name=name) self.ValidateName(name) self.schema = api # TODO(user): Deal with dots in names better. What we should do is: # For x.y, x.z create a little class X, with members y and z. Then # have the constructor method take an X. self._repeated = self.values.get('repeated', False) self._required = self.values.get('required', False) self._location = (self.values.get('location') or self.values.get('restParameterType') or 'query') # TODO(user): Why not just use Schema.Create here? referenced_schema = self.values.get('$ref') if referenced_schema: self._data_type = (api.SchemaByName(referenced_schema) or data_types.SchemaReference(referenced_schema, api)) elif def_dict.get('type') == 'array': self._data_type = Schema.Create(api, name, def_dict, name, method) elif self.values.get('enum'): self._data_type = data_types.Enum(def_dict, api, name, self.values.get('enum'), self.values.get('enumDescriptions'), parent=method) self.SetTemplateValue('enumType', self._data_type) else: self._data_type = data_types.PrimitiveDataType(def_dict, api, parent=self) if self._repeated: self._data_type = data_types.ArrayDataType(name, self._data_type, parent=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)
def testDataValue(self): foo_def_dict = { 'className': 'Foo', 'type': 'string', } prototype = data_types.DataType(foo_def_dict, None, language_model=self.language_model) dv = data_value.DataValue(3, prototype) # Basic Checks self.assertEqual(3, dv.value) self.assertEqual(prototype, dv.data_type) self.assertEqual({}, dv.metadata) self.assertEqual('Foo', dv.code_type) dv.metadata['foo'] = 'bar' self.assertEqual({'foo': 'bar'}, dv.metadata) dv.SetValue('four') self.assertEqual(dv.value, 'four') self.assertEqual(self.language_model, dv.GetLanguageModel()) other_language_model = language_model.LanguageModel( class_name_delimiter='+') dv.SetLanguageModel(other_language_model) self.assertEqual(other_language_model, dv.GetLanguageModel()) # Now that we've set a local language model... make sure the codepath # for setting the data_type's language model gets exercised. self.assertEqual('Foo', dv.code_type) # Check that the constructor doesn't freak if an odd object is passed in dv = data_value.DataValue(object, prototype) self.assertEqual(dv.value, object) # A standard case is the prototype being a Property object. It is not # uncommon that the Property's data_type is a SchemaReference. To verify # this case is handled correctly we must fake an API. bar_def_dict = { 'className': 'Foo', 'type': 'string', } class MockApi(object): def __init__(self): self.model_module = None def SetSchema(self, s): self.schema = s def SetSchemaRef(self, schema_ref): self.schema_ref = schema_ref # pylint: disable=unused-argument def ToClassName(self, name, element, element_type): return name # pylint: disable=unused-argument def SchemaByName(self, schema_name): return self.schema # pylint: disable=unused-argument def DataTypeFromJson(self, unused_def_dict, tentative_class_name, parent=None, wire_name=None): return self.schema_ref def NestedClassNameForProperty(self, name, owning_schema): return '%s%s' % (owning_schema.class_name, name) mock_api = MockApi() bar_schema = schema.Schema(mock_api, 'Bar', bar_def_dict) mock_api.SetSchema(bar_schema) schema_ref = data_types.SchemaReference('Bar', mock_api) mock_api.SetSchemaRef(schema_ref) prototype = schema.Property(mock_api, schema_ref, 'baz', foo_def_dict) dv = data_value.DataValue('3', prototype) # Assure all the unwrapping gymnastics in the DataValue constructor did # their job correctly. self.assertEqual(mock_api.schema, dv.data_type)
def Create(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. 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, element_type='schema') # 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. Primative data types, described by type and format. # { type: string, format: int32 } # # 5. Refs to another schema. # { $ref: name } 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 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 properties = [] schema = Schema(api, name, def_dict, parent=parent) if wire_name: schema.SetTemplateValue('wireName', wire_name) for prop_name, prop_dict in props.iteritems(): Trace(' adding prop: %s to %s' % (prop_name, name)) properties.append( Property(api, schema, prop_name, prop_dict)) Trace('Marking %s fully defined' % schema.values['className']) schema.SetTemplateValue('properties', properties) return schema # Look for case 2 additional_props = def_dict.get(_ADDITIONAL_PROPERTIES) if additional_props: Trace('Have only additionalProps for %s, dict=%s' % (name, str(additional_props))) # TODO(user): Remove this hack at the next large breaking change # The "Items" added to the end is unneeded and ugly. This is for # temporary backwards compatability. And in case 3 too. if additional_props.get('type') == 'array': name = '%sItems' % name # Note, since this is an interim, non class just to hold the map # make the parent schema the parent passed in, not myself. base_type = api.DataTypeFromJson(additional_props, name, parent=parent, wire_name=wire_name) map_type = data_types.MapDataType(base_type, parent=parent) Trace(' %s is MapOf<string, %s>' % (class_name, base_type.class_name)) return map_type raise ApiException('object without properties in: %s' % def_dict) elif json_type == 'array': # Case 3: Look for array definition items = def_dict.get('items') if not items: raise ApiException('array without items in: %s' % def_dict) tentative_class_name = class_name if schema_id: Trace('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) Trace(' %s is ArrayOf<%s>' % (class_name, base_type.class_name)) array_type = data_types.ArrayDataType(base_type, parent=parent) # If I am not a top level schema, mark me as not generatable if not schema_id: array_type.SetTemplateValue('builtIn', True) else: Trace('Top level schema %s is an array' % class_name) array_type.SetTemplateValue('className', schema_id) return array_type else: # Case 4: This must be a basic type. Create a DataType for it. format_type = def_dict.get('format') if format_type: Trace(' Found Type: %s with Format: %s' % (json_type, format_type)) base_type = data_types.BuiltInDataType(def_dict, api, parent=parent) return base_type 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, pointing back to one previously defined # 2. In a property of a schema, pointing forward # 3. In a method request or response pointing to a defined schema # 4. In a method request or response or property of a schema pointing to # something undefined. # # This code is not reached in case 1. The way the Generators loads # schemas (see _BuildSchemaDefinitions), is to loop over them and add # them to a dict of schemas. A backwards reference would be in the table # so the DataTypeFromJson call in the Property constructor will resolve # to the defined schema. # # 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 should not reach this code, because the # DataTypeFromJson would # have returned the defined schema. # # For case 4, we punt on the whole API. return data_types.SchemaReference(referenced_schema, api) raise ApiException('Cannot decode JSON Schema for: %s' % def_dict)