Skip to content
This repository was archived by the owner on Dec 25, 2024. It is now read-only.

v2 Restructures endpoint parameter, body + response information #57

Merged
merged 7 commits into from
Oct 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ public class CodegenResponse implements IJsonSchemaValidationProperties {
private Map<String, CodegenProperty> requiredVarsMap;
private String ref;
private boolean schemaIsFromAdditionalProperties;
public Set<String> imports = new TreeSet<>();

@Override
public int hashCode() {
Expand All @@ -105,7 +106,7 @@ public int hashCode() {
getMinLength(), exclusiveMinimum, exclusiveMaximum, getMinimum(), getMaximum(), getPattern(),
is1xx, is2xx, is3xx, is4xx, is5xx, additionalPropertiesIsAnyType, hasVars, hasRequired,
hasDiscriminatorWithNonEmptyMapping, composedSchemas, hasMultipleTypes, responseHeaders, content,
requiredVarsMap, ref, uniqueItemsBoolean, schemaIsFromAdditionalProperties);
requiredVarsMap, ref, uniqueItemsBoolean, schemaIsFromAdditionalProperties, imports);
}

@Override
Expand Down Expand Up @@ -155,6 +156,7 @@ public boolean equals(Object o) {
getAdditionalPropertiesIsAnyType() == that.getAdditionalPropertiesIsAnyType() &&
getHasVars() == that.getHasVars() &&
getHasRequired() == that.getHasRequired() &&
Objects.equals(imports, that.imports) &&
Objects.equals(uniqueItemsBoolean, that.getUniqueItemsBoolean()) &&
Objects.equals(ref, that.getRef()) &&
Objects.equals(requiredVarsMap, that.getRequiredVarsMap()) &&
Expand Down Expand Up @@ -612,6 +614,7 @@ public String toString() {
sb.append(", requiredVarsMap=").append(requiredVarsMap);
sb.append(", ref=").append(ref);
sb.append(", schemaIsFromAdditionalProperties=").append(schemaIsFromAdditionalProperties);
sb.append(", imports=").append(imports);
sb.append('}');
return sb.toString();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4265,13 +4265,12 @@ public CodegenOperation fromOperation(String path,
for (Entry<String, Header> entry : headers.entrySet()) {
String headerName = entry.getKey();
Header header = ModelUtils.getReferencedHeader(this.openAPI, entry.getValue());
CodegenParameter responseHeader = headerToCodegenParameter(header, headerName, imports, String.format(Locale.ROOT, "%sResponseParameter", r.code));
CodegenParameter responseHeader = headerToCodegenParameter(header, headerName, r.imports, String.format(Locale.ROOT, "%sResponseParameter", r.code));
responseHeaders.add(responseHeader);
}
r.setResponseHeaders(responseHeaders);
}
String mediaTypeSchemaSuffix = String.format(Locale.ROOT, "%sResponseBody", r.code);
r.setContent(getContent(response.getContent(), imports, mediaTypeSchemaSuffix));
r.setContent(getContent(response.getContent(), r.imports, ""));

if (!addSchemaImportsFromV3SpecLocations) {
if (r.baseType != null &&
Expand Down Expand Up @@ -4391,7 +4390,7 @@ public CodegenOperation fromOperation(String path,
param = ModelUtils.getReferencedParameter(this.openAPI, param);

CodegenParameter p = fromParameter(param, imports);
p.setContent(getContent(param.getContent(), imports, "RequestParameter" + toModelName(param.getName())));
p.setContent(getContent(param.getContent(), imports, param.getName()));

// ensure unique params
if (ensureUniqueParams) {
Expand Down Expand Up @@ -7092,10 +7091,6 @@ protected void updateRequestBodyForString(CodegenParameter codegenParameter, Sch
codegenParameter.pattern = toRegularExpression(schema.getPattern());
}

protected String toMediaTypeSchemaName(String contentType, String mediaTypeSchemaSuffix) {
return "SchemaFor" + mediaTypeSchemaSuffix + toModelName(contentType);
}

private CodegenParameter headerToCodegenParameter(Header header, String headerName, Set<String> imports, String mediaTypeSchemaSuffix) {
if (header == null) {
return null;
Expand All @@ -7121,7 +7116,7 @@ private CodegenParameter headerToCodegenParameter(Header header, String headerNa
return param;
}

protected LinkedHashMap<String, CodegenMediaType> getContent(Content content, Set<String> imports, String mediaTypeSchemaSuffix) {
protected LinkedHashMap<String, CodegenMediaType> getContent(Content content, Set<String> imports, String schemaName) {
if (content == null) {
return null;
}
Expand All @@ -7140,7 +7135,7 @@ protected LinkedHashMap<String, CodegenMediaType> getContent(Content content, Se
for (Entry<String, Header> headerEntry : encHeaders.entrySet()) {
String headerName = headerEntry.getKey();
Header header = ModelUtils.getReferencedHeader(this.openAPI, headerEntry.getValue());
CodegenParameter param = headerToCodegenParameter(header, headerName, imports, mediaTypeSchemaSuffix);
CodegenParameter param = headerToCodegenParameter(header, headerName, imports, schemaName);
headers.add(param);
}
}
Expand All @@ -7157,8 +7152,12 @@ protected LinkedHashMap<String, CodegenMediaType> getContent(Content content, Se
}
String contentType = contentEntry.getKey();
CodegenProperty schemaProp = null;
String usedSchemaName = schemaName;
if (usedSchemaName.equals("")) {
usedSchemaName = contentType;
}
if (mt.getSchema() != null) {
schemaProp = fromProperty(toMediaTypeSchemaName(contentType, mediaTypeSchemaSuffix), mt.getSchema(), false);
schemaProp = fromProperty(usedSchemaName, mt.getSchema(), false);
}
HashMap<String, SchemaTestCase> schemaTestCases = null;
if (mt.getExtensions() != null && mt.getExtensions().containsKey(xSchemaTestExamplesKey)) {
Expand Down Expand Up @@ -7207,7 +7206,7 @@ public CodegenParameter fromRequestBody(RequestBody body, Set<String> imports, S
if (schema == null) {
throw new RuntimeException("Request body cannot be null. Possible cause: missing schema in body parameter (OAS v2): " + body);
}
codegenParameter.setContent(getContent(body.getContent(), imports, "RequestBody"));
codegenParameter.setContent(getContent(body.getContent(), imports, ""));

if (StringUtils.isNotBlank(schema.get$ref())) {
name = ModelUtils.getSimpleRef(schema.get$ref());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -552,7 +552,8 @@ protected void generateEndpoints(OperationsMap objs) {
OperationMap operations = objs.getOperations();
List<CodegenOperation> codegenOperations = operations.getOperation();
HashMap<String, String> pathModuleToPath = new HashMap<>();
// paths.some_path.post.py (single endpoint definition)
// paths.some_path.post.__init__.py (single endpoint definition)
// responses are adjacent to the init file
for (CodegenOperation co: codegenOperations) {
if (co.tags != null) {
for (Tag tag: co.tags) {
Expand All @@ -574,14 +575,29 @@ protected void generateEndpoints(OperationsMap objs) {
endpointMap.put("operation", co);
endpointMap.put("imports", co.imports);
endpointMap.put("packageName", packageName);
outputFilename = packageFilename(Arrays.asList("paths", pathModuleName, co.httpMethod + ".py"));
outputFilename = packageFilename(Arrays.asList("paths", pathModuleName, co.httpMethod, "__init__.py"));
pathsFiles.add(Arrays.asList(endpointMap, "endpoint.handlebars", outputFilename));

for (CodegenResponse response: co.responses) {
// paths.some_path.post.response_for_200.py (file per response)
Map<String, Object> responseMap = new HashMap<>();
responseMap.put("response", response);
responseMap.put("packageName", packageName);
String responseModuleName = "response_for_";
if (response.isDefault) {
responseModuleName += "default";
} else {
responseModuleName += response.code;
}
String responseFilename = packageFilename(Arrays.asList("paths", pathModuleName, co.httpMethod, responseModuleName+ ".py"));
pathsFiles.add(Arrays.asList(responseMap, "endpoint_response.handlebars", responseFilename));
}
/*
This stub file exists to allow pycharm to read and use typing.overload decorators for it to see that
dict_instance["someProp"] is of type SomeClass.properties.someProp
See https://youtrack.jetbrains.com/issue/PY-42137/PyCharm-type-hinting-doesnt-work-well-with-overload-decorator
*/
String stubOutputFilename = packageFilename(Arrays.asList("paths", pathModuleName, co.httpMethod + ".pyi"));
String stubOutputFilename = packageFilename(Arrays.asList("paths", pathModuleName, co.httpMethod, "__init__.pyi"));
pathsFiles.add(Arrays.asList(endpointMap, "endpoint_stub.handlebars", stubOutputFilename));

Map<String, Object> endpointTestMap = new HashMap<>();
Expand Down Expand Up @@ -749,72 +765,7 @@ public String getHelp() {

@Override
public Schema unaliasSchema(Schema schema) {
Map<String, Schema> allSchemas = ModelUtils.getSchemas(openAPI);
if (allSchemas == null || allSchemas.isEmpty()) {
// skip the warning as the spec can have no model defined
//LOGGER.warn("allSchemas cannot be null/empty in unaliasSchema. Returned 'schema'");
return schema;
}

if (schema != null && StringUtils.isNotEmpty(schema.get$ref())) {
String simpleRef = ModelUtils.getSimpleRef(schema.get$ref());
if (schemaMapping.containsKey(simpleRef)) {
LOGGER.debug("Schema unaliasing of {} omitted because aliased class is to be mapped to {}", simpleRef, schemaMapping.get(simpleRef));
return schema;
}
Schema ref = allSchemas.get(simpleRef);
if (ref == null) {
once(LOGGER).warn("{} is not defined", schema.get$ref());
return schema;
} else if (ref.getEnum() != null && !ref.getEnum().isEmpty()) {
// top-level enum class
return schema;
} else if (ModelUtils.isArraySchema(ref)) {
if (ModelUtils.isGenerateAliasAsModel(ref)) {
return schema; // generate a model extending array
} else {
return unaliasSchema(allSchemas.get(ModelUtils.getSimpleRef(schema.get$ref())));
}
} else if (ModelUtils.isComposedSchema(ref)) {
return schema;
} else if (ModelUtils.isMapSchema(ref)) {
if (ref.getProperties() != null && !ref.getProperties().isEmpty()) // has at least one property
return schema; // treat it as model
else {
if (ModelUtils.isGenerateAliasAsModel(ref)) {
return schema; // generate a model extending map
} else {
// treat it as a typical map
return unaliasSchema(allSchemas.get(ModelUtils.getSimpleRef(schema.get$ref())));
}
}
} else if (ModelUtils.isObjectSchema(ref)) { // model
if (ref.getProperties() != null && !ref.getProperties().isEmpty()) { // has at least one property
return schema;
} else {
// free form object (type: object)
if (ModelUtils.hasValidation(ref)) {
return schema;
} else if (getAllOfDescendants(simpleRef, openAPI).size() > 0) {
return schema;
}
return unaliasSchema(allSchemas.get(ModelUtils.getSimpleRef(schema.get$ref())));
}
} else if (ModelUtils.hasValidation(ref)) {
// non object non array non map schemas that have validations
// are returned so we can generate those schemas as models
// we do this to:
// - preserve the validations in that model class in python
// - use those validations when we use this schema in composed oneOf schemas
return schema;
} else if (Boolean.TRUE.equals(ref.getNullable()) && ref.getEnum() == null) {
// non enum primitive with nullable True
// we make these models so instances of this will be subclasses of this model
return schema;
} else {
return unaliasSchema(allSchemas.get(ModelUtils.getSimpleRef(schema.get$ref())));
}
}
// python allows schemas to be inlined at any location so unaliasing should do nothing
return schema;
}

Expand Down Expand Up @@ -892,6 +843,17 @@ public String toModelImport(String name) {
return "from " + packagePath() + "." + modelPackage() + "." + toModelFilename(name) + " import " + toModelName(name);
}

private void fixSchemaImports(Set<String> imports) {
if (imports.size() == 0) {
return;
}
String[] modelNames = imports.toArray(new String[0]);
imports.clear();
for (String modelName : modelNames) {
imports.add(toModelImport(modelName));
}
}

@Override
@SuppressWarnings("static-method")
public OperationsMap postProcessOperationsWithModels(OperationsMap objs, List<ModelMap> allModels) {
Expand All @@ -902,13 +864,9 @@ public OperationsMap postProcessOperationsWithModels(OperationsMap objs, List<Mo
OperationMap val = objs.getOperations();
List<CodegenOperation> operations = val.getOperation();
for (CodegenOperation operation : operations) {
if (operation.imports.size() == 0) {
continue;
}
String[] modelNames = operation.imports.toArray(new String[0]);
operation.imports.clear();
for (String modelName : modelNames) {
operation.imports.add(toModelImport(modelName));
fixSchemaImports(operation.imports);
for (CodegenResponse response: operation.responses) {
fixSchemaImports(response.imports);
}
}
generateEndpoints(objs);
Expand Down Expand Up @@ -996,18 +954,6 @@ public CodegenParameter fromParameter(Parameter parameter, Set<String> imports)
break;
}
}
// clone this so we can change some properties on it
CodegenProperty schemaProp = cp.getSchema();
// parameters may have valid python names like some_val or invalid ones like Content-Type
// we always set nameInSnakeCase to null so special handling will not be done for these names
// invalid python names will be handled in python by using a TypedDict which will allow us to have a type hint
// for keys that cannot be variable names to the schema baseName
if (schemaProp != null) {
schemaProp = schemaProp.clone();
schemaProp.nameInSnakeCase = null;
schemaProp.baseName = toModelName(cp.baseName) + "Schema";
cp.setSchema(schemaProp);
}
return cp;
}

Expand Down Expand Up @@ -2650,13 +2596,17 @@ public List<CodegenParameter> fromRequestBodyToFormParameters(RequestBody body,
Schema schema = ModelUtils.getSchemaFromRequestBody(body);
schema = ModelUtils.getReferencedSchema(this.openAPI, schema);
CodegenParameter cp = fromFormProperty("body", schema, imports);
cp.setContent(getContent(body.getContent(), imports, "RequestBody"));
cp.setContent(getContent(body.getContent(), imports, ""));
cp.isFormParam = false;
cp.isBodyParam = true;
parameters.add(cp);
return parameters;
}

protected boolean needToImport(String type) {
return true;
}

/**
* Custom version of this method so we can move the body parameter into bodyParam
*
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# coding: utf-8
{{>partial_header}}

from dataclasses import dataclass
import dataclasses
from decimal import Decimal
import enum
import email
Expand Down Expand Up @@ -339,7 +339,7 @@ class JSONDetector:
return False


@dataclass
@dataclasses.dataclass
class ParameterBase(JSONDetector):
name: str
in_type: ParameterInType
Expand Down Expand Up @@ -779,7 +779,7 @@ class Encoding:
self.allow_reserved = allow_reserved


@dataclass
@dataclasses.dataclass
class MediaType:
"""
Used to store request and response body schema information
Expand All @@ -793,7 +793,7 @@ class MediaType:
encoding: typing.Optional[typing.Dict[str, Encoding]] = None


@dataclass
@dataclasses.dataclass
class ApiResponse:
response: urllib3.HTTPResponse
body: typing.Union[Unset, Schema]
Expand All @@ -802,8 +802,8 @@ class ApiResponse:
def __init__(
self,
response: urllib3.HTTPResponse,
body: typing.Union[Unset, typing.Type[Schema]],
headers: typing.Union[Unset, typing.List[HeaderParameter]]
body: typing.Union[Unset, typing.Type[Schema]] = unset,
headers: typing.Union[Unset, typing.List[HeaderParameter]] = unset
):
"""
pycharm needs this to prevent 'Unexpected argument' warnings
Expand All @@ -813,7 +813,7 @@ class ApiResponse:
self.headers = headers


@dataclass
@dataclasses.dataclass
class ApiResponseWithoutDeserialization(ApiResponse):
response: urllib3.HTTPResponse
body: typing.Union[Unset, typing.Type[Schema]] = unset
Expand Down
Loading