Skip to content

[WIP] flatten JSONModelType body properties as operation parameters #1623

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Jan 6, 2023
16 changes: 16 additions & 0 deletions packages/autorest.python/ChangeLog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
# Release History

### 2023-01-xx - 6.xx.xx

| Library | Min Version |
| ----------------------------------------------------------------------- | ----------- |
| `@autorest/core` | `3.9.2` |
| `@autorest/modelerfour` | `4.24.3` |
| `azure-core` dep of generated code | `1.24.0` |
| `isodate` dep of generated code | `0.6.1` |
| `msrest` dep of generated code (If generating legacy code) | `0.7.1` |
| `azure-mgmt-core` dep of generated code (If generating mgmt plane code) | `1.3.2` |
| `typing-extensions` dep of generated code (If generating with constants) | `4.0.1` |

**New Features**

- Flatten JSONModelType body properties as operation parameters #1623

### 2022-12-16 - 6.2.13

| Library | Min Version |
Expand Down
23 changes: 22 additions & 1 deletion packages/autorest.python/autorest/codegen/models/parameter.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
from .base import BaseType
from .constant_type import ConstantType
from .utils import add_to_description
from .combined_type import CombinedType
from .model_type import JSONModelType

if TYPE_CHECKING:
from .code_model import CodeModel
Expand Down Expand Up @@ -241,6 +243,20 @@ def content_types(self) -> List[str]:
def default_content_type(self) -> str:
return self.yaml_data["defaultContentType"]

@staticmethod
def _has_json_model_type(t: BaseType) -> bool:
if isinstance(t, JSONModelType):
return True
if isinstance(t, CombinedType):
for sub_t in t.types:
if BodyParameter._has_json_model_type(sub_t):
return True
return False

@property
def has_json_model_type(self) -> bool:
return BodyParameter._has_json_model_type(self.type)

@classmethod
def from_yaml(
cls, yaml_data: Dict[str, Any], code_model: "CodeModel"
Expand Down Expand Up @@ -314,6 +330,7 @@ def __init__(
self.in_overload: bool = self.yaml_data["inOverload"]
self.in_overriden: bool = self.yaml_data.get("inOverriden", False)
self.delimiter: Optional[ParameterDelimeter] = self.yaml_data.get("delimiter")
self.in_flattened_body: bool = self.yaml_data.get("inFlattenedBody", False)

@property
def in_method_signature(self) -> bool:
Expand All @@ -334,9 +351,13 @@ def is_content_type(self) -> bool:
return bool(self.rest_api_name) and self.rest_api_name.lower() == "content-type"

@property
def method_location(self) -> ParameterMethodLocation:
def method_location( # pylint: disable=too-many-return-statements
self,
) -> ParameterMethodLocation:
if not self.in_method_signature:
raise ValueError(f"Parameter '{self.client_name}' is not in the method.")
if self.code_model.options["models_mode"] == "dpg" and self.in_flattened_body:
return ParameterMethodLocation.KEYWORD_ONLY
if self.grouper:
return ParameterMethodLocation.POSITIONAL
if self.constant:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
MultipartBodyParameter,
Property,
RequestBuilderType,
JSONModelType,
CombinedType,
)
from .parameter_serializer import ParameterSerializer, PopKwargType
from . import utils
Expand Down Expand Up @@ -146,6 +148,25 @@ def _serialize_flattened_body(body_parameter: BodyParameter) -> List[str]:
return retval


def _serialize_json_model_body(body_parameter: BodyParameter) -> List[str]:
retval: List[str] = []
if not body_parameter.property_to_parameter_name:
raise ValueError(
"This method can't be called if the operation doesn't need parameter flattening"
)

retval.append(f"if {body_parameter.client_name} is None:")
parameter_string = ", \n".join(
f'"{property_name}": {parameter_name}'
for property_name, parameter_name in body_parameter.property_to_parameter_name.items()
)
model_type = cast(ModelType, body_parameter.type)
if isinstance(model_type, CombinedType):
model_type = next(t for t in model_type.types if isinstance(t, JSONModelType))
retval.append(f" {body_parameter.client_name} = {{{parameter_string}}}")
return retval


def _serialize_multipart_body(builder: BuilderType) -> List[str]:
retval: List[str] = []
body_param = cast(MultipartBodyParameter, builder.parameters.body_parameter)
Expand Down Expand Up @@ -928,6 +949,12 @@ def _call_request_builder_helper( # pylint: disable=too-many-statements
if builder.parameters.has_body and builder.parameters.body_parameter.flattened:
# unflatten before passing to request builder as well
retval.extend(_serialize_flattened_body(builder.parameters.body_parameter))
if (
builder.parameters.has_body
and builder.parameters.body_parameter.has_json_model_type
and any(p.in_flattened_body for p in builder.parameters.parameters)
):
retval.extend(_serialize_json_model_body(builder.parameters.body_parameter))
if builder.overloads:
# we are only dealing with two overloads. If there are three, we generate an abstract operation
retval.extend(self._initialize_overloads(builder, is_paging=is_paging))
Expand Down
20 changes: 16 additions & 4 deletions packages/autorest.python/autorest/preprocess/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,21 @@ def update_overload_section(
overload_h["type"] = original_h["type"]


def add_overload(yaml_data: Dict[str, Any], body_type: Dict[str, Any]):
def add_overload(
yaml_data: Dict[str, Any], body_type: Dict[str, Any], for_flatten_params=False
):
overload = copy.deepcopy(yaml_data)
overload["isOverload"] = True
overload["bodyParameter"]["type"] = body_type

overload["overloads"] = []

if for_flatten_params:
overload["bodyParameter"]["flattened"] = True
else:
overload["parameters"] = [
p for p in overload["parameters"] if not p.get("inFlattenedBody")
]
# for yaml sync, we need to make sure all of the responses, parameters, and exceptions' types have the same yaml id
for overload_p, original_p in zip(overload["parameters"], yaml_data["parameters"]):
overload_p["type"] = original_p["type"]
Expand Down Expand Up @@ -107,6 +115,10 @@ def add_overloads_for_body_param(yaml_data: Dict[str, Any]) -> None:
):
continue
yaml_data["overloads"].append(add_overload(yaml_data, body_type))
if body_type.get("type") == "model" and body_type.get("base") == "json":
yaml_data["overloads"].append(
add_overload(yaml_data, body_type, for_flatten_params=True)
)
content_type_param = next(
p for p in yaml_data["parameters"] if p["restApiName"].lower() == "content-type"
)
Expand Down Expand Up @@ -162,9 +174,9 @@ def update_parameter(yaml_data: Dict[str, Any]) -> None:
if yaml_data.get("propertyToParameterName"):
# need to create a new one with padded keys and values
yaml_data["propertyToParameterName"] = {
pad_reserved_words(prop, PadType.PROPERTY)
.lower(): pad_reserved_words(param_name, PadType.PARAMETER)
.lower()
pad_reserved_words(prop, PadType.PROPERTY): pad_reserved_words(
param_name, PadType.PARAMETER
)
for prop, param_name in yaml_data["propertyToParameterName"].items()
}

Expand Down
1 change: 1 addition & 0 deletions packages/cadl-python/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
**Other Changes**

- Support multiple authentication #1626
- Flatten JSONModelType body properties as operation parameters #1623

**Bug Fixes**

Expand Down
31 changes: 31 additions & 0 deletions packages/cadl-python/src/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,30 @@ function emitContentTypeParameter(
};
}

function emitFlattenedParameter(
bodyParameter: Record<string, any>,
property: Record<string, any>,
): Record<string, any> {
return {
checkClientInput: false,
clientDefaultValue: null,
clientName: property.clientName,
delimiter: null,
description: property.description,
implementation: "Method",
inDocstring: true,
inFlattenedBody: true,
inOverload: false,
inOverriden: false,
isApiVersion: bodyParameter["isApiVersion"],
location: "other",
optional: property["optional"],
restApiName: null,
skipUrlEncoding: false,
type: property["type"],
};
}

function getConstantType(key: string): Record<string, any> {
const cache = simpleTypesMap.get(key);
if (cache) {
Expand Down Expand Up @@ -614,6 +638,13 @@ function emitBasicOperation(program: Program, operation: Operation, operationGro
if (parameters.filter((e) => e.restApiName.toLowerCase() === "content-type").length === 0) {
parameters.push(emitContentTypeParameter(bodyParameter, isOverload, isOverriden));
}
if (bodyParameter.type.type == "model" && bodyParameter.type.base == "json") {
bodyParameter["propertyToParameterName"] = {};
for (const property of bodyParameter.type.properties) {
bodyParameter["propertyToParameterName"][property["restApiName"]] = property["clientName"];
parameters.push(emitFlattenedParameter(bodyParameter, property));
}
}
}
const name = camelToSnakeCase(operation.name);
return {
Expand Down