Better types for additionalProps

recurse down the definitions for additionalProps, so that the types are better
pull/977/head
Richard van der Hoff 8 years ago
parent aefe8f9430
commit dfbe416490

@ -12,6 +12,8 @@ properties:
additionalProperties: additionalProperties:
type: array type: array
title: User ID title: User ID
items:
type: string
type: object type: object
type: type:
enum: enum:

@ -119,42 +119,27 @@ def get_json_schema_object_fields(obj, enforce_title=False):
"get_json_schema_object_fields: Object %s isn't an object." % obj "get_json_schema_object_fields: Object %s isn't an object." % obj
) )
logger.debug("Processing object with title '%s'", obj.get("title")) obj_title = obj.get("title")
logger.debug("Processing object with title '%s'", obj_title)
if enforce_title and not obj.get("title"): if enforce_title and not obj_title:
# Force a default titile of "NO_TITLE" to make it obvious in the # Force a default titile of "NO_TITLE" to make it obvious in the
# specification output which parts of the schema are missing a title # specification output which parts of the schema are missing a title
obj["title"] = 'NO_TITLE' obj_title = 'NO_TITLE'
additionalProps = obj.get("additionalProperties") additionalProps = obj.get("additionalProperties")
props = obj.get("properties") props = obj.get("properties")
if additionalProps and not props: if additionalProps and not props:
# not "really" an object, just a KV store # not "really" an object, just a KV store
additionalProps = inherit_parents(additionalProps) logger.debug("%s is a pseudo-object", obj_title)
logger.debug("%s is a pseudo-object", obj.get("title"))
key_type = additionalProps.get("x-pattern", "string") key_type = additionalProps.get("x-pattern", "string")
res = process_data_type(additionalProps)
value_type = additionalProps["type"] return {
if value_type == "object": "type": "{%s: %s}" % (key_type, res["type"]),
nested_objects = get_json_schema_object_fields( "tables": res["tables"],
additionalProps, }
enforce_title=True,
)
value_type = nested_objects[0]["title"]
tables = [x for x in nested_objects if not x.get("no-table")]
else:
key_type = "string"
tables = []
tables = [{
"title": "{%s: %s}" % (key_type, value_type),
"no-table": True
}]+tables
logger.debug("%s done: returning %s", obj.get("title"), tables)
return tables
if not props: if not props:
props = obj.get("patternProperties") props = obj.get("patternProperties")
@ -169,14 +154,13 @@ def get_json_schema_object_fields(obj, enforce_title=False):
# Sometimes you just want to specify that a thing is an object without # Sometimes you just want to specify that a thing is an object without
# doing all the keys. # doing all the keys.
if not props: if not props:
return [{ return {
"title": obj.get("title"), "type": obj_title,
"no-table": True "tables": [],
}] }
required_keys = set(obj.get("required", [])) required_keys = set(obj.get("required", []))
obj_title = obj.get("title")
first_table_rows = [] first_table_rows = []
tables = [] tables = []
@ -184,9 +168,14 @@ def get_json_schema_object_fields(obj, enforce_title=False):
try: try:
logger.debug("Processing property %s.%s", obj_title, key_name) logger.debug("Processing property %s.%s", obj_title, key_name)
required = key_name in required_keys required = key_name in required_keys
res = process_prop(key_name, props[key_name], required) res = process_data_type(props[key_name], required)
first_table_rows.append(res["row"]) first_table_rows.append({
"key": key_name,
"type": res["type"],
"required": required,
"desc": res["desc"],
})
tables.extend(res["tables"]) tables.extend(res["tables"])
logger.debug("Done property %s" % key_name) logger.debug("Done property %s" % key_name)
@ -202,87 +191,64 @@ def get_json_schema_object_fields(obj, enforce_title=False):
"rows": first_table_rows, "rows": first_table_rows,
}) })
return tables return {
"type": obj_title,
"tables": tables,
}
def process_prop(key_name, prop, required): # process a data type definition. returns a dictionary with the keys:
# type: stringified type name
# desc: description
# enum_desc: description of permissible enum fields
# is_object: true if the data type is an object
# tables: list of additional table definitions
def process_data_type(prop, required=False, enforce_title=True):
prop = inherit_parents(prop) prop = inherit_parents(prop)
value_type = None
desc = prop.get("description", "")
prop_type = prop['type'] prop_type = prop['type']
tables = [] tables = []
enum_desc = None
logger.debug("%s is a %s", key_name, prop_type) is_object = False
if prop_type == "object": if prop_type == "object":
nested_objects = get_json_schema_object_fields( res = get_json_schema_object_fields(
prop, prop,
enforce_title=True, enforce_title=enforce_title,
) )
value_type = nested_objects[0]["title"] prop_type = res["type"]
value_id = value_type tables = res["tables"]
is_object = True
tables += [x for x in nested_objects if not x.get("no-table")]
elif prop_type == "array": elif prop_type == "array":
items = inherit_parents(prop["items"]) nested = process_data_type(prop["items"])
# if the items of the array are objects then recurse prop_type = "[%s]" % nested["type"]
if items["type"] == "object": tables = nested["tables"]
nested_objects = get_json_schema_object_fields( enum_desc = nested["enum_desc"]
items,
enforce_title=True, if prop.get("enum"):
if len(prop["enum"]) > 1:
prop_type = "enum"
enum_desc = (
"One of: %s" % json.dumps(prop["enum"])
) )
value_id = nested_objects[0]["title"]
value_type = "[%s]" % value_id
tables += nested_objects
else: else:
value_type = items["type"] enum_desc = (
if isinstance(value_type, list): "Must be '%s'." % prop["enum"][0]
value_type = " or ".join(value_type) )
value_id = value_type
value_type = "[%s]" % value_type
array_enums = items.get("enum")
if array_enums:
if len(array_enums) > 1:
value_type = "[enum]"
desc += (
" One of: %s" % json.dumps(array_enums)
)
else:
desc += (
" Must be '%s'." % array_enums[0]
)
else:
value_type = prop_type
value_id = prop_type
if prop.get("enum"):
if len(prop["enum"]) > 1:
value_type = "enum"
if desc:
desc += " "
desc += (
"One of: %s" % json.dumps(prop["enum"])
)
else:
if desc:
desc += " "
desc += (
"Must be '%s'." % prop["enum"][0]
)
if isinstance(value_type, list):
value_type = " or ".join(value_type)
if isinstance(prop_type, list):
prop_type = " or ".join(prop_type)
if required:
desc = "**Required.** " + desc rq = "**Required.**" if required else None
desc = " ".join(x for x in [rq, prop.get("description"), enum_desc] if x)
return { return {
"row": { "type": prop_type,
"key": key_name, "desc": desc,
"type": value_type, "enum_desc": enum_desc,
"id": value_id, "is_object": is_object,
"required": required,
"desc": desc,
},
"tables": tables, "tables": tables,
} }
@ -309,62 +275,28 @@ def deduplicate_tables(tables):
return filtered return filtered
def get_tables_for_schema(schema): def get_tables_for_schema(schema):
schema = inherit_parents(schema) pv = process_data_type(schema, enforce_title=False)
tables = get_json_schema_object_fields(schema) return deduplicate_tables(pv["tables"])
return deduplicate_tables(tables)
def get_tables_for_response(api, schema):
schema = inherit_parents(schema)
resp_type = schema.get("type")
if resp_type is None:
raise KeyError("Response definition for api '%s' missing 'type' field"
% (api))
resp_title = schema.get("title", "") def get_tables_for_response(schema):
resp_description = schema.get("description", "") pv = process_data_type(schema, enforce_title=False)
tables = deduplicate_tables(pv["tables"])
logger.debug("Found a 200 response for this API; type %s" % resp_type)
if resp_type == "object":
tables = get_json_schema_object_fields(
schema,
enforce_title=False,
)
else:
nested_items = []
if resp_type == "array":
items = inherit_parents(schema["items"])
if items["type"] == "object":
nested_items = get_json_schema_object_fields(
items,
enforce_title=True,
)
value_id = nested_items[0]["title"]
resp_type = "[%s]" % value_id
else:
raise Exception("Unsupported array response type [%s] for %s" %
(items["type"], api))
# make up the first table, with just the 'body' row in, unless the response
# is an object, in which case there's little point in having one.
if not pv["is_object"]:
tables = [{ tables = [{
"title": resp_title, "title": None,
"rows": [{ "rows": [{
"key": "<body>", "key": "<body>",
"type": resp_type, "type": pv["type"],
"desc": resp_description, "desc": pv["desc"],
}] }]
}] + nested_items }] + tables
res = deduplicate_tables(tables) logger.debug("response: %r" % tables)
if len(res) == 0: return tables
logger.warn(
"This API appears to have no response table. Are you " +
"sure this API returns no parameters?"
)
return res
def get_example_for_schema(schema): def get_example_for_schema(schema):
"""Returns a python object representing a suitable example for this object""" """Returns a python object representing a suitable example for this object"""
@ -514,7 +446,6 @@ class MatrixUnits(Units):
if good_response: if good_response:
if "schema" in good_response: if "schema" in good_response:
endpoint["res_tables"] = get_tables_for_response( endpoint["res_tables"] = get_tables_for_response(
"%s %s" % (method, path),
good_response["schema"] good_response["schema"]
) )
if "headers" in good_response: if "headers" in good_response:

Loading…
Cancel
Save