Skip to content

Commit 74fa0a7

Browse files
therveci.datadog-api-spec
andauthored
Remove models for arrays (#1431)
* Remove models for arrays Array introduces a level of indirection which doesn't bring much value. We're losing some validation but we gain consistency which I think is worth it. See #1377 * Fix mypy almost * Progress * Green now * Fix examples * Cleanup * Another cleanup * Fix unused models * Unless ibits * Handle Point * Fix subitem handling * Fix distribution_point_item * pre-commit fixes * Cleanup * Last fix for unparsed list? * pre-commit fixes --------- Co-authored-by: ci.datadog-api-spec <[email protected]>
1 parent 8ed9af9 commit 74fa0a7

File tree

136 files changed

+677
-2167
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

136 files changed

+677
-2167
lines changed

.generator/src/generator/formatter.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,7 @@ def format_data_with_schema_list(
237237
):
238238
"""Format data with schema."""
239239
assert version is not None
240-
name, imports = get_name_and_imports(schema, version, imports)
240+
imports = imports or defaultdict(set)
241241

242242
if "oneOf" in schema:
243243
for sub_schema in schema["oneOf"]:
@@ -263,16 +263,12 @@ def format_data_with_schema_list(
263263
d,
264264
schema["items"],
265265
replace_values=replace_values,
266-
default_name=name,
267266
version=version,
268267
)
269268
parameters += f"{value}, "
270269
imports = _merge_imports(imports, extra_imports)
271270
parameters = f"[{parameters}]"
272271

273-
if name:
274-
return f"{name}({parameters})", imports
275-
276272
return parameters, imports
277273

278274

.generator/src/generator/openapi.py

Lines changed: 67 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,9 @@ def type_to_python_helper(type_, schema, alternative_name=None, in_list=False):
3737
elif type_ == "boolean":
3838
return "bool"
3939
elif type_ == "array":
40-
subtype = type_to_python(schema["items"], in_list=True)
40+
subtype = type_to_python(schema["items"], alternative_name=alternative_name + "Item" if alternative_name else None, in_list=True)
4141
if schema["items"].get("nullable"):
42-
subtype += ", none_type"
42+
subtype += ", none_type"
4343
return "[{}]".format(subtype)
4444
elif type_ == "object":
4545
if "additionalProperties" in schema:
@@ -51,7 +51,7 @@ def type_to_python_helper(type_, schema, alternative_name=None, in_list=False):
5151
return (
5252
alternative_name
5353
if alternative_name
54-
and ("properties" in schema or "oneOf" in schema or "anyOf" in schema or "allOf" in schema)
54+
and ("properties" in schema or "oneOf" in schema)
5555
else "dict"
5656
)
5757
elif type_ == "null":
@@ -63,12 +63,15 @@ def type_to_python_helper(type_, schema, alternative_name=None, in_list=False):
6363
def type_to_python(schema, alternative_name=None, in_list=False):
6464
"""Return Python type name for the type."""
6565
name = formatter.get_name(schema)
66-
if name:
66+
if name and "items" not in schema:
6767
if "enum" in schema:
6868
return name
69-
if schema.get("type", "object") in ("object", "array"):
69+
if schema.get("type", "object") == "object":
7070
return name
7171

72+
if name:
73+
alternative_name = name
74+
7275
type_ = schema.get("type")
7376
if type_ is None:
7477
if "oneOf" in schema and in_list:
@@ -111,7 +114,7 @@ def typing_to_python_helper(type_, schema, alternative_name=None, in_list=False)
111114
elif type_ == "boolean":
112115
return "bool"
113116
elif type_ == "array":
114-
return "List[{}]".format(typing_to_python(schema["items"], in_list=True))
117+
return "List[{}]".format(typing_to_python(schema["items"], alternative_name=alternative_name + "Item" if alternative_name else None, in_list=True))
115118
elif type_ == "object":
116119
if "additionalProperties" in schema:
117120
nested_schema = schema["additionalProperties"]
@@ -122,7 +125,7 @@ def typing_to_python_helper(type_, schema, alternative_name=None, in_list=False)
122125
return (
123126
alternative_name
124127
if alternative_name
125-
and ("properties" in schema or "oneOf" in schema or "anyOf" in schema or "allOf" in schema)
128+
and ("properties" in schema or "oneOf" in schema)
126129
else "dict"
127130
)
128131
elif type_ == "null":
@@ -137,13 +140,16 @@ def typing_to_python(schema, alternative_name=None, in_list=False):
137140
if name:
138141
if "enum" in schema:
139142
return name
140-
if schema.get("type", "object") in ("object", "array"):
143+
if schema.get("type", "object") == "object":
141144
if "oneOf" in schema:
142145
types = [name]
143-
types.extend(get_oneof_types(schema))
146+
types.extend(get_oneof_types(schema, typing=True))
144147
return f"Union[{','.join(types)}]"
145148
return name
146149

150+
if name:
151+
alternative_name = name
152+
147153
type_ = schema.get("type")
148154
if type_ is None:
149155
if "oneOf" in schema and in_list:
@@ -154,7 +160,7 @@ def typing_to_python(schema, alternative_name=None, in_list=False):
154160
type_ += f"{typing_to_python_helper(child.get('type'), child, in_list=in_list)},"
155161
else:
156162
type_ += f"{typing_to_python(child, in_list=in_list)},"
157-
return type_
163+
return f"Union[{type_}]"
158164
if "items" in schema:
159165
type_ = "array"
160166

@@ -227,21 +233,18 @@ def child_models(schema, alternative_name=None, seen=None, in_list=False):
227233
name = current_name or alternative_name
228234

229235
has_sub_models = False
230-
if "allOf" in schema:
231-
has_sub_models = True
232-
for child in schema["allOf"]:
233-
yield from child_models(child, seen=seen)
234236
if "oneOf" in schema:
235-
has_sub_models = True
237+
has_sub_models = not in_list
236238
for child in schema["oneOf"]:
237-
yield from child_models(child, seen=seen)
238-
if "anyOf" in schema:
239-
has_sub_models = True
240-
for child in schema["anyOf"]:
241-
yield from child_models(child, seen=seen)
239+
sub_models = list(child_models(child, seen=seen))
240+
if sub_models:
241+
has_sub_models = True
242+
yield from sub_models
243+
if in_list and not has_sub_models:
244+
return
242245

243246
if "items" in schema:
244-
yield from child_models(schema["items"], None, seen=seen, in_list=True)
247+
yield from child_models(schema["items"], alternative_name=name + "Item" if name is not None else None, seen=seen, in_list=True)
245248

246249
if schema.get("type") == "object" or "properties" in schema or has_sub_models:
247250
if not has_sub_models and name is None:
@@ -265,13 +268,6 @@ def child_models(schema, alternative_name=None, seen=None, in_list=False):
265268
for key, child in schema.get("properties", {}).items():
266269
yield from child_models(child, alternative_name=name + formatter.camel_case(key), seen=seen)
267270

268-
if current_name and schema.get("type") == "array":
269-
if name in seen:
270-
return
271-
272-
seen.add(name)
273-
yield name, schema
274-
275271
if "enum" in schema:
276272
if name is None:
277273
raise ValueError(f"Schema {schema} has no name")
@@ -315,6 +311,17 @@ def models(spec):
315311
return name_to_schema
316312

317313

314+
def find_non_primitive_type(schema):
315+
if schema.get("enum"):
316+
return True
317+
sub_type = schema.get("type")
318+
if sub_type == "array":
319+
return find_non_primitive_type(schema["items"])
320+
if sub_type not in PRIMITIVE_TYPES:
321+
return True
322+
return False
323+
324+
318325
def get_references_for_model(model, model_name):
319326
result = {}
320327
top_name = formatter.get_name(model) or model_name
@@ -330,13 +337,12 @@ def get_references_for_model(model, model_name):
330337
if name:
331338
result[name] = None
332339
elif definition.get("type") == "array":
333-
name = formatter.get_name(definition)
334-
if name:
340+
name = formatter.get_name(definition.get("items"))
341+
if name and find_non_primitive_type(definition["items"]):
335342
result[name] = None
336-
else:
337-
name = formatter.get_name(definition.get("items"))
338-
if name:
339-
result[name] = None
343+
elif formatter.get_name(definition) and definition["items"].get("type") not in PRIMITIVE_TYPES:
344+
result[formatter.get_name(definition) + "Item"] = None
345+
340346
elif definition.get("properties") and top_name:
341347
result[top_name + formatter.camel_case(key)] = None
342348
if model.get("additionalProperties"):
@@ -365,8 +371,12 @@ def get_oneof_references_for_model(model, model_name, seen=None):
365371
if model.get("oneOf"):
366372
for schema in model["oneOf"]:
367373
type_ = schema.get("type", "object")
368-
if type_ in ("array", "object"):
374+
if type_ == "object":
369375
result[formatter.get_name(schema)] = None
376+
elif type_ == "array":
377+
sub_name = formatter.get_name(schema["items"])
378+
if sub_name:
379+
result[sub_name] = None
370380

371381
for key, definition in model.get("properties", {}).items():
372382
result.update({k: None for k in get_oneof_references_for_model(definition, model_name, seen)})
@@ -389,11 +399,18 @@ def get_oneof_parameters(model):
389399
yield attr, definition, schema
390400

391401

392-
def get_oneof_types(model):
402+
def get_oneof_types(model, typing=False):
393403
for schema in model["oneOf"]:
394404
type_ = schema.get("type", "object")
395-
if type_ in ("array", "object"):
405+
if type_ == "object":
396406
yield formatter.get_name(schema)
407+
elif type_ == "array":
408+
name = formatter.get_name(schema["items"])
409+
if name:
410+
if typing:
411+
yield f"List[{name}]"
412+
else:
413+
yield f"[{name}]"
397414
elif type_ == "integer":
398415
yield "int"
399416
elif type_ == "string":
@@ -409,8 +426,13 @@ def get_oneof_types(model):
409426
def get_oneof_models(model):
410427
result = []
411428
for schema in model["oneOf"]:
412-
if schema.get("type", "object") in ("array", "object"):
429+
type_ = schema.get("type", "object")
430+
if type_ == "object":
413431
result.append(formatter.get_name(schema))
432+
elif type_ == "array":
433+
name = formatter.get_name(schema["items"])
434+
if name:
435+
result.append(name)
414436
return result
415437

416438

@@ -464,12 +486,17 @@ def get_api_models(operations):
464486
yield name
465487
if "oneOf" in content["schema"]:
466488
for schema in content["schema"]["oneOf"]:
467-
type_ = schema.get("type", "object")
468-
if type_ in ("array", "object"):
489+
if schema.get("type", "object") == "object":
469490
name = formatter.get_name(schema)
470491
if name and name not in seen:
471492
seen.add(name)
472493
yield name
494+
if "items" in content["schema"]:
495+
name = formatter.get_name(content["schema"]["items"])
496+
if name and name not in seen:
497+
seen.add(name)
498+
yield name
499+
473500
if "x-pagination" in operation:
474501
name = get_type_at_path(operation, operation["x-pagination"]["resultsPath"])
475502
if name and name not in seen:

.generator/src/generator/templates/model_utils.j2

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ def allows_single_value_input(cls):
6565
elif issubclass(cls, ModelComposed):
6666
if not cls._composed_schemas["oneOf"]:
6767
return False
68-
return any(allows_single_value_input(c) for c in cls._composed_schemas["oneOf"])
68+
return any(allows_single_value_input(c) for c in cls._composed_schemas["oneOf"] if not isinstance(c, list))
6969
return False
7070

7171

@@ -982,7 +982,7 @@ def change_keys_js_to_python(input_dict, model_class):
982982
if issubclass(model_class, ModelComposed):
983983
attribute_map = {}
984984
for t in model_class._composed_schemas.get("oneOf", ()):
985-
if issubclass(t, OpenApiModel):
985+
if not isinstance(t, list) and issubclass(t, OpenApiModel):
986986
attribute_map.update(t.attribute_map)
987987
elif not getattr(model_class, "attribute_map", None):
988988
return input_dict
@@ -1495,7 +1495,7 @@ def get_oneof_instance(cls, model_kwargs, constant_kwargs, model_arg=None):
14951495
# none_type deserialization is handled in the __new__ method
14961496
continue
14971497

1498-
single_value_input = allows_single_value_input(oneof_class)
1498+
single_value_input = allows_single_value_input(oneof_class) if not isinstance(oneof_class, list) else True
14991499

15001500
with suppress(Exception):
15011501
if not single_value_input:
@@ -1508,7 +1508,25 @@ def get_oneof_instance(cls, model_kwargs, constant_kwargs, model_arg=None):
15081508
if not oneof_instance._unparsed:
15091509
oneof_instances.append(oneof_instance)
15101510
else:
1511-
if issubclass(oneof_class, ModelSimple):
1511+
if isinstance(oneof_class, list):
1512+
oneof_class = oneof_class[0]
1513+
list_oneof_instance = []
1514+
if model_arg is None and not model_kwargs:
1515+
# Empty data
1516+
oneof_instances.append(list_oneof_instance)
1517+
continue
1518+
for arg in model_arg:
1519+
if constant_kwargs.get("_spec_property_naming"):
1520+
oneof_instance = oneof_class(
1521+
**change_keys_js_to_python(arg, oneof_class), **constant_kwargs
1522+
)
1523+
else:
1524+
oneof_instance = oneof_class(**arg, **constant_kwargs)
1525+
if not oneof_instance._unparsed:
1526+
list_oneof_instance.append(oneof_instance)
1527+
if list_oneof_instance:
1528+
oneof_instances.append(list_oneof_instance)
1529+
elif issubclass(oneof_class, ModelSimple):
15121530
oneof_instance = oneof_class(model_arg, **constant_kwargs)
15131531
if not oneof_instance._unparsed:
15141532
oneof_instances.append(oneof_instance)
@@ -1578,7 +1596,7 @@ def validate_get_composed_info(constant_args, model_args, self):
15781596
# Create composed_instances
15791597
composed_instances = []
15801598
oneof_instance = get_oneof_instance(self.__class__, model_args, constant_args)
1581-
if oneof_instance is not None:
1599+
if oneof_instance is not None and not isinstance(oneof_instance, list):
15821600
composed_instances.append(oneof_instance)
15831601

15841602
additional_properties_model_instances = []

0 commit comments

Comments
 (0)