@typespec/http-client-python 0.6.2 → 0.6.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/eng/scripts/ci/regenerate.ts +25 -8
- package/eng/scripts/setup/__pycache__/venvtools.cpython-38.pyc +0 -0
- package/generator/build/lib/pygen/codegen/models/code_model.py +4 -0
- package/generator/build/lib/pygen/codegen/models/combined_type.py +4 -3
- package/generator/build/lib/pygen/codegen/models/credential_types.py +4 -0
- package/generator/build/lib/pygen/codegen/models/operation.py +8 -2
- package/generator/build/lib/pygen/codegen/models/operation_group.py +26 -1
- package/generator/build/lib/pygen/codegen/models/paging_operation.py +1 -1
- package/generator/build/lib/pygen/codegen/models/property.py +2 -2
- package/generator/build/lib/pygen/codegen/serializers/__init__.py +12 -2
- package/generator/build/lib/pygen/codegen/serializers/model_serializer.py +2 -2
- package/generator/build/lib/pygen/codegen/serializers/operation_groups_serializer.py +1 -0
- package/generator/build/lib/pygen/codegen/templates/operation_group.py.jinja2 +4 -4
- package/generator/build/lib/pygen/codegen/templates/operation_groups_container.py.jinja2 +2 -0
- package/generator/build/lib/pygen/codegen/templates/serialization.py.jinja2 +2 -68
- package/generator/dist/pygen-0.1.0-py3-none-any.whl +0 -0
- package/generator/pygen/codegen/models/code_model.py +4 -0
- package/generator/pygen/codegen/models/combined_type.py +4 -3
- package/generator/pygen/codegen/models/credential_types.py +4 -0
- package/generator/pygen/codegen/models/operation.py +8 -2
- package/generator/pygen/codegen/models/operation_group.py +26 -1
- package/generator/pygen/codegen/models/paging_operation.py +1 -1
- package/generator/pygen/codegen/models/property.py +2 -2
- package/generator/pygen/codegen/serializers/__init__.py +12 -2
- package/generator/pygen/codegen/serializers/model_serializer.py +2 -2
- package/generator/pygen/codegen/serializers/operation_groups_serializer.py +1 -0
- package/generator/pygen/codegen/templates/operation_group.py.jinja2 +4 -4
- package/generator/pygen/codegen/templates/operation_groups_container.py.jinja2 +2 -0
- package/generator/pygen/codegen/templates/serialization.py.jinja2 +2 -68
- package/generator/test/azure/mock_api_tests/conftest.py +57 -1
- package/generator/test/azure/mock_api_tests/data/image.jpg +0 -0
- package/generator/test/azure/mock_api_tests/data/image.png +0 -0
- package/generator/test/generic_mock_api_tests/conftest.py +0 -43
- package/generator/test/unbranded/mock_api_tests/asynctests/test_encode_duration_async.py +63 -0
- package/generator/test/unbranded/mock_api_tests/asynctests/test_encode_numeric_async.py +35 -0
- package/generator/test/unbranded/mock_api_tests/asynctests/test_parameters_basic_async.py +24 -0
- package/generator/test/unbranded/mock_api_tests/asynctests/test_parameters_spread_async.py +76 -0
- package/generator/test/unbranded/mock_api_tests/asynctests/test_payload_content_negotiation_async.py +37 -0
- package/generator/test/unbranded/mock_api_tests/asynctests/test_payload_multipart_async.py +154 -0
- package/generator/test/unbranded/mock_api_tests/asynctests/test_serialization_encoded_name_json_async.py +24 -0
- package/generator/test/unbranded/mock_api_tests/asynctests/test_special_words_async.py +43 -0
- package/generator/test/unbranded/mock_api_tests/conftest.py +57 -0
- package/generator/test/unbranded/mock_api_tests/data/image.jpg +0 -0
- package/generator/test/unbranded/mock_api_tests/data/image.png +0 -0
- package/generator/test/unbranded/mock_api_tests/test_encode_duration.py +60 -0
- package/generator/test/unbranded/mock_api_tests/test_encode_numeric.py +32 -0
- package/generator/test/unbranded/mock_api_tests/test_parameters_basic.py +22 -0
- package/generator/test/unbranded/mock_api_tests/test_parameters_spread.py +66 -0
- package/generator/test/unbranded/mock_api_tests/test_payload_content_negotiation.py +33 -0
- package/generator/test/unbranded/mock_api_tests/test_payload_multipart.py +141 -0
- package/generator/test/unbranded/mock_api_tests/test_serialization_encoded_name_json.py +22 -0
- package/generator/test/unbranded/mock_api_tests/test_special_words.py +39 -0
- package/package.json +1 -1
- /package/generator/test/{generic_mock_api_tests → azure/mock_api_tests}/asynctests/test_encode_duration_async.py +0 -0
- /package/generator/test/{generic_mock_api_tests → azure/mock_api_tests}/asynctests/test_encode_numeric_async.py +0 -0
- /package/generator/test/{generic_mock_api_tests → azure/mock_api_tests}/asynctests/test_parameters_basic_async.py +0 -0
- /package/generator/test/{generic_mock_api_tests → azure/mock_api_tests}/asynctests/test_parameters_spread_async.py +0 -0
- /package/generator/test/{generic_mock_api_tests → azure/mock_api_tests}/asynctests/test_payload_content_negotiation_async.py +0 -0
- /package/generator/test/{generic_mock_api_tests → azure/mock_api_tests}/asynctests/test_payload_multipart_async.py +0 -0
- /package/generator/test/{generic_mock_api_tests → azure/mock_api_tests}/asynctests/test_serialization_encoded_name_json_async.py +0 -0
- /package/generator/test/{generic_mock_api_tests → azure/mock_api_tests}/asynctests/test_special_words_async.py +0 -0
- /package/generator/test/{generic_mock_api_tests → azure/mock_api_tests}/test_encode_duration.py +0 -0
- /package/generator/test/{generic_mock_api_tests → azure/mock_api_tests}/test_encode_numeric.py +0 -0
- /package/generator/test/{generic_mock_api_tests → azure/mock_api_tests}/test_parameters_basic.py +0 -0
- /package/generator/test/{generic_mock_api_tests → azure/mock_api_tests}/test_parameters_spread.py +0 -0
- /package/generator/test/{generic_mock_api_tests → azure/mock_api_tests}/test_payload_content_negotiation.py +0 -0
- /package/generator/test/{generic_mock_api_tests → azure/mock_api_tests}/test_payload_multipart.py +0 -0
- /package/generator/test/{generic_mock_api_tests → azure/mock_api_tests}/test_serialization_encoded_name_json.py +0 -0
- /package/generator/test/{generic_mock_api_tests → azure/mock_api_tests}/test_special_words.py +0 -0
|
@@ -132,14 +132,35 @@ function toPosix(dir: string): string {
|
|
|
132
132
|
return dir.replace(/\\/g, "/");
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
-
function getEmitterOption(spec: string): Record<string, string>[] {
|
|
135
|
+
function getEmitterOption(spec: string, flavor: string): Record<string, string>[] {
|
|
136
136
|
const specDir = spec.includes("azure") ? AZURE_HTTP_SPECS : HTTP_SPECS;
|
|
137
137
|
const relativeSpec = toPosix(relative(specDir, spec));
|
|
138
138
|
const key = relativeSpec.includes("resiliency/srv-driven/old.tsp")
|
|
139
139
|
? relativeSpec
|
|
140
140
|
: dirname(relativeSpec);
|
|
141
|
-
const
|
|
142
|
-
|
|
141
|
+
const emitter_options = EMITTER_OPTIONS[key] || [{}];
|
|
142
|
+
const result = Array.isArray(emitter_options) ? emitter_options : [emitter_options];
|
|
143
|
+
|
|
144
|
+
function updateOptions(options: Record<string, string>): void {
|
|
145
|
+
if (options["package-name"] && options["enable-typespec-namespace"] === undefined) {
|
|
146
|
+
options["enable-typespec-namespace"] = "false";
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// when package name is different with typespec namespace, disable typespec namespace
|
|
151
|
+
if (flavor !== "azure") {
|
|
152
|
+
for (const options of result) {
|
|
153
|
+
if (Array.isArray(options)) {
|
|
154
|
+
for (const option of options) {
|
|
155
|
+
updateOptions(option);
|
|
156
|
+
}
|
|
157
|
+
} else {
|
|
158
|
+
updateOptions(options);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return result;
|
|
143
164
|
}
|
|
144
165
|
|
|
145
166
|
// Function to execute CLI commands asynchronously
|
|
@@ -172,7 +193,6 @@ interface RegenerateFlags {
|
|
|
172
193
|
debug: boolean;
|
|
173
194
|
name?: string;
|
|
174
195
|
pyodide?: boolean;
|
|
175
|
-
"enable-typespec-namespace"?: boolean;
|
|
176
196
|
}
|
|
177
197
|
|
|
178
198
|
const SpecialFlags: Record<string, Record<string, any>> = {
|
|
@@ -249,7 +269,7 @@ function addOptions(
|
|
|
249
269
|
flags: RegenerateFlags,
|
|
250
270
|
): EmitterConfig[] {
|
|
251
271
|
const emitterConfigs: EmitterConfig[] = [];
|
|
252
|
-
for (const config of getEmitterOption(spec)) {
|
|
272
|
+
for (const config of getEmitterOption(spec, flags.flavor)) {
|
|
253
273
|
const options: Record<string, string> = { ...config };
|
|
254
274
|
if (flags.pyodide) {
|
|
255
275
|
options["use-pyodide"] = "true";
|
|
@@ -271,9 +291,6 @@ function addOptions(
|
|
|
271
291
|
options["company-name"] = "Unbranded";
|
|
272
292
|
}
|
|
273
293
|
options["examples-dir"] = toPosix(join(dirname(spec), "examples"));
|
|
274
|
-
if (options["enable-typespec-namespace"] === undefined) {
|
|
275
|
-
options["enable-typespec-namespace"] = "false";
|
|
276
|
-
}
|
|
277
294
|
const configs = Object.entries(options).flatMap(([k, v]) => {
|
|
278
295
|
return `--option ${argv.values.emitterName || "@typespec/http-client-python"}.${k}=${v}`;
|
|
279
296
|
});
|
|
Binary file
|
|
@@ -402,3 +402,7 @@ class CodeModel: # pylint: disable=too-many-public-methods, disable=too-many-in
|
|
|
402
402
|
@property
|
|
403
403
|
def is_legacy(self) -> bool:
|
|
404
404
|
return _is_legacy(self.options)
|
|
405
|
+
|
|
406
|
+
@staticmethod
|
|
407
|
+
def has_non_json_models(models: List[ModelType]) -> bool:
|
|
408
|
+
return any(m for m in models if m.base != "json")
|
|
@@ -53,9 +53,10 @@ class CombinedType(BaseType):
|
|
|
53
53
|
return self.yaml_data.get("clientDefaultValue")
|
|
54
54
|
|
|
55
55
|
def description(self, *, is_operation_file: bool) -> str:
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
type_descriptions = list({t.type_description: None for t in self.types}.keys())
|
|
57
|
+
if len(type_descriptions) == 2:
|
|
58
|
+
return f"Is either a {type_descriptions[0]} type or a {type_descriptions[1]} type."
|
|
59
|
+
return f"Is one of the following types: {', '.join(t for t in type_descriptions)}"
|
|
59
60
|
|
|
60
61
|
def docstring_text(self, **kwargs: Any) -> str:
|
|
61
62
|
return " or ".join(t.docstring_text(**kwargs) for t in self.types)
|
|
@@ -198,6 +198,10 @@ class KeyCredentialType(CredentialType[KeyCredentialPolicyType]):
|
|
|
198
198
|
def type_annotation(self, **kwargs: Any) -> str:
|
|
199
199
|
return self.policy.credential_name
|
|
200
200
|
|
|
201
|
+
@property
|
|
202
|
+
def type_description(self) -> str:
|
|
203
|
+
return "key credential"
|
|
204
|
+
|
|
201
205
|
@property
|
|
202
206
|
def instance_check_template(self) -> str:
|
|
203
207
|
return "isinstance({}, " + f"{self.policy.credential_name})"
|
|
@@ -35,6 +35,7 @@ from .parameter import (
|
|
|
35
35
|
)
|
|
36
36
|
from .parameter_list import ParameterList
|
|
37
37
|
from .model_type import ModelType
|
|
38
|
+
from .primitive_types import BinaryIteratorType, BinaryType
|
|
38
39
|
from .base import BaseType
|
|
39
40
|
from .combined_type import CombinedType
|
|
40
41
|
from .request_builder import OverloadedRequestBuilder, RequestBuilder
|
|
@@ -313,6 +314,10 @@ class OperationBase( # pylint: disable=too-many-public-methods,too-many-instanc
|
|
|
313
314
|
)
|
|
314
315
|
return file_import
|
|
315
316
|
|
|
317
|
+
@property
|
|
318
|
+
def need_deserialize(self) -> bool:
|
|
319
|
+
return any(r.type and not isinstance(r.type, BinaryIteratorType) for r in self.responses)
|
|
320
|
+
|
|
316
321
|
def imports( # pylint: disable=too-many-branches, disable=too-many-statements
|
|
317
322
|
self, async_mode: bool, **kwargs: Any
|
|
318
323
|
) -> FileImport:
|
|
@@ -432,7 +437,8 @@ class OperationBase( # pylint: disable=too-many-public-methods,too-many-instanc
|
|
|
432
437
|
file_import.add_submodule_import("typing", "overload", ImportType.STDLIB)
|
|
433
438
|
if self.code_model.options["models_mode"] == "dpg":
|
|
434
439
|
relative_path = self.code_model.get_relative_import_path(serialize_namespace, module_name="_model_base")
|
|
435
|
-
if self.parameters.has_body
|
|
440
|
+
body_param = self.parameters.body_parameter if self.parameters.has_body else None
|
|
441
|
+
if body_param and not isinstance(body_param.type, BinaryType):
|
|
436
442
|
if self.has_form_data_body:
|
|
437
443
|
file_import.add_submodule_import(
|
|
438
444
|
self.code_model.get_relative_import_path(serialize_namespace), "_model_base", ImportType.LOCAL
|
|
@@ -452,7 +458,7 @@ class OperationBase( # pylint: disable=too-many-public-methods,too-many-instanc
|
|
|
452
458
|
file_import.add_import("json", ImportType.STDLIB)
|
|
453
459
|
if any(xml_serializable(str(r.default_content_type)) for r in self.responses):
|
|
454
460
|
file_import.add_submodule_import(relative_path, "_deserialize_xml", ImportType.LOCAL)
|
|
455
|
-
elif
|
|
461
|
+
elif self.need_deserialize:
|
|
456
462
|
file_import.add_submodule_import(relative_path, "_deserialize", ImportType.LOCAL)
|
|
457
463
|
if self.default_error_deserialization or self.non_default_errors:
|
|
458
464
|
file_import.add_submodule_import(relative_path, "_failsafe_deserialize", ImportType.LOCAL)
|
|
@@ -9,7 +9,7 @@ from .utils import OrderedSet
|
|
|
9
9
|
|
|
10
10
|
from .base import BaseModel
|
|
11
11
|
from .operation import get_operation
|
|
12
|
-
from .imports import FileImport, ImportType, TypingSection
|
|
12
|
+
from .imports import FileImport, ImportType, TypingSection, MsrestImportType
|
|
13
13
|
from .utils import add_to_pylint_disable, NamespaceType
|
|
14
14
|
from .lro_operation import LROOperation
|
|
15
15
|
from .lro_paging_operation import LROPagingOperation
|
|
@@ -152,6 +152,31 @@ class OperationGroup(BaseModel):
|
|
|
152
152
|
f"{self.client.name}MixinABC",
|
|
153
153
|
ImportType.LOCAL,
|
|
154
154
|
)
|
|
155
|
+
else:
|
|
156
|
+
file_import.add_submodule_import(
|
|
157
|
+
"" if self.code_model.is_azure_flavor else "runtime",
|
|
158
|
+
f"{'Async' if async_mode else ''}PipelineClient",
|
|
159
|
+
ImportType.SDKCORE,
|
|
160
|
+
)
|
|
161
|
+
file_import.add_submodule_import(
|
|
162
|
+
self.code_model.get_relative_import_path(
|
|
163
|
+
serialize_namespace,
|
|
164
|
+
self.code_model.get_imported_namespace_for_client(self.client.client_namespace, async_mode),
|
|
165
|
+
module_name="_configuration",
|
|
166
|
+
),
|
|
167
|
+
f"{self.client.name}Configuration",
|
|
168
|
+
ImportType.LOCAL,
|
|
169
|
+
)
|
|
170
|
+
file_import.add_msrest_import(
|
|
171
|
+
serialize_namespace=kwargs.get("serialize_namespace", self.code_model.namespace),
|
|
172
|
+
msrest_import_type=MsrestImportType.Serializer,
|
|
173
|
+
typing_section=TypingSection.REGULAR,
|
|
174
|
+
)
|
|
175
|
+
file_import.add_msrest_import(
|
|
176
|
+
serialize_namespace=kwargs.get("serialize_namespace", self.code_model.namespace),
|
|
177
|
+
msrest_import_type=MsrestImportType.SerializerDeserializer,
|
|
178
|
+
typing_section=TypingSection.REGULAR,
|
|
179
|
+
)
|
|
155
180
|
if self.has_abstract_operations:
|
|
156
181
|
file_import.add_submodule_import(
|
|
157
182
|
# raise_if_not_implemented is always defined in _vendor of top namespace
|
|
@@ -150,7 +150,7 @@ class PagingOperationBase(OperationBase[PagingResponseType]):
|
|
|
150
150
|
if self.code_model.options["models_mode"] == "dpg":
|
|
151
151
|
relative_path = self.code_model.get_relative_import_path(serialize_namespace, module_name="_model_base")
|
|
152
152
|
file_import.merge(self.item_type.imports(**kwargs))
|
|
153
|
-
if self.default_error_deserialization or
|
|
153
|
+
if self.default_error_deserialization or self.need_deserialize:
|
|
154
154
|
file_import.add_submodule_import(relative_path, "_deserialize", ImportType.LOCAL)
|
|
155
155
|
return file_import
|
|
156
156
|
|
|
@@ -102,7 +102,7 @@ class Property(BaseModel): # pylint: disable=too-many-instance-attributes
|
|
|
102
102
|
if self.is_base_discriminator:
|
|
103
103
|
return "str"
|
|
104
104
|
types_type_annotation = self.type.type_annotation(is_operation_file=is_operation_file, **kwargs)
|
|
105
|
-
if self.optional and self.client_default_value is None:
|
|
105
|
+
if (self.optional and self.client_default_value is None) or self.readonly:
|
|
106
106
|
return f"Optional[{types_type_annotation}]"
|
|
107
107
|
return types_type_annotation
|
|
108
108
|
|
|
@@ -144,7 +144,7 @@ class Property(BaseModel): # pylint: disable=too-many-instance-attributes
|
|
|
144
144
|
if self.is_discriminator and isinstance(self.type, EnumType):
|
|
145
145
|
return file_import
|
|
146
146
|
file_import.merge(self.type.imports(**kwargs))
|
|
147
|
-
if self.optional and self.client_default_value is None:
|
|
147
|
+
if (self.optional and self.client_default_value is None) or self.readonly:
|
|
148
148
|
file_import.add_submodule_import("typing", "Optional", ImportType.STDLIB)
|
|
149
149
|
if self.code_model.options["models_mode"] == "dpg":
|
|
150
150
|
serialize_namespace = kwargs.get("serialize_namespace", self.code_model.namespace)
|
|
@@ -159,7 +159,9 @@ class JinjaSerializer(ReaderAndWriter):
|
|
|
159
159
|
self._serialize_and_write_top_level_folder(env=env, namespace=client_namespace)
|
|
160
160
|
|
|
161
161
|
# add models folder if there are models in this namespace
|
|
162
|
-
if (
|
|
162
|
+
if (
|
|
163
|
+
self.code_model.has_non_json_models(client_namespace_type.models) or client_namespace_type.enums
|
|
164
|
+
) and self.code_model.options["models_mode"]:
|
|
163
165
|
self._serialize_and_write_models_folder(
|
|
164
166
|
env=env,
|
|
165
167
|
namespace=client_namespace,
|
|
@@ -181,6 +183,14 @@ class JinjaSerializer(ReaderAndWriter):
|
|
|
181
183
|
if self.code_model.options["multiapi"]:
|
|
182
184
|
self._serialize_and_write_metadata(env=env, namespace=client_namespace)
|
|
183
185
|
|
|
186
|
+
# if there are only operations under this namespace, we need to add general __init__.py into `aio` folder
|
|
187
|
+
# to make sure all generated files could be packed into .zip/.whl/.tgz package
|
|
188
|
+
if not client_namespace_type.clients and client_namespace_type.operation_groups and self.has_aio_folder:
|
|
189
|
+
self.write_file(
|
|
190
|
+
exec_path / Path("aio/__init__.py"),
|
|
191
|
+
general_serializer.serialize_pkgutil_init_file(),
|
|
192
|
+
)
|
|
193
|
+
|
|
184
194
|
def _serialize_and_write_package_files(self, client_namespace: str) -> None:
|
|
185
195
|
root_of_sdk = self.exec_path(client_namespace)
|
|
186
196
|
if self.code_model.options["package_mode"] in VALID_PACKAGE_MODE:
|
|
@@ -230,7 +240,7 @@ class JinjaSerializer(ReaderAndWriter):
|
|
|
230
240
|
# Write the models folder
|
|
231
241
|
models_path = self.exec_path(namespace + ".models")
|
|
232
242
|
serializer = DpgModelSerializer if self.code_model.options["models_mode"] == "dpg" else MsrestModelSerializer
|
|
233
|
-
if models:
|
|
243
|
+
if self.code_model.has_non_json_models(models):
|
|
234
244
|
self.write_file(
|
|
235
245
|
models_path / Path(f"{self.code_model.models_filename}.py"),
|
|
236
246
|
serializer(code_model=self.code_model, env=env, client_namespace=namespace, models=models).serialize(),
|
|
@@ -192,9 +192,9 @@ class MsrestModelSerializer(_ModelSerializer):
|
|
|
192
192
|
if prop.is_discriminator:
|
|
193
193
|
init_args.append(self.initialize_discriminator_property(model, prop))
|
|
194
194
|
elif prop.readonly:
|
|
195
|
-
init_args.append(f"self.{prop.client_name} = None")
|
|
195
|
+
init_args.append(f"self.{prop.client_name}: {prop.type_annotation()} = None")
|
|
196
196
|
elif not prop.constant:
|
|
197
|
-
init_args.append(f"self.{prop.client_name} = {prop.client_name}")
|
|
197
|
+
init_args.append(f"self.{prop.client_name}: {prop.type_annotation()} = {prop.client_name}")
|
|
198
198
|
return init_args
|
|
199
199
|
|
|
200
200
|
@staticmethod
|
|
@@ -31,10 +31,10 @@ class {{ operation_group.class_name }}: {{ operation_group.pylint_disable() }}
|
|
|
31
31
|
{% endif %}
|
|
32
32
|
def __init__(self, *args, **kwargs){{ return_none_type_annotation }}:
|
|
33
33
|
input_args = list(args)
|
|
34
|
-
self._client = input_args.pop(0) if input_args else kwargs.pop("client")
|
|
35
|
-
self._config = input_args.pop(0) if input_args else kwargs.pop("config")
|
|
36
|
-
self._serialize = input_args.pop(0) if input_args else kwargs.pop("serializer")
|
|
37
|
-
self._deserialize = input_args.pop(0) if input_args else kwargs.pop("deserializer")
|
|
34
|
+
self._client: {{ 'Async' if async_mode else ''}}PipelineClient = input_args.pop(0) if input_args else kwargs.pop("client")
|
|
35
|
+
self._config: {{ operation_group.client.name }}Configuration = input_args.pop(0) if input_args else kwargs.pop("config")
|
|
36
|
+
self._serialize: Serializer = input_args.pop(0) if input_args else kwargs.pop("serializer")
|
|
37
|
+
self._deserialize: Deserializer = input_args.pop(0) if input_args else kwargs.pop("deserializer")
|
|
38
38
|
{% if code_model.options["multiapi"] %}
|
|
39
39
|
self._api_version = input_args.pop(0) if input_args else kwargs.pop("api_version")
|
|
40
40
|
{% endif %}
|
|
@@ -6,7 +6,9 @@
|
|
|
6
6
|
{{ imports }}
|
|
7
7
|
{{ unset }}
|
|
8
8
|
{% if code_model.options["builders_visibility"] == "embedded" and not async_mode %}
|
|
9
|
+
{% if need_declare_serializer %}
|
|
9
10
|
{{ op_tools.declare_serializer(code_model) }}
|
|
11
|
+
{% endif %}
|
|
10
12
|
{% for operation_group in operation_groups %}
|
|
11
13
|
{% for request_builder in get_request_builders(operation_group) %}
|
|
12
14
|
|
|
@@ -184,73 +184,7 @@ try:
|
|
|
184
184
|
except NameError:
|
|
185
185
|
_long_type = int
|
|
186
186
|
|
|
187
|
-
|
|
188
|
-
class UTC(datetime.tzinfo):
|
|
189
|
-
"""Time Zone info for handling UTC"""
|
|
190
|
-
|
|
191
|
-
def utcoffset(self, dt):
|
|
192
|
-
"""UTF offset for UTC is 0.
|
|
193
|
-
|
|
194
|
-
:param datetime.datetime dt: The datetime
|
|
195
|
-
:returns: The offset
|
|
196
|
-
:rtype: datetime.timedelta
|
|
197
|
-
"""
|
|
198
|
-
return datetime.timedelta(0)
|
|
199
|
-
|
|
200
|
-
def tzname(self, dt):
|
|
201
|
-
"""Timestamp representation.
|
|
202
|
-
|
|
203
|
-
:param datetime.datetime dt: The datetime
|
|
204
|
-
:returns: The timestamp representation
|
|
205
|
-
:rtype: str
|
|
206
|
-
"""
|
|
207
|
-
return "Z"
|
|
208
|
-
|
|
209
|
-
def dst(self, dt):
|
|
210
|
-
"""No daylight saving for UTC.
|
|
211
|
-
|
|
212
|
-
:param datetime.datetime dt: The datetime
|
|
213
|
-
:returns: The daylight saving time
|
|
214
|
-
:rtype: datetime.timedelta
|
|
215
|
-
"""
|
|
216
|
-
return datetime.timedelta(hours=1)
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
try:
|
|
220
|
-
from datetime import timezone as _FixedOffset # type: ignore
|
|
221
|
-
except ImportError: # Python 2.7
|
|
222
|
-
|
|
223
|
-
class _FixedOffset(datetime.tzinfo): # type: ignore
|
|
224
|
-
"""Fixed offset in minutes east from UTC.
|
|
225
|
-
Copy/pasted from Python doc
|
|
226
|
-
:param datetime.timedelta offset: offset in timedelta format
|
|
227
|
-
"""
|
|
228
|
-
|
|
229
|
-
def __init__(self, offset) -> None:
|
|
230
|
-
self.__offset = offset
|
|
231
|
-
|
|
232
|
-
def utcoffset(self, dt):
|
|
233
|
-
return self.__offset
|
|
234
|
-
|
|
235
|
-
def tzname(self, dt):
|
|
236
|
-
return str(self.__offset.total_seconds() / 3600)
|
|
237
|
-
|
|
238
|
-
def __repr__(self):
|
|
239
|
-
return "<FixedOffset {}>".format(self.tzname(None))
|
|
240
|
-
|
|
241
|
-
def dst(self, dt):
|
|
242
|
-
return datetime.timedelta(0)
|
|
243
|
-
|
|
244
|
-
def __getinitargs__(self):
|
|
245
|
-
return (self.__offset,)
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
try:
|
|
249
|
-
from datetime import timezone
|
|
250
|
-
|
|
251
|
-
TZ_UTC = timezone.utc
|
|
252
|
-
except ImportError:
|
|
253
|
-
TZ_UTC = UTC() # type: ignore
|
|
187
|
+
TZ_UTC = datetime.timezone.utc
|
|
254
188
|
|
|
255
189
|
_FLATTEN = re.compile(r"(?<!\\)\.")
|
|
256
190
|
|
|
@@ -2050,7 +1984,7 @@ class Deserializer:
|
|
|
2050
1984
|
try:
|
|
2051
1985
|
parsed_date = email.utils.parsedate_tz(attr) # type: ignore
|
|
2052
1986
|
date_obj = datetime.datetime(
|
|
2053
|
-
*parsed_date[:6], tzinfo=
|
|
1987
|
+
*parsed_date[:6], tzinfo=datetime.timezone(datetime.timedelta(minutes=(parsed_date[9] or 0) / 60))
|
|
2054
1988
|
)
|
|
2055
1989
|
if not date_obj.tzinfo:
|
|
2056
1990
|
date_obj = date_obj.astimezone(tz=TZ_UTC)
|
|
Binary file
|
|
@@ -402,3 +402,7 @@ class CodeModel: # pylint: disable=too-many-public-methods, disable=too-many-in
|
|
|
402
402
|
@property
|
|
403
403
|
def is_legacy(self) -> bool:
|
|
404
404
|
return _is_legacy(self.options)
|
|
405
|
+
|
|
406
|
+
@staticmethod
|
|
407
|
+
def has_non_json_models(models: List[ModelType]) -> bool:
|
|
408
|
+
return any(m for m in models if m.base != "json")
|
|
@@ -53,9 +53,10 @@ class CombinedType(BaseType):
|
|
|
53
53
|
return self.yaml_data.get("clientDefaultValue")
|
|
54
54
|
|
|
55
55
|
def description(self, *, is_operation_file: bool) -> str:
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
type_descriptions = list({t.type_description: None for t in self.types}.keys())
|
|
57
|
+
if len(type_descriptions) == 2:
|
|
58
|
+
return f"Is either a {type_descriptions[0]} type or a {type_descriptions[1]} type."
|
|
59
|
+
return f"Is one of the following types: {', '.join(t for t in type_descriptions)}"
|
|
59
60
|
|
|
60
61
|
def docstring_text(self, **kwargs: Any) -> str:
|
|
61
62
|
return " or ".join(t.docstring_text(**kwargs) for t in self.types)
|
|
@@ -198,6 +198,10 @@ class KeyCredentialType(CredentialType[KeyCredentialPolicyType]):
|
|
|
198
198
|
def type_annotation(self, **kwargs: Any) -> str:
|
|
199
199
|
return self.policy.credential_name
|
|
200
200
|
|
|
201
|
+
@property
|
|
202
|
+
def type_description(self) -> str:
|
|
203
|
+
return "key credential"
|
|
204
|
+
|
|
201
205
|
@property
|
|
202
206
|
def instance_check_template(self) -> str:
|
|
203
207
|
return "isinstance({}, " + f"{self.policy.credential_name})"
|
|
@@ -35,6 +35,7 @@ from .parameter import (
|
|
|
35
35
|
)
|
|
36
36
|
from .parameter_list import ParameterList
|
|
37
37
|
from .model_type import ModelType
|
|
38
|
+
from .primitive_types import BinaryIteratorType, BinaryType
|
|
38
39
|
from .base import BaseType
|
|
39
40
|
from .combined_type import CombinedType
|
|
40
41
|
from .request_builder import OverloadedRequestBuilder, RequestBuilder
|
|
@@ -313,6 +314,10 @@ class OperationBase( # pylint: disable=too-many-public-methods,too-many-instanc
|
|
|
313
314
|
)
|
|
314
315
|
return file_import
|
|
315
316
|
|
|
317
|
+
@property
|
|
318
|
+
def need_deserialize(self) -> bool:
|
|
319
|
+
return any(r.type and not isinstance(r.type, BinaryIteratorType) for r in self.responses)
|
|
320
|
+
|
|
316
321
|
def imports( # pylint: disable=too-many-branches, disable=too-many-statements
|
|
317
322
|
self, async_mode: bool, **kwargs: Any
|
|
318
323
|
) -> FileImport:
|
|
@@ -432,7 +437,8 @@ class OperationBase( # pylint: disable=too-many-public-methods,too-many-instanc
|
|
|
432
437
|
file_import.add_submodule_import("typing", "overload", ImportType.STDLIB)
|
|
433
438
|
if self.code_model.options["models_mode"] == "dpg":
|
|
434
439
|
relative_path = self.code_model.get_relative_import_path(serialize_namespace, module_name="_model_base")
|
|
435
|
-
if self.parameters.has_body
|
|
440
|
+
body_param = self.parameters.body_parameter if self.parameters.has_body else None
|
|
441
|
+
if body_param and not isinstance(body_param.type, BinaryType):
|
|
436
442
|
if self.has_form_data_body:
|
|
437
443
|
file_import.add_submodule_import(
|
|
438
444
|
self.code_model.get_relative_import_path(serialize_namespace), "_model_base", ImportType.LOCAL
|
|
@@ -452,7 +458,7 @@ class OperationBase( # pylint: disable=too-many-public-methods,too-many-instanc
|
|
|
452
458
|
file_import.add_import("json", ImportType.STDLIB)
|
|
453
459
|
if any(xml_serializable(str(r.default_content_type)) for r in self.responses):
|
|
454
460
|
file_import.add_submodule_import(relative_path, "_deserialize_xml", ImportType.LOCAL)
|
|
455
|
-
elif
|
|
461
|
+
elif self.need_deserialize:
|
|
456
462
|
file_import.add_submodule_import(relative_path, "_deserialize", ImportType.LOCAL)
|
|
457
463
|
if self.default_error_deserialization or self.non_default_errors:
|
|
458
464
|
file_import.add_submodule_import(relative_path, "_failsafe_deserialize", ImportType.LOCAL)
|
|
@@ -9,7 +9,7 @@ from .utils import OrderedSet
|
|
|
9
9
|
|
|
10
10
|
from .base import BaseModel
|
|
11
11
|
from .operation import get_operation
|
|
12
|
-
from .imports import FileImport, ImportType, TypingSection
|
|
12
|
+
from .imports import FileImport, ImportType, TypingSection, MsrestImportType
|
|
13
13
|
from .utils import add_to_pylint_disable, NamespaceType
|
|
14
14
|
from .lro_operation import LROOperation
|
|
15
15
|
from .lro_paging_operation import LROPagingOperation
|
|
@@ -152,6 +152,31 @@ class OperationGroup(BaseModel):
|
|
|
152
152
|
f"{self.client.name}MixinABC",
|
|
153
153
|
ImportType.LOCAL,
|
|
154
154
|
)
|
|
155
|
+
else:
|
|
156
|
+
file_import.add_submodule_import(
|
|
157
|
+
"" if self.code_model.is_azure_flavor else "runtime",
|
|
158
|
+
f"{'Async' if async_mode else ''}PipelineClient",
|
|
159
|
+
ImportType.SDKCORE,
|
|
160
|
+
)
|
|
161
|
+
file_import.add_submodule_import(
|
|
162
|
+
self.code_model.get_relative_import_path(
|
|
163
|
+
serialize_namespace,
|
|
164
|
+
self.code_model.get_imported_namespace_for_client(self.client.client_namespace, async_mode),
|
|
165
|
+
module_name="_configuration",
|
|
166
|
+
),
|
|
167
|
+
f"{self.client.name}Configuration",
|
|
168
|
+
ImportType.LOCAL,
|
|
169
|
+
)
|
|
170
|
+
file_import.add_msrest_import(
|
|
171
|
+
serialize_namespace=kwargs.get("serialize_namespace", self.code_model.namespace),
|
|
172
|
+
msrest_import_type=MsrestImportType.Serializer,
|
|
173
|
+
typing_section=TypingSection.REGULAR,
|
|
174
|
+
)
|
|
175
|
+
file_import.add_msrest_import(
|
|
176
|
+
serialize_namespace=kwargs.get("serialize_namespace", self.code_model.namespace),
|
|
177
|
+
msrest_import_type=MsrestImportType.SerializerDeserializer,
|
|
178
|
+
typing_section=TypingSection.REGULAR,
|
|
179
|
+
)
|
|
155
180
|
if self.has_abstract_operations:
|
|
156
181
|
file_import.add_submodule_import(
|
|
157
182
|
# raise_if_not_implemented is always defined in _vendor of top namespace
|
|
@@ -150,7 +150,7 @@ class PagingOperationBase(OperationBase[PagingResponseType]):
|
|
|
150
150
|
if self.code_model.options["models_mode"] == "dpg":
|
|
151
151
|
relative_path = self.code_model.get_relative_import_path(serialize_namespace, module_name="_model_base")
|
|
152
152
|
file_import.merge(self.item_type.imports(**kwargs))
|
|
153
|
-
if self.default_error_deserialization or
|
|
153
|
+
if self.default_error_deserialization or self.need_deserialize:
|
|
154
154
|
file_import.add_submodule_import(relative_path, "_deserialize", ImportType.LOCAL)
|
|
155
155
|
return file_import
|
|
156
156
|
|
|
@@ -102,7 +102,7 @@ class Property(BaseModel): # pylint: disable=too-many-instance-attributes
|
|
|
102
102
|
if self.is_base_discriminator:
|
|
103
103
|
return "str"
|
|
104
104
|
types_type_annotation = self.type.type_annotation(is_operation_file=is_operation_file, **kwargs)
|
|
105
|
-
if self.optional and self.client_default_value is None:
|
|
105
|
+
if (self.optional and self.client_default_value is None) or self.readonly:
|
|
106
106
|
return f"Optional[{types_type_annotation}]"
|
|
107
107
|
return types_type_annotation
|
|
108
108
|
|
|
@@ -144,7 +144,7 @@ class Property(BaseModel): # pylint: disable=too-many-instance-attributes
|
|
|
144
144
|
if self.is_discriminator and isinstance(self.type, EnumType):
|
|
145
145
|
return file_import
|
|
146
146
|
file_import.merge(self.type.imports(**kwargs))
|
|
147
|
-
if self.optional and self.client_default_value is None:
|
|
147
|
+
if (self.optional and self.client_default_value is None) or self.readonly:
|
|
148
148
|
file_import.add_submodule_import("typing", "Optional", ImportType.STDLIB)
|
|
149
149
|
if self.code_model.options["models_mode"] == "dpg":
|
|
150
150
|
serialize_namespace = kwargs.get("serialize_namespace", self.code_model.namespace)
|
|
@@ -159,7 +159,9 @@ class JinjaSerializer(ReaderAndWriter):
|
|
|
159
159
|
self._serialize_and_write_top_level_folder(env=env, namespace=client_namespace)
|
|
160
160
|
|
|
161
161
|
# add models folder if there are models in this namespace
|
|
162
|
-
if (
|
|
162
|
+
if (
|
|
163
|
+
self.code_model.has_non_json_models(client_namespace_type.models) or client_namespace_type.enums
|
|
164
|
+
) and self.code_model.options["models_mode"]:
|
|
163
165
|
self._serialize_and_write_models_folder(
|
|
164
166
|
env=env,
|
|
165
167
|
namespace=client_namespace,
|
|
@@ -181,6 +183,14 @@ class JinjaSerializer(ReaderAndWriter):
|
|
|
181
183
|
if self.code_model.options["multiapi"]:
|
|
182
184
|
self._serialize_and_write_metadata(env=env, namespace=client_namespace)
|
|
183
185
|
|
|
186
|
+
# if there are only operations under this namespace, we need to add general __init__.py into `aio` folder
|
|
187
|
+
# to make sure all generated files could be packed into .zip/.whl/.tgz package
|
|
188
|
+
if not client_namespace_type.clients and client_namespace_type.operation_groups and self.has_aio_folder:
|
|
189
|
+
self.write_file(
|
|
190
|
+
exec_path / Path("aio/__init__.py"),
|
|
191
|
+
general_serializer.serialize_pkgutil_init_file(),
|
|
192
|
+
)
|
|
193
|
+
|
|
184
194
|
def _serialize_and_write_package_files(self, client_namespace: str) -> None:
|
|
185
195
|
root_of_sdk = self.exec_path(client_namespace)
|
|
186
196
|
if self.code_model.options["package_mode"] in VALID_PACKAGE_MODE:
|
|
@@ -230,7 +240,7 @@ class JinjaSerializer(ReaderAndWriter):
|
|
|
230
240
|
# Write the models folder
|
|
231
241
|
models_path = self.exec_path(namespace + ".models")
|
|
232
242
|
serializer = DpgModelSerializer if self.code_model.options["models_mode"] == "dpg" else MsrestModelSerializer
|
|
233
|
-
if models:
|
|
243
|
+
if self.code_model.has_non_json_models(models):
|
|
234
244
|
self.write_file(
|
|
235
245
|
models_path / Path(f"{self.code_model.models_filename}.py"),
|
|
236
246
|
serializer(code_model=self.code_model, env=env, client_namespace=namespace, models=models).serialize(),
|
|
@@ -192,9 +192,9 @@ class MsrestModelSerializer(_ModelSerializer):
|
|
|
192
192
|
if prop.is_discriminator:
|
|
193
193
|
init_args.append(self.initialize_discriminator_property(model, prop))
|
|
194
194
|
elif prop.readonly:
|
|
195
|
-
init_args.append(f"self.{prop.client_name} = None")
|
|
195
|
+
init_args.append(f"self.{prop.client_name}: {prop.type_annotation()} = None")
|
|
196
196
|
elif not prop.constant:
|
|
197
|
-
init_args.append(f"self.{prop.client_name} = {prop.client_name}")
|
|
197
|
+
init_args.append(f"self.{prop.client_name}: {prop.type_annotation()} = {prop.client_name}")
|
|
198
198
|
return init_args
|
|
199
199
|
|
|
200
200
|
@staticmethod
|
|
@@ -31,10 +31,10 @@ class {{ operation_group.class_name }}: {{ operation_group.pylint_disable() }}
|
|
|
31
31
|
{% endif %}
|
|
32
32
|
def __init__(self, *args, **kwargs){{ return_none_type_annotation }}:
|
|
33
33
|
input_args = list(args)
|
|
34
|
-
self._client = input_args.pop(0) if input_args else kwargs.pop("client")
|
|
35
|
-
self._config = input_args.pop(0) if input_args else kwargs.pop("config")
|
|
36
|
-
self._serialize = input_args.pop(0) if input_args else kwargs.pop("serializer")
|
|
37
|
-
self._deserialize = input_args.pop(0) if input_args else kwargs.pop("deserializer")
|
|
34
|
+
self._client: {{ 'Async' if async_mode else ''}}PipelineClient = input_args.pop(0) if input_args else kwargs.pop("client")
|
|
35
|
+
self._config: {{ operation_group.client.name }}Configuration = input_args.pop(0) if input_args else kwargs.pop("config")
|
|
36
|
+
self._serialize: Serializer = input_args.pop(0) if input_args else kwargs.pop("serializer")
|
|
37
|
+
self._deserialize: Deserializer = input_args.pop(0) if input_args else kwargs.pop("deserializer")
|
|
38
38
|
{% if code_model.options["multiapi"] %}
|
|
39
39
|
self._api_version = input_args.pop(0) if input_args else kwargs.pop("api_version")
|
|
40
40
|
{% endif %}
|
|
@@ -6,7 +6,9 @@
|
|
|
6
6
|
{{ imports }}
|
|
7
7
|
{{ unset }}
|
|
8
8
|
{% if code_model.options["builders_visibility"] == "embedded" and not async_mode %}
|
|
9
|
+
{% if need_declare_serializer %}
|
|
9
10
|
{{ op_tools.declare_serializer(code_model) }}
|
|
11
|
+
{% endif %}
|
|
10
12
|
{% for operation_group in operation_groups %}
|
|
11
13
|
{% for request_builder in get_request_builders(operation_group) %}
|
|
12
14
|
|