@typespec/http-client-python 0.6.3 → 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.
Files changed (67) hide show
  1. package/eng/scripts/ci/regenerate.ts +25 -8
  2. package/eng/scripts/setup/__pycache__/venvtools.cpython-38.pyc +0 -0
  3. package/generator/build/lib/pygen/codegen/models/code_model.py +4 -0
  4. package/generator/build/lib/pygen/codegen/models/combined_type.py +4 -3
  5. package/generator/build/lib/pygen/codegen/models/credential_types.py +4 -0
  6. package/generator/build/lib/pygen/codegen/models/operation.py +8 -2
  7. package/generator/build/lib/pygen/codegen/models/operation_group.py +26 -1
  8. package/generator/build/lib/pygen/codegen/models/paging_operation.py +1 -1
  9. package/generator/build/lib/pygen/codegen/models/property.py +2 -2
  10. package/generator/build/lib/pygen/codegen/serializers/__init__.py +12 -2
  11. package/generator/build/lib/pygen/codegen/serializers/model_serializer.py +2 -2
  12. package/generator/build/lib/pygen/codegen/serializers/operation_groups_serializer.py +1 -0
  13. package/generator/build/lib/pygen/codegen/templates/operation_group.py.jinja2 +4 -4
  14. package/generator/build/lib/pygen/codegen/templates/operation_groups_container.py.jinja2 +2 -0
  15. package/generator/dist/pygen-0.1.0-py3-none-any.whl +0 -0
  16. package/generator/pygen/codegen/models/code_model.py +4 -0
  17. package/generator/pygen/codegen/models/combined_type.py +4 -3
  18. package/generator/pygen/codegen/models/credential_types.py +4 -0
  19. package/generator/pygen/codegen/models/operation.py +8 -2
  20. package/generator/pygen/codegen/models/operation_group.py +26 -1
  21. package/generator/pygen/codegen/models/paging_operation.py +1 -1
  22. package/generator/pygen/codegen/models/property.py +2 -2
  23. package/generator/pygen/codegen/serializers/__init__.py +12 -2
  24. package/generator/pygen/codegen/serializers/model_serializer.py +2 -2
  25. package/generator/pygen/codegen/serializers/operation_groups_serializer.py +1 -0
  26. package/generator/pygen/codegen/templates/operation_group.py.jinja2 +4 -4
  27. package/generator/pygen/codegen/templates/operation_groups_container.py.jinja2 +2 -0
  28. package/generator/test/azure/mock_api_tests/conftest.py +57 -1
  29. package/generator/test/azure/mock_api_tests/data/image.jpg +0 -0
  30. package/generator/test/azure/mock_api_tests/data/image.png +0 -0
  31. package/generator/test/generic_mock_api_tests/conftest.py +0 -43
  32. package/generator/test/unbranded/mock_api_tests/asynctests/test_encode_duration_async.py +63 -0
  33. package/generator/test/unbranded/mock_api_tests/asynctests/test_encode_numeric_async.py +35 -0
  34. package/generator/test/unbranded/mock_api_tests/asynctests/test_parameters_basic_async.py +24 -0
  35. package/generator/test/unbranded/mock_api_tests/asynctests/test_parameters_spread_async.py +76 -0
  36. package/generator/test/unbranded/mock_api_tests/asynctests/test_payload_content_negotiation_async.py +37 -0
  37. package/generator/test/unbranded/mock_api_tests/asynctests/test_payload_multipart_async.py +154 -0
  38. package/generator/test/unbranded/mock_api_tests/asynctests/test_serialization_encoded_name_json_async.py +24 -0
  39. package/generator/test/unbranded/mock_api_tests/asynctests/test_special_words_async.py +43 -0
  40. package/generator/test/unbranded/mock_api_tests/conftest.py +57 -0
  41. package/generator/test/unbranded/mock_api_tests/data/image.jpg +0 -0
  42. package/generator/test/unbranded/mock_api_tests/data/image.png +0 -0
  43. package/generator/test/unbranded/mock_api_tests/test_encode_duration.py +60 -0
  44. package/generator/test/unbranded/mock_api_tests/test_encode_numeric.py +32 -0
  45. package/generator/test/unbranded/mock_api_tests/test_parameters_basic.py +22 -0
  46. package/generator/test/unbranded/mock_api_tests/test_parameters_spread.py +66 -0
  47. package/generator/test/unbranded/mock_api_tests/test_payload_content_negotiation.py +33 -0
  48. package/generator/test/unbranded/mock_api_tests/test_payload_multipart.py +141 -0
  49. package/generator/test/unbranded/mock_api_tests/test_serialization_encoded_name_json.py +22 -0
  50. package/generator/test/unbranded/mock_api_tests/test_special_words.py +39 -0
  51. package/package.json +1 -1
  52. /package/generator/test/{generic_mock_api_tests → azure/mock_api_tests}/asynctests/test_encode_duration_async.py +0 -0
  53. /package/generator/test/{generic_mock_api_tests → azure/mock_api_tests}/asynctests/test_encode_numeric_async.py +0 -0
  54. /package/generator/test/{generic_mock_api_tests → azure/mock_api_tests}/asynctests/test_parameters_basic_async.py +0 -0
  55. /package/generator/test/{generic_mock_api_tests → azure/mock_api_tests}/asynctests/test_parameters_spread_async.py +0 -0
  56. /package/generator/test/{generic_mock_api_tests → azure/mock_api_tests}/asynctests/test_payload_content_negotiation_async.py +0 -0
  57. /package/generator/test/{generic_mock_api_tests → azure/mock_api_tests}/asynctests/test_payload_multipart_async.py +0 -0
  58. /package/generator/test/{generic_mock_api_tests → azure/mock_api_tests}/asynctests/test_serialization_encoded_name_json_async.py +0 -0
  59. /package/generator/test/{generic_mock_api_tests → azure/mock_api_tests}/asynctests/test_special_words_async.py +0 -0
  60. /package/generator/test/{generic_mock_api_tests → azure/mock_api_tests}/test_encode_duration.py +0 -0
  61. /package/generator/test/{generic_mock_api_tests → azure/mock_api_tests}/test_encode_numeric.py +0 -0
  62. /package/generator/test/{generic_mock_api_tests → azure/mock_api_tests}/test_parameters_basic.py +0 -0
  63. /package/generator/test/{generic_mock_api_tests → azure/mock_api_tests}/test_parameters_spread.py +0 -0
  64. /package/generator/test/{generic_mock_api_tests → azure/mock_api_tests}/test_payload_content_negotiation.py +0 -0
  65. /package/generator/test/{generic_mock_api_tests → azure/mock_api_tests}/test_payload_multipart.py +0 -0
  66. /package/generator/test/{generic_mock_api_tests → azure/mock_api_tests}/test_serialization_encoded_name_json.py +0 -0
  67. /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 result = EMITTER_OPTIONS[key] || [{}];
142
- return Array.isArray(result) ? result : [result];
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
  });
@@ -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
- if len(self.types) == 2:
57
- return f"Is either a {self.types[0].type_description} type or a {self.types[1].type_description} type."
58
- return f"Is one of the following types: {', '.join([t.type_description for t in self.types])}"
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 any(r.type for r in self.responses):
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 any(r.type for r in self.responses):
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 (client_namespace_type.models or client_namespace_type.enums) and self.code_model.options["models_mode"]:
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
@@ -89,4 +89,5 @@ class OperationGroupsSerializer(BaseSerializer):
89
89
  client_namespace=self.client_namespace,
90
90
  ),
91
91
  get_request_builders=self._get_request_builders,
92
+ need_declare_serializer=any(operation_group.operations for operation_group in self.operation_groups),
92
93
  )
@@ -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
 
@@ -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
- if len(self.types) == 2:
57
- return f"Is either a {self.types[0].type_description} type or a {self.types[1].type_description} type."
58
- return f"Is one of the following types: {', '.join([t.type_description for t in self.types])}"
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 any(r.type for r in self.responses):
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 any(r.type for r in self.responses):
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 (client_namespace_type.models or client_namespace_type.enums) and self.code_model.options["models_mode"]:
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
@@ -89,4 +89,5 @@ class OperationGroupsSerializer(BaseSerializer):
89
89
  client_namespace=self.client_namespace,
90
90
  ),
91
91
  get_request_builders=self._get_request_builders,
92
+ need_declare_serializer=any(operation_group.operations for operation_group in self.operation_groups),
92
93
  )
@@ -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
 
@@ -8,9 +8,11 @@ import subprocess
8
8
  import signal
9
9
  import pytest
10
10
  import re
11
- from typing import Literal
11
+ from typing import Literal, List
12
12
  from pathlib import Path
13
13
 
14
+ FILE_FOLDER = Path(__file__).parent
15
+
14
16
 
15
17
  def start_server_process():
16
18
  azure_http_path = Path(os.path.dirname(__file__)) / Path("../../../../node_modules/@azure-tools/azure-http-specs")
@@ -149,3 +151,57 @@ def authentication_policy():
149
151
  from azure.core.pipeline.policies import SansIOHTTPPolicy
150
152
 
151
153
  return SansIOHTTPPolicy()
154
+
155
+
156
+ SPECIAL_WORDS = [
157
+ "and",
158
+ "as",
159
+ "assert",
160
+ "async",
161
+ "await",
162
+ "break",
163
+ "class",
164
+ "constructor",
165
+ "continue",
166
+ "def",
167
+ "del",
168
+ "elif",
169
+ "else",
170
+ "except",
171
+ "exec",
172
+ "finally",
173
+ "for",
174
+ "from",
175
+ "global",
176
+ "if",
177
+ "import",
178
+ "in",
179
+ "is",
180
+ "lambda",
181
+ "not",
182
+ "or",
183
+ "pass",
184
+ "raise",
185
+ "return",
186
+ "try",
187
+ "while",
188
+ "with",
189
+ "yield",
190
+ ]
191
+
192
+
193
+ @pytest.fixture
194
+ def special_words() -> List[str]:
195
+ return SPECIAL_WORDS
196
+
197
+
198
+ @pytest.fixture
199
+ def png_data() -> bytes:
200
+ with open(str(FILE_FOLDER / "data/image.png"), "rb") as file_in:
201
+ return file_in.read()
202
+
203
+
204
+ @pytest.fixture
205
+ def jpg_data() -> bytes:
206
+ with open(str(FILE_FOLDER / "data/image.jpg"), "rb") as file_in:
207
+ return file_in.read()