Index: tools/json_schema_compiler/model.py |
diff --git a/tools/json_schema_compiler/model.py b/tools/json_schema_compiler/model.py |
index 36be5a3745f0a4b4532dffc2bac6024d273f9a0e..637942790825b1e76a9ac4033777589be57680e7 100644 |
--- a/tools/json_schema_compiler/model.py |
+++ b/tools/json_schema_compiler/model.py |
@@ -60,76 +60,129 @@ class Namespace(object): |
self.source_file_dir, self.source_file_filename = os.path.split(source_file) |
self.parent = None |
self.platforms = _GetPlatforms(json) |
- _AddTypes(self, json, self) |
- _AddFunctions(self, json, self) |
- _AddEvents(self, json, self) |
- _AddProperties(self, json, self) |
+ toplevel_origin = Origin(from_client=True, from_json=True) |
+ self.types = _GetTypes(self, json, self, toplevel_origin) |
+ self.functions = _GetFunctions(self, json, self) |
+ self.events = _GetEvents(self, json, self) |
+ self.properties = _GetProperties(self, json, self, toplevel_origin) |
if include_compiler_options: |
self.compiler_options = json.get('compiler_options', {}) |
+class Origin(object): |
+ """Stores the possible origin of model object as a pair of bools. These are: |
+ |
+ |from_client| indicating that instances can originate from users of |
+ generated code (for example, function results), or |
+ |from_json| indicating that instances can originate from the JSON (for |
+ example, function parameters) |
+ |
+ It is possible for model objects to originate from both the client and json, |
+ for example Types defined in the top-level schema, in which case both |
+ |from_client| and |from_json| would be True. |
+ """ |
+ def __init__(self, from_client=False, from_json=False): |
+ if not from_client and not from_json: |
+ raise ValueError('One of from_client or from_json must be true') |
+ self.from_client = from_client |
+ self.from_json = from_json |
+ |
class Type(object): |
"""A Type defined in the json. |
Properties: |
- |name| the type name |
+ - |namespace| the Type's namespace |
- |description| the description of the type (if provided) |
- |properties| a map of property unix_names to their model.Property |
- |functions| a map of function names to their model.Function |
- |events| a map of event names to their model.Event |
- - |from_client| indicates that instances of the Type can originate from the |
- users of generated code, such as top-level types and function results |
- - |from_json| indicates that instances of the Type can originate from the |
- JSON (as described by the schema), such as top-level types and function |
- parameters |
- - |type_| the PropertyType of this Type |
+ - |origin| the Origin of the type |
+ - |property_type| the PropertyType of this Type |
- |item_type| if this is an array, the type of items in the array |
- |simple_name| the name of this Type without a namespace |
+ - |additional_properties| the type of the additional properties, if any is |
+ specified |
""" |
- def __init__(self, parent, name, json, namespace): |
- if json.get('type') == 'array': |
- self.type_ = PropertyType.ARRAY |
- self.item_type = Property(self, |
- name + "Element", |
- json['items'], |
- namespace, |
- from_json=True, |
- from_client=True) |
- elif 'enum' in json: |
- self.enum_values = [] |
- for value in json['enum']: |
- self.enum_values.append(value) |
- self.type_ = PropertyType.ENUM |
- elif json.get('type') == 'string': |
- self.type_ = PropertyType.STRING |
- else: |
+ def __init__(self, |
+ parent, |
+ name, |
+ json, |
+ namespace, |
+ origin): |
+ self.name = name |
+ self.namespace = namespace |
+ self.simple_name = _StripNamespace(self.name, namespace) |
+ self.unix_name = UnixName(self.name) |
+ self.description = json.get('description', None) |
+ self.origin = origin |
+ self.parent = parent |
+ self.instance_of = json.get('isInstanceOf', None) |
+ |
+ # TODO(kalman): Only objects need functions/events/properties, but callers |
+ # assume that all types have them. Fix this. |
+ self.functions = _GetFunctions(self, json, namespace) |
+ self.events = _GetEvents(self, json, namespace) |
+ self.properties = _GetProperties(self, json, namespace, origin) |
+ |
+ json_type = json.get('type', None) |
+ if json_type == 'array': |
+ self.property_type = PropertyType.ARRAY |
+ self.item_type = Type( |
+ self, '%sType' % name, json['items'], namespace, origin) |
+ elif '$ref' in json: |
+ self.property_type = PropertyType.REF |
+ self.ref_type = json['$ref'] |
+ elif 'enum' in json and json_type == 'string': |
+ self.property_type = PropertyType.ENUM |
+ self.enum_values = [value for value in json['enum']] |
+ elif json_type == 'any': |
+ self.property_type = PropertyType.ANY |
+ elif json_type == 'binary': |
+ self.property_type = PropertyType.BINARY |
+ elif json_type == 'boolean': |
+ self.property_type = PropertyType.BOOLEAN |
+ elif json_type == 'integer': |
+ self.property_type = PropertyType.INTEGER |
+ elif (json_type == 'double' or |
+ json_type == 'number'): |
+ self.property_type = PropertyType.DOUBLE |
+ elif json_type == 'string': |
+ self.property_type = PropertyType.STRING |
+ elif 'choices' in json: |
+ self.property_type = PropertyType.CHOICES |
+ self.choices = [Type(self, |
+ # The name of the choice type - there had better be |
+ # either a type or a $ref specified for the choice. |
+ json.get('type', json.get('$ref')), |
+ json, |
+ namespace, |
+ origin) |
+ for json in json['choices']] |
+ elif json_type == 'object': |
if not ( |
'properties' in json or |
'additionalProperties' in json or |
'functions' in json or |
'events' in json): |
raise ParseException(self, name + " has no properties or functions") |
- self.type_ = PropertyType.OBJECT |
- self.name = name |
- self.simple_name = _StripNamespace(self.name, namespace) |
- self.unix_name = UnixName(self.name) |
- self.description = json.get('description') |
- self.from_json = True |
- self.from_client = True |
- self.parent = parent |
- self.instance_of = json.get('isInstanceOf', None) |
- _AddFunctions(self, json, namespace) |
- _AddEvents(self, json, namespace) |
- _AddProperties(self, json, namespace, from_json=True, from_client=True) |
- |
- additional_properties_key = 'additionalProperties' |
- additional_properties = json.get(additional_properties_key) |
- if additional_properties: |
- self.properties[additional_properties_key] = Property( |
- self, |
- additional_properties_key, |
- additional_properties, |
- namespace, |
- is_additional_properties=True) |
+ self.property_type = PropertyType.OBJECT |
+ additional_properties_json = json.get('additionalProperties', None) |
+ if additional_properties_json is not None: |
+ self.additional_properties = Type(self, |
+ 'additionalProperties', |
+ additional_properties_json, |
+ namespace, |
+ origin) |
+ else: |
+ self.additional_properties = None |
+ elif json_type == 'function': |
+ self.property_type = PropertyType.FUNCTION |
+ # Sometimes we might have an unnamed function, e.g. if it's a property |
+ # of an object. Use the name of the property in that case. |
+ function_name = json.get('name', name) |
+ self.function = Function(self, function_name, json, namespace, origin) |
+ else: |
+ raise ParseException(self, 'Unsupported JSON type %s' % json_type) |
class Function(object): |
"""A Function defined in the API. |
@@ -149,11 +202,11 @@ class Function(object): |
""" |
def __init__(self, |
parent, |
+ name, |
json, |
namespace, |
- from_json=False, |
- from_client=False): |
- self.name = json['name'] |
+ origin): |
+ self.name = name |
self.simple_name = _StripNamespace(self.name, namespace) |
self.platforms = _GetPlatforms(json) |
self.params = [] |
@@ -167,18 +220,14 @@ class Function(object): |
self.actions = options.get('actions', []) |
self.supports_listeners = options.get('supportsListeners', True) |
self.supports_rules = options.get('supportsRules', False) |
+ |
def GeneratePropertyFromParam(p): |
- return Property(self, |
- p['name'], p, |
- namespace, |
- from_json=from_json, |
- from_client=from_client) |
+ return Property.FromJSON(self, p['name'], p, namespace, origin) |
self.filters = [GeneratePropertyFromParam(filter) |
for filter in json.get('filters', [])] |
callback_param = None |
for param in json.get('parameters', []): |
- |
if param.get('type') == 'function': |
if callback_param: |
# No ParseException because the webstore has this. |
@@ -190,153 +239,97 @@ class Function(object): |
if callback_param: |
self.callback = Function(self, |
+ callback_param['name'], |
callback_param, |
namespace, |
- from_client=True) |
+ Origin(from_client=True)) |
self.returns = None |
if 'returns' in json: |
- self.returns = Property(self, 'return', json['returns'], namespace) |
+ self.returns = Property.FromJSON( |
+ self, 'return', json['returns'], namespace, origin) |
class Property(object): |
"""A property of a type OR a parameter to a function. |
- |
Properties: |
- |name| name of the property as in the json. This shouldn't change since |
it is the key used to access DictionaryValues |
- |unix_name| the unix_style_name of the property. Used as variable name |
- |optional| a boolean representing whether the property is optional |
- |description| a description of the property (if provided) |
- - |type_| the model.PropertyType of this property |
- - |compiled_type| the model.PropertyType that this property should be |
- compiled to from the JSON. Defaults to |type_|. |
- - |ref_type| the type that the REF property is referencing. Can be used to |
- map to its model.Type |
- - |item_type| a model.Property representing the type of each element in an |
- ARRAY |
- - |properties| the properties of an OBJECT parameter |
- - |from_client| indicates that instances of the Type can originate from the |
- users of generated code, such as top-level types and function results |
- - |from_json| indicates that instances of the Type can originate from the |
- JSON (as described by the schema), such as top-level types and function |
- parameters |
+ - |type_| the model.Type of this property |
- |simple_name| the name of this Property without a namespace |
""" |
+ @staticmethod |
+ def FromJSON(parent, name, json, namespace, origin): |
+ """Creates a Property from JSON. |
+ """ |
+ opt_args = {} |
+ if 'description' in json: |
+ opt_args['description'] = json['description'] |
+ if 'optional' in json: |
+ opt_args['optional'] = json.get('optional') |
+ if 'isInstanceOf' in json: |
+ opt_args['instance_of'] = json.get('isInstanceOf') |
+ |
+ # HACK: only support very specific value types. |
+ is_allowed_value = ( |
+ '$ref' not in json and |
+ ('type' not in json or json['type'] == 'integer' |
+ or json['type'] == 'string')) |
+ |
+ if 'value' in json and is_allowed_value: |
+ value = json['value'] |
+ opt_args['value'] = value |
+ if 'type' not in json: |
+ # Sometimes the type of the value is left out, and we need to figure |
+ # it out for ourselves. |
+ if isinstance(value, int): |
+ json['type'] = 'integer' |
+ elif isinstance(value, basestring): |
+ json['type'] = 'string' |
+ else: |
+ # TODO(kalman): support more types as necessary. |
+ raise ParseException( |
+ parent, '"%s" is not a supported type for "value"' % type(value)) |
+ |
+ type_ = Type(parent, name, json, namespace, origin) |
+ return Property(parent, |
+ name, |
+ namespace, |
+ type_, |
+ origin, |
+ **opt_args); |
+ |
def __init__(self, |
parent, |
name, |
- json, |
namespace, |
- is_additional_properties=False, |
- from_json=False, |
- from_client=False): |
+ type_, |
+ origin, |
+ description=None, |
+ optional=False, |
+ returns=None, |
+ instance_of=None, |
+ value=None): |
+ """Directly initializes the fields of the Property. |
+ """ |
self.name = name |
self.simple_name = _StripNamespace(self.name, namespace) |
self._unix_name = UnixName(self.name) |
self._unix_name_used = False |
- self.optional = json.get('optional', False) |
- self.functions = OrderedDict() |
- self.has_value = False |
- self.description = json.get('description') |
+ self.optional = optional |
+ self.description = description |
self.parent = parent |
- self.from_json = from_json |
- self.from_client = from_client |
- self.instance_of = json.get('isInstanceOf', None) |
- self.params = [] |
- self.returns = None |
- _AddProperties(self, json, namespace) |
- if is_additional_properties: |
- self.type_ = PropertyType.ADDITIONAL_PROPERTIES |
- elif '$ref' in json: |
- self.ref_type = json['$ref'] |
- self.type_ = PropertyType.REF |
- elif 'enum' in json and json.get('type') == 'string': |
- # Non-string enums (as in the case of [legalValues=(1,2)]) should fall |
- # through to the next elif. |
- self.enum_values = [] |
- for value in json['enum']: |
- self.enum_values.append(value) |
- self.type_ = PropertyType.ENUM |
- elif 'type' in json: |
- self.type_ = self._JsonTypeToPropertyType(json['type']) |
- if self.type_ == PropertyType.ARRAY: |
- self.item_type = Property(self, |
- name + "Element", |
- json['items'], |
- namespace, |
- from_json=from_json, |
- from_client=from_client) |
- elif self.type_ == PropertyType.OBJECT: |
- # These members are read when this OBJECT Property is used as a Type |
- type_ = Type(self, self.name, json, namespace) |
- # self.properties will already have some value from |_AddProperties|. |
- self.properties.update(type_.properties) |
- self.functions = type_.functions |
- elif self.type_ == PropertyType.FUNCTION: |
- for p in json.get('parameters', []): |
- self.params.append(Property(self, |
- p['name'], |
- p, |
- namespace, |
- from_json=from_json, |
- from_client=from_client)) |
- if 'returns' in json: |
- self.returns = Property(self, 'return', json['returns'], namespace) |
- elif 'choices' in json: |
- if not json['choices'] or len(json['choices']) == 0: |
- raise ParseException(self, 'Choices has no choices') |
- self.choices = {} |
- self.type_ = PropertyType.CHOICES |
- self.compiled_type = self.type_ |
- for choice_json in json['choices']: |
- choice = Property(self, |
- self.name, |
- choice_json, |
- namespace, |
- from_json=from_json, |
- from_client=from_client) |
- choice.unix_name = UnixName(self.name + choice.type_.name) |
- # The existence of any single choice is optional |
- choice.optional = True |
- self.choices[choice.type_] = choice |
- elif 'value' in json: |
- self.has_value = True |
- self.value = json['value'] |
- if type(self.value) == int: |
- self.type_ = PropertyType.INTEGER |
- self.compiled_type = self.type_ |
- else: |
- # TODO(kalman): support more types as necessary. |
- raise ParseException( |
- self, '"%s" is not a supported type' % type(self.value)) |
- else: |
- raise ParseException( |
- self, 'Property has no type, $ref, choices, or value') |
- if 'compiled_type' in json: |
- if 'type' in json: |
- self.compiled_type = self._JsonTypeToPropertyType(json['compiled_type']) |
- else: |
- raise ParseException(self, 'Property has compiled_type but no type') |
- else: |
- self.compiled_type = self.type_ |
- |
- def _JsonTypeToPropertyType(self, json_type): |
- try: |
- return { |
- 'any': PropertyType.ANY, |
- 'array': PropertyType.ARRAY, |
- 'binary': PropertyType.BINARY, |
- 'boolean': PropertyType.BOOLEAN, |
- 'integer': PropertyType.INTEGER, |
- 'int64': PropertyType.INT64, |
- 'function': PropertyType.FUNCTION, |
- 'number': PropertyType.DOUBLE, |
- 'object': PropertyType.OBJECT, |
- 'string': PropertyType.STRING, |
- }[json_type] |
- except KeyError: |
- raise NotImplementedError('Type %s not recognized' % json_type) |
+ self.origin = origin |
+ if not isinstance(type_, Type): |
+ raise ValueError("not Type: %s" % type_) |
+ self.type_ = type_ |
+ self.returns = returns |
+ if instance_of is not None: |
+ self.instance_of = instance_of |
+ self.value = value |
def GetUnixName(self): |
"""Gets the property's unix_name. Raises AttributeError if not set. |
@@ -359,14 +352,6 @@ class Property(object): |
(self.name, self._unix_name)) |
self._unix_name = unix_name |
- def Copy(self): |
- """Makes a copy of this model.Property object and allow the unix_name to be |
- set again. |
- """ |
- property_copy = copy.copy(self) |
- property_copy._unix_name_used = False |
- return property_copy |
- |
unix_name = property(GetUnixName, SetUnixName) |
class _Enum(object): |
@@ -403,20 +388,19 @@ class _PropertyTypeInfo(_Enum): |
class PropertyType(object): |
"""Enum of different types of properties/parameters. |
""" |
- INTEGER = _PropertyTypeInfo(True, "INTEGER") |
- INT64 = _PropertyTypeInfo(True, "INT64") |
- DOUBLE = _PropertyTypeInfo(True, "DOUBLE") |
- BOOLEAN = _PropertyTypeInfo(True, "BOOLEAN") |
- STRING = _PropertyTypeInfo(True, "STRING") |
- ENUM = _PropertyTypeInfo(False, "ENUM") |
- ARRAY = _PropertyTypeInfo(False, "ARRAY") |
- REF = _PropertyTypeInfo(False, "REF") |
- CHOICES = _PropertyTypeInfo(False, "CHOICES") |
- OBJECT = _PropertyTypeInfo(False, "OBJECT") |
- FUNCTION = _PropertyTypeInfo(False, "FUNCTION") |
- BINARY = _PropertyTypeInfo(False, "BINARY") |
- ANY = _PropertyTypeInfo(False, "ANY") |
- ADDITIONAL_PROPERTIES = _PropertyTypeInfo(False, "ADDITIONAL_PROPERTIES") |
+ INTEGER = _PropertyTypeInfo(True, "integer") |
+ INT64 = _PropertyTypeInfo(True, "int64") |
+ DOUBLE = _PropertyTypeInfo(True, "double") |
+ BOOLEAN = _PropertyTypeInfo(True, "boolean") |
+ STRING = _PropertyTypeInfo(True, "string") |
+ ENUM = _PropertyTypeInfo(False, "enum") |
+ ARRAY = _PropertyTypeInfo(False, "array") |
+ REF = _PropertyTypeInfo(False, "ref") |
+ CHOICES = _PropertyTypeInfo(False, "choices") |
+ OBJECT = _PropertyTypeInfo(False, "object") |
+ FUNCTION = _PropertyTypeInfo(False, "function") |
+ BINARY = _PropertyTypeInfo(False, "binary") |
+ ANY = _PropertyTypeInfo(False, "any") |
def UnixName(name): |
"""Returns the unix_style name for a given lowerCamelCase string. |
@@ -436,57 +420,57 @@ def _StripNamespace(name, namespace): |
def _GetModelHierarchy(entity): |
"""Returns the hierarchy of the given model entity.""" |
hierarchy = [] |
- while entity: |
- try: |
- hierarchy.append(entity.name) |
- except AttributeError: |
- hierarchy.append(repr(entity)) |
- entity = entity.parent |
+ while entity is not None: |
+ hierarchy.append(getattr(entity, 'name', repr(entity))) |
+ if isinstance(entity, Namespace): |
+ hierarchy.insert(0, ' in %s' % entity.source_file) |
+ entity = getattr(entity, 'parent', None) |
hierarchy.reverse() |
return hierarchy |
-def _AddTypes(model, json, namespace): |
- """Adds Type objects to |model| contained in the 'types' field of |json|. |
+def _GetTypes(parent, json, namespace, origin): |
+ """Creates Type objects extracted from |json|. |
""" |
- model.types = OrderedDict() |
+ types = OrderedDict() |
for type_json in json.get('types', []): |
- type_ = Type(model, type_json['id'], type_json, namespace) |
- model.types[type_.name] = type_ |
+ type_ = Type(parent, type_json['id'], type_json, namespace, origin) |
+ types[type_.name] = type_ |
+ return types |
-def _AddFunctions(model, json, namespace): |
- """Adds Function objects to |model| contained in the 'functions' field of |
- |json|. |
+def _GetFunctions(parent, json, namespace): |
+ """Creates Function objects extracted from |json|. |
""" |
- model.functions = OrderedDict() |
+ functions = OrderedDict() |
for function_json in json.get('functions', []): |
- function = Function(model, function_json, namespace, from_json=True) |
- model.functions[function.name] = function |
- |
-def _AddEvents(model, json, namespace): |
- """Adds Function objects to |model| contained in the 'events' field of |json|. |
+ function = Function(parent, |
+ function_json['name'], |
+ function_json, |
+ namespace, |
+ Origin(from_json=True)) |
+ functions[function.name] = function |
+ return functions |
+ |
+def _GetEvents(parent, json, namespace): |
+ """Creates Function objects generated from the events in |json|. |
""" |
- model.events = OrderedDict() |
+ events = OrderedDict() |
for event_json in json.get('events', []): |
- event = Function(model, event_json, namespace, from_client=True) |
- model.events[event.name] = event |
- |
-def _AddProperties(model, |
- json, |
- namespace, |
- from_json=False, |
- from_client=False): |
- """Adds model.Property objects to |model| contained in the 'properties' field |
- of |json|. |
+ event = Function(parent, |
+ event_json['name'], |
+ event_json, |
+ namespace, |
+ Origin(from_client=True)) |
+ events[event.name] = event |
+ return events |
+ |
+def _GetProperties(parent, json, namespace, origin): |
+ """Generates Property objects extracted from |json|. |
""" |
- model.properties = OrderedDict() |
+ properties = OrderedDict() |
for name, property_json in json.get('properties', {}).items(): |
- model.properties[name] = Property( |
- model, |
- name, |
- property_json, |
- namespace, |
- from_json=from_json, |
- from_client=from_client) |
+ properties[name] = Property.FromJSON( |
+ parent, name, property_json, namespace, origin) |
+ return properties |
class _PlatformInfo(_Enum): |
def __init__(self, name): |