@typespec/http-client-python 0.32.0 → 0.34.0
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/dist/emitter/emitter.js +10 -2
- package/dist/emitter/emitter.js.map +1 -1
- package/dist/emitter/lib.d.ts +6 -0
- package/dist/emitter/lib.d.ts.map +1 -1
- package/dist/emitter/lib.js +29 -0
- package/dist/emitter/lib.js.map +1 -1
- package/dist/emitter/types.d.ts.map +1 -1
- package/dist/emitter/types.js +2 -0
- package/dist/emitter/types.js.map +1 -1
- package/emitter/src/emitter.ts +9 -1
- package/emitter/src/lib.ts +36 -0
- package/emitter/src/types.ts +2 -0
- package/emitter/temp/tsconfig.tsbuildinfo +1 -1
- package/eng/scripts/ci/regenerate-common.ts +40 -15
- package/eng/scripts/setup/__pycache__/package_manager.cpython-311.pyc +0 -0
- package/eng/scripts/setup/__pycache__/venvtools.cpython-311.pyc +0 -0
- package/generator/build/lib/pygen/__init__.py +4 -3
- package/generator/build/lib/pygen/codegen/models/__init__.py +4 -2
- package/generator/build/lib/pygen/codegen/models/code_model.py +27 -5
- package/generator/build/lib/pygen/codegen/models/combined_type.py +3 -3
- package/generator/build/lib/pygen/codegen/models/enum_type.py +75 -25
- package/generator/build/lib/pygen/codegen/models/list_type.py +10 -4
- package/generator/build/lib/pygen/codegen/models/model_type.py +145 -4
- package/generator/build/lib/pygen/codegen/models/operation.py +1 -0
- package/generator/build/lib/pygen/codegen/models/paging_operation.py +5 -1
- package/generator/build/lib/pygen/codegen/models/parameter.py +2 -2
- package/generator/build/lib/pygen/codegen/models/primitive_types.py +30 -6
- package/generator/build/lib/pygen/codegen/models/property.py +21 -8
- package/generator/build/lib/pygen/codegen/models/response.py +16 -3
- package/generator/build/lib/pygen/codegen/models/utils.py +12 -10
- package/generator/build/lib/pygen/codegen/serializers/__init__.py +46 -11
- package/generator/build/lib/pygen/codegen/serializers/builder_serializer.py +48 -15
- package/generator/build/lib/pygen/codegen/serializers/general_serializer.py +32 -2
- package/generator/build/lib/pygen/codegen/serializers/model_init_serializer.py +1 -1
- package/generator/build/lib/pygen/codegen/serializers/model_serializer.py +127 -3
- package/generator/build/lib/pygen/codegen/serializers/types_serializer.py +263 -12
- package/generator/build/lib/pygen/codegen/serializers/unions_serializer.py +44 -0
- package/generator/build/lib/pygen/codegen/templates/model_container.py.jinja2 +1 -1
- package/generator/build/lib/pygen/codegen/templates/packaging_templates/pyproject.toml.jinja2 +30 -2
- package/generator/build/lib/pygen/codegen/templates/types.py.jinja2 +51 -2
- package/generator/build/lib/pygen/codegen/templates/unions.py.jinja2 +9 -0
- package/generator/build/lib/pygen/preprocess/__init__.py +104 -13
- package/generator/build/lib/pygen/utils.py +16 -0
- package/generator/dist/pygen-0.1.0-py3-none-any.whl +0 -0
- package/generator/pygen/__init__.py +4 -3
- package/generator/pygen/codegen/models/__init__.py +4 -2
- package/generator/pygen/codegen/models/code_model.py +27 -5
- package/generator/pygen/codegen/models/combined_type.py +3 -3
- package/generator/pygen/codegen/models/enum_type.py +75 -25
- package/generator/pygen/codegen/models/list_type.py +10 -4
- package/generator/pygen/codegen/models/model_type.py +145 -4
- package/generator/pygen/codegen/models/operation.py +1 -0
- package/generator/pygen/codegen/models/paging_operation.py +5 -1
- package/generator/pygen/codegen/models/parameter.py +2 -2
- package/generator/pygen/codegen/models/primitive_types.py +30 -6
- package/generator/pygen/codegen/models/property.py +21 -8
- package/generator/pygen/codegen/models/response.py +16 -3
- package/generator/pygen/codegen/models/utils.py +12 -10
- package/generator/pygen/codegen/serializers/__init__.py +46 -11
- package/generator/pygen/codegen/serializers/builder_serializer.py +48 -15
- package/generator/pygen/codegen/serializers/general_serializer.py +32 -2
- package/generator/pygen/codegen/serializers/model_init_serializer.py +1 -1
- package/generator/pygen/codegen/serializers/model_serializer.py +127 -3
- package/generator/pygen/codegen/serializers/types_serializer.py +263 -12
- package/generator/pygen/codegen/serializers/unions_serializer.py +44 -0
- package/generator/pygen/codegen/templates/model_container.py.jinja2 +1 -1
- package/generator/pygen/codegen/templates/packaging_templates/pyproject.toml.jinja2 +30 -2
- package/generator/pygen/codegen/templates/types.py.jinja2 +51 -2
- package/generator/pygen/codegen/templates/unions.py.jinja2 +9 -0
- package/generator/pygen/preprocess/__init__.py +104 -13
- package/generator/pygen/utils.py +16 -0
- package/generator/pygen.egg-info/SOURCES.txt +2 -0
- package/package.json +5 -5
|
@@ -77,11 +77,19 @@ class ModelType(BaseType): # pylint: disable=too-many-instance-attributes, too-
|
|
|
77
77
|
self.cross_language_definition_id: Optional[str] = self.yaml_data.get("crossLanguageDefinitionId")
|
|
78
78
|
self.usage: int = self.yaml_data.get("usage", UsageFlags.Input.value | UsageFlags.Output.value)
|
|
79
79
|
self.client_namespace: str = self.yaml_data.get("clientNamespace", code_model.namespace)
|
|
80
|
+
self.is_typed_dict_only: bool = (
|
|
81
|
+
self.yaml_data.get("typedDictOnly", False) or code_model.options["models-mode"] == "typeddict"
|
|
82
|
+
)
|
|
80
83
|
|
|
81
84
|
@property
|
|
82
85
|
def is_usage_output(self) -> bool:
|
|
83
86
|
return bool(self.usage & UsageFlags.Output.value)
|
|
84
87
|
|
|
88
|
+
@property
|
|
89
|
+
def is_used_in_operations_via_types(self) -> bool:
|
|
90
|
+
"""Whether this model would be imported from types.py (not models) in operations."""
|
|
91
|
+
return False
|
|
92
|
+
|
|
85
93
|
@property
|
|
86
94
|
def flattened_property(self) -> Optional[Property]:
|
|
87
95
|
try:
|
|
@@ -275,12 +283,20 @@ class GeneratedModelType(ModelType):
|
|
|
275
283
|
def type_annotation(self, **kwargs: Any) -> str:
|
|
276
284
|
is_operation_file = kwargs.pop("is_operation_file", False)
|
|
277
285
|
skip_quote = kwargs.get("skip_quote", False)
|
|
286
|
+
serialize_namespace_type = kwargs.get("serialize_namespace_type")
|
|
278
287
|
module_name = ""
|
|
279
|
-
|
|
288
|
+
# In types.py, use bare name to avoid pyright "variable in type expression" errors
|
|
289
|
+
if serialize_namespace_type == NamespaceType.TYPES_FILE:
|
|
290
|
+
pass # no module prefix, no internal file prefix
|
|
291
|
+
elif kwargs.get("need_model_alias", True):
|
|
280
292
|
serialize_namespace = kwargs.get("serialize_namespace", self.code_model.namespace)
|
|
281
293
|
model_alias = self.code_model.get_unique_models_alias(serialize_namespace, self.client_namespace)
|
|
282
294
|
module_name = f"{model_alias}."
|
|
283
|
-
file_name =
|
|
295
|
+
file_name = (
|
|
296
|
+
f"{self.code_model.models_filename}."
|
|
297
|
+
if self.internal and serialize_namespace_type != NamespaceType.TYPES_FILE
|
|
298
|
+
else ""
|
|
299
|
+
)
|
|
284
300
|
retval = module_name + file_name + self.name
|
|
285
301
|
return retval if is_operation_file or skip_quote else f'"{retval}"'
|
|
286
302
|
|
|
@@ -305,7 +321,7 @@ class GeneratedModelType(ModelType):
|
|
|
305
321
|
alias = self.code_model.get_unique_models_alias(serialize_namespace, self.client_namespace)
|
|
306
322
|
serialize_namespace_type = kwargs.get("serialize_namespace_type")
|
|
307
323
|
called_by_property = kwargs.get("called_by_property", False)
|
|
308
|
-
# add import for models in operations or
|
|
324
|
+
# add import for models in operations, types, or unions file
|
|
309
325
|
if serialize_namespace_type in [NamespaceType.OPERATION, NamespaceType.CLIENT]:
|
|
310
326
|
file_import.add_submodule_import(
|
|
311
327
|
relative_path,
|
|
@@ -320,7 +336,33 @@ class GeneratedModelType(ModelType):
|
|
|
320
336
|
ImportType.LOCAL,
|
|
321
337
|
alias="_Model",
|
|
322
338
|
)
|
|
323
|
-
elif serialize_namespace_type == NamespaceType.TYPES_FILE
|
|
339
|
+
elif serialize_namespace_type == NamespaceType.TYPES_FILE:
|
|
340
|
+
# Don't import models that will be defined in this namespace's types.py —
|
|
341
|
+
# either as TypedDict classes (non-discriminated) or as Union aliases (discriminated bases).
|
|
342
|
+
# Only same-namespace non-json models are in the same types.py file.
|
|
343
|
+
same_namespace = relative_path == "."
|
|
344
|
+
will_be_in_types_file = self.base != "json" and same_namespace
|
|
345
|
+
if not will_be_in_types_file:
|
|
346
|
+
if same_namespace:
|
|
347
|
+
# json models from same namespace — import from .models (or .models._models for internal)
|
|
348
|
+
import_path = f".models.{self.code_model.models_filename}" if self.internal else ".models"
|
|
349
|
+
file_import.add_submodule_import(
|
|
350
|
+
import_path,
|
|
351
|
+
self.name,
|
|
352
|
+
ImportType.LOCAL,
|
|
353
|
+
typing_section=TypingSection.TYPING,
|
|
354
|
+
)
|
|
355
|
+
else:
|
|
356
|
+
# Cross-namespace model — import from sibling namespace's types module
|
|
357
|
+
file_import.add_submodule_import(
|
|
358
|
+
self.code_model.get_relative_import_path(
|
|
359
|
+
serialize_namespace, self.client_namespace, module_name="types"
|
|
360
|
+
),
|
|
361
|
+
self.name,
|
|
362
|
+
ImportType.LOCAL,
|
|
363
|
+
typing_section=TypingSection.TYPING,
|
|
364
|
+
)
|
|
365
|
+
elif serialize_namespace_type == NamespaceType.UNIONS_FILE or (
|
|
324
366
|
serialize_namespace_type == NamespaceType.MODEL and called_by_property
|
|
325
367
|
):
|
|
326
368
|
file_import.add_submodule_import(
|
|
@@ -352,6 +394,33 @@ class MsrestModelType(GeneratedModelType):
|
|
|
352
394
|
class DPGModelType(GeneratedModelType):
|
|
353
395
|
base = "dpg"
|
|
354
396
|
|
|
397
|
+
@property
|
|
398
|
+
def is_used_in_operations_via_types(self) -> bool:
|
|
399
|
+
return self.is_typed_dict_only
|
|
400
|
+
|
|
401
|
+
def type_annotation(self, **kwargs: Any) -> str:
|
|
402
|
+
if self.is_typed_dict_only:
|
|
403
|
+
is_operation_file = kwargs.pop("is_operation_file", False)
|
|
404
|
+
skip_quote = kwargs.get("skip_quote", False)
|
|
405
|
+
serialize_namespace_type = kwargs.get("serialize_namespace_type")
|
|
406
|
+
# Within types.py, use bare name (no module prefix)
|
|
407
|
+
if serialize_namespace_type == NamespaceType.TYPES_FILE:
|
|
408
|
+
retval = self.name
|
|
409
|
+
else:
|
|
410
|
+
serialize_namespace = kwargs.get("serialize_namespace", self.code_model.namespace)
|
|
411
|
+
types_alias = self.code_model.get_unique_types_alias(serialize_namespace, self.client_namespace)
|
|
412
|
+
retval = f"{types_alias}.{self.name}"
|
|
413
|
+
return retval if is_operation_file or skip_quote else f'"{retval}"'
|
|
414
|
+
return super().type_annotation(**kwargs)
|
|
415
|
+
|
|
416
|
+
def docstring_type(self, **kwargs: Any) -> str:
|
|
417
|
+
if self.is_typed_dict_only:
|
|
418
|
+
client_namespace = self.client_namespace
|
|
419
|
+
if self.code_model.options.get("generation-subdir"):
|
|
420
|
+
client_namespace += f".{self.code_model.options['generation-subdir']}"
|
|
421
|
+
return f"~{client_namespace}.types.{self.name}"
|
|
422
|
+
return super().docstring_type(**kwargs)
|
|
423
|
+
|
|
355
424
|
def serialization_type(self, **kwargs: Any) -> str:
|
|
356
425
|
return (
|
|
357
426
|
self.type_annotation(skip_quote=True, **kwargs)
|
|
@@ -364,7 +433,79 @@ class DPGModelType(GeneratedModelType):
|
|
|
364
433
|
return "isinstance({}, " + f"_models.{self.name})"
|
|
365
434
|
|
|
366
435
|
def imports(self, **kwargs: Any) -> FileImport:
|
|
436
|
+
if self.is_typed_dict_only:
|
|
437
|
+
file_import = FileImport(self.code_model)
|
|
438
|
+
serialize_namespace_type = kwargs.get("serialize_namespace_type")
|
|
439
|
+
serialize_namespace = kwargs.get("serialize_namespace", self.code_model.namespace)
|
|
440
|
+
relative_path = self.code_model.get_relative_import_path(serialize_namespace, self.client_namespace)
|
|
441
|
+
alias = self.code_model.get_unique_types_alias(serialize_namespace, self.client_namespace)
|
|
442
|
+
same_namespace = relative_path == "."
|
|
443
|
+
if serialize_namespace_type in [NamespaceType.OPERATION, NamespaceType.CLIENT]:
|
|
444
|
+
file_import.add_submodule_import(
|
|
445
|
+
relative_path,
|
|
446
|
+
"types",
|
|
447
|
+
ImportType.LOCAL,
|
|
448
|
+
alias=alias,
|
|
449
|
+
)
|
|
450
|
+
elif serialize_namespace_type == NamespaceType.TYPES_FILE and same_namespace:
|
|
451
|
+
pass # model is defined in this types.py — no import needed
|
|
452
|
+
elif serialize_namespace_type in [NamespaceType.TYPES_FILE, NamespaceType.UNIONS_FILE] or (
|
|
453
|
+
serialize_namespace_type == NamespaceType.MODEL and kwargs.get("called_by_property", False)
|
|
454
|
+
):
|
|
455
|
+
file_import.add_submodule_import(
|
|
456
|
+
relative_path,
|
|
457
|
+
"types",
|
|
458
|
+
ImportType.LOCAL,
|
|
459
|
+
alias=alias,
|
|
460
|
+
typing_section=TypingSection.TYPING,
|
|
461
|
+
)
|
|
462
|
+
return file_import
|
|
367
463
|
file_import = super().imports(**kwargs)
|
|
368
464
|
if self.flattened_property:
|
|
369
465
|
file_import.add_submodule_import("typing", "Any", ImportType.STDLIB)
|
|
370
466
|
return file_import
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
class TypedDictModelType(DPGModelType):
|
|
470
|
+
base = "typeddict"
|
|
471
|
+
|
|
472
|
+
@property
|
|
473
|
+
def is_used_in_operations_via_types(self) -> bool:
|
|
474
|
+
return True
|
|
475
|
+
|
|
476
|
+
def type_annotation(self, **kwargs: Any) -> str:
|
|
477
|
+
is_operation_file = kwargs.pop("is_operation_file", False)
|
|
478
|
+
skip_quote = kwargs.get("skip_quote", False)
|
|
479
|
+
serialize_namespace_type = kwargs.get("serialize_namespace_type")
|
|
480
|
+
if serialize_namespace_type == NamespaceType.TYPES_FILE:
|
|
481
|
+
retval = self.name
|
|
482
|
+
else:
|
|
483
|
+
serialize_namespace = kwargs.get("serialize_namespace", self.code_model.namespace)
|
|
484
|
+
types_alias = self.code_model.get_unique_types_alias(serialize_namespace, self.client_namespace)
|
|
485
|
+
retval = f"{types_alias}.{self.name}"
|
|
486
|
+
return retval if is_operation_file or skip_quote else f'"{retval}"'
|
|
487
|
+
|
|
488
|
+
def docstring_type(self, **kwargs: Any) -> str:
|
|
489
|
+
client_namespace = self.client_namespace
|
|
490
|
+
if self.code_model.options.get("generation-subdir"):
|
|
491
|
+
client_namespace += f".{self.code_model.options['generation-subdir']}"
|
|
492
|
+
return f"~{client_namespace}.types.{self.name}"
|
|
493
|
+
|
|
494
|
+
@property
|
|
495
|
+
def instance_check_template(self) -> str:
|
|
496
|
+
return "isinstance({}, MutableMapping)"
|
|
497
|
+
|
|
498
|
+
def imports(self, **kwargs: Any) -> FileImport:
|
|
499
|
+
file_import = FileImport(self.code_model)
|
|
500
|
+
serialize_namespace_type = kwargs.get("serialize_namespace_type")
|
|
501
|
+
serialize_namespace = kwargs.get("serialize_namespace", self.code_model.namespace)
|
|
502
|
+
relative_path = self.code_model.get_relative_import_path(serialize_namespace, self.client_namespace)
|
|
503
|
+
alias = self.code_model.get_unique_types_alias(serialize_namespace, self.client_namespace)
|
|
504
|
+
if serialize_namespace_type in [NamespaceType.OPERATION, NamespaceType.CLIENT]:
|
|
505
|
+
file_import.add_submodule_import(
|
|
506
|
+
relative_path,
|
|
507
|
+
"types",
|
|
508
|
+
ImportType.LOCAL,
|
|
509
|
+
alias=alias,
|
|
510
|
+
)
|
|
511
|
+
return file_import
|
|
@@ -458,6 +458,7 @@ class OperationBase( # pylint: disable=too-many-public-methods,too-many-instanc
|
|
|
458
458
|
r.type
|
|
459
459
|
and not isinstance(r.type, BinaryIteratorType)
|
|
460
460
|
and not xml_serializable(str(r.default_content_type))
|
|
461
|
+
and not (isinstance(r.type, ModelType) and r.type.is_typed_dict_only)
|
|
461
462
|
for r in self.responses
|
|
462
463
|
):
|
|
463
464
|
file_import.add_submodule_import(relative_path, "_deserialize", ImportType.LOCAL)
|
|
@@ -186,7 +186,11 @@ class PagingOperationBase(OperationBase[PagingResponseType]):
|
|
|
186
186
|
serialize_namespace, module_name="_utils.model_base"
|
|
187
187
|
)
|
|
188
188
|
file_import.merge(self.item_type.imports(**kwargs))
|
|
189
|
-
|
|
189
|
+
need_deserialize_import = self.default_error_deserialization(serialize_namespace) or (
|
|
190
|
+
self.need_deserialize
|
|
191
|
+
and not (isinstance(self.item_type, ModelType) and self.item_type.is_typed_dict_only)
|
|
192
|
+
)
|
|
193
|
+
if need_deserialize_import:
|
|
190
194
|
file_import.add_submodule_import(relative_path, "_deserialize", ImportType.LOCAL)
|
|
191
195
|
if self.is_xml_paging:
|
|
192
196
|
file_import.add_submodule_import("xml.etree", "ElementTree", ImportType.STDLIB, alias="ET")
|
|
@@ -180,7 +180,7 @@ class _ParameterBase(BaseModel, abc.ABC): # pylint: disable=too-many-instance-a
|
|
|
180
180
|
if isinstance(self.type, CombinedType) and self.type.name:
|
|
181
181
|
file_import.add_submodule_import(
|
|
182
182
|
self.code_model.get_relative_import_path(serialize_namespace),
|
|
183
|
-
"
|
|
183
|
+
"_unions",
|
|
184
184
|
ImportType.LOCAL,
|
|
185
185
|
TypingSection.TYPING,
|
|
186
186
|
)
|
|
@@ -338,7 +338,7 @@ class Parameter(_ParameterBase):
|
|
|
338
338
|
) -> ParameterMethodLocation:
|
|
339
339
|
if not self.in_method_signature:
|
|
340
340
|
raise ValueError(f"Parameter '{self.client_name}' is not in the method.")
|
|
341
|
-
if self.code_model.options["models-mode"]
|
|
341
|
+
if self.code_model.options["models-mode"] in ("dpg", "typeddict") and self.in_flattened_body:
|
|
342
342
|
return ParameterMethodLocation.KEYWORD_ONLY
|
|
343
343
|
if self.grouper:
|
|
344
344
|
return ParameterMethodLocation.POSITIONAL
|
|
@@ -9,6 +9,7 @@ from typing import Any, Optional, Union, TYPE_CHECKING
|
|
|
9
9
|
|
|
10
10
|
from .base import BaseType
|
|
11
11
|
from .imports import FileImport, ImportType, TypingSection
|
|
12
|
+
from .utils import NamespaceType
|
|
12
13
|
|
|
13
14
|
if TYPE_CHECKING:
|
|
14
15
|
from .code_model import CodeModel
|
|
@@ -271,6 +272,8 @@ class DecimalType(NumberType):
|
|
|
271
272
|
return "~" + self.type_annotation()
|
|
272
273
|
|
|
273
274
|
def type_annotation(self, **kwargs: Any) -> str:
|
|
275
|
+
if kwargs.get("serialize_namespace_type") == NamespaceType.TYPES_FILE:
|
|
276
|
+
return "float"
|
|
274
277
|
return "decimal.Decimal"
|
|
275
278
|
|
|
276
279
|
def docstring_text(self, **kwargs: Any) -> str:
|
|
@@ -281,7 +284,8 @@ class DecimalType(NumberType):
|
|
|
281
284
|
|
|
282
285
|
def imports(self, **kwargs: Any) -> FileImport:
|
|
283
286
|
file_import = FileImport(self.code_model)
|
|
284
|
-
|
|
287
|
+
if kwargs.get("serialize_namespace_type") != NamespaceType.TYPES_FILE:
|
|
288
|
+
file_import.add_import("decimal", ImportType.STDLIB)
|
|
285
289
|
return file_import
|
|
286
290
|
|
|
287
291
|
@property
|
|
@@ -357,6 +361,8 @@ class DatetimeType(PrimitiveType):
|
|
|
357
361
|
return "~" + self.type_annotation()
|
|
358
362
|
|
|
359
363
|
def type_annotation(self, **kwargs: Any) -> str:
|
|
364
|
+
if kwargs.get("serialize_namespace_type") == NamespaceType.TYPES_FILE:
|
|
365
|
+
return "str"
|
|
360
366
|
return "datetime.datetime"
|
|
361
367
|
|
|
362
368
|
def docstring_text(self, **kwargs: Any) -> str:
|
|
@@ -370,7 +376,8 @@ class DatetimeType(PrimitiveType):
|
|
|
370
376
|
|
|
371
377
|
def imports(self, **kwargs: Any) -> FileImport:
|
|
372
378
|
file_import = FileImport(self.code_model)
|
|
373
|
-
|
|
379
|
+
if kwargs.get("serialize_namespace_type") != NamespaceType.TYPES_FILE:
|
|
380
|
+
file_import.add_import("datetime", ImportType.STDLIB)
|
|
374
381
|
return file_import
|
|
375
382
|
|
|
376
383
|
@property
|
|
@@ -399,6 +406,8 @@ class TimeType(PrimitiveType):
|
|
|
399
406
|
return "~" + self.type_annotation()
|
|
400
407
|
|
|
401
408
|
def type_annotation(self, **kwargs: Any) -> str:
|
|
409
|
+
if kwargs.get("serialize_namespace_type") == NamespaceType.TYPES_FILE:
|
|
410
|
+
return "str"
|
|
402
411
|
return "datetime.time"
|
|
403
412
|
|
|
404
413
|
def docstring_text(self, **kwargs: Any) -> str:
|
|
@@ -412,7 +421,8 @@ class TimeType(PrimitiveType):
|
|
|
412
421
|
|
|
413
422
|
def imports(self, **kwargs: Any) -> FileImport:
|
|
414
423
|
file_import = FileImport(self.code_model)
|
|
415
|
-
|
|
424
|
+
if kwargs.get("serialize_namespace_type") != NamespaceType.TYPES_FILE:
|
|
425
|
+
file_import.add_import("datetime", ImportType.STDLIB)
|
|
416
426
|
return file_import
|
|
417
427
|
|
|
418
428
|
@property
|
|
@@ -445,6 +455,8 @@ class UnixTimeType(PrimitiveType):
|
|
|
445
455
|
return "~" + self.type_annotation()
|
|
446
456
|
|
|
447
457
|
def type_annotation(self, **kwargs: Any) -> str:
|
|
458
|
+
if kwargs.get("serialize_namespace_type") == NamespaceType.TYPES_FILE:
|
|
459
|
+
return "int"
|
|
448
460
|
return "datetime.datetime"
|
|
449
461
|
|
|
450
462
|
def docstring_text(self, **kwargs: Any) -> str:
|
|
@@ -458,7 +470,8 @@ class UnixTimeType(PrimitiveType):
|
|
|
458
470
|
|
|
459
471
|
def imports(self, **kwargs: Any) -> FileImport:
|
|
460
472
|
file_import = FileImport(self.code_model)
|
|
461
|
-
|
|
473
|
+
if kwargs.get("serialize_namespace_type") != NamespaceType.TYPES_FILE:
|
|
474
|
+
file_import.add_import("datetime", ImportType.STDLIB)
|
|
462
475
|
return file_import
|
|
463
476
|
|
|
464
477
|
@property
|
|
@@ -487,6 +500,8 @@ class DateType(PrimitiveType):
|
|
|
487
500
|
return "~" + self.type_annotation()
|
|
488
501
|
|
|
489
502
|
def type_annotation(self, **kwargs: Any) -> str:
|
|
503
|
+
if kwargs.get("serialize_namespace_type") == NamespaceType.TYPES_FILE:
|
|
504
|
+
return "str"
|
|
490
505
|
return "datetime.date"
|
|
491
506
|
|
|
492
507
|
def docstring_text(self, **kwargs: Any) -> str:
|
|
@@ -500,7 +515,8 @@ class DateType(PrimitiveType):
|
|
|
500
515
|
|
|
501
516
|
def imports(self, **kwargs: Any) -> FileImport:
|
|
502
517
|
file_import = FileImport(self.code_model)
|
|
503
|
-
|
|
518
|
+
if kwargs.get("serialize_namespace_type") != NamespaceType.TYPES_FILE:
|
|
519
|
+
file_import.add_import("datetime", ImportType.STDLIB)
|
|
504
520
|
return file_import
|
|
505
521
|
|
|
506
522
|
@property
|
|
@@ -543,6 +559,8 @@ class DurationType(PrimitiveType):
|
|
|
543
559
|
return "~" + self.type_annotation()
|
|
544
560
|
|
|
545
561
|
def type_annotation(self, **kwargs: Any) -> str:
|
|
562
|
+
if kwargs.get("serialize_namespace_type") == NamespaceType.TYPES_FILE:
|
|
563
|
+
return "str"
|
|
546
564
|
return "datetime.timedelta"
|
|
547
565
|
|
|
548
566
|
def docstring_text(self, **kwargs: Any) -> str:
|
|
@@ -556,7 +574,8 @@ class DurationType(PrimitiveType):
|
|
|
556
574
|
|
|
557
575
|
def imports(self, **kwargs: Any) -> FileImport:
|
|
558
576
|
file_import = FileImport(self.code_model)
|
|
559
|
-
|
|
577
|
+
if kwargs.get("serialize_namespace_type") != NamespaceType.TYPES_FILE:
|
|
578
|
+
file_import.add_import("datetime", ImportType.STDLIB)
|
|
560
579
|
return file_import
|
|
561
580
|
|
|
562
581
|
@property
|
|
@@ -590,6 +609,11 @@ class ByteArraySchema(PrimitiveType):
|
|
|
590
609
|
def docstring_type(self, **kwargs: Any) -> str:
|
|
591
610
|
return "bytes"
|
|
592
611
|
|
|
612
|
+
def type_annotation(self, **kwargs: Any) -> str:
|
|
613
|
+
if kwargs.get("serialize_namespace_type") == NamespaceType.TYPES_FILE:
|
|
614
|
+
return "str"
|
|
615
|
+
return "bytes"
|
|
616
|
+
|
|
593
617
|
def get_declaration(self, value: str) -> str:
|
|
594
618
|
return f'bytes("{value}", encoding="utf-8")'
|
|
595
619
|
|
|
@@ -10,7 +10,7 @@ from .constant_type import ConstantType
|
|
|
10
10
|
from .enum_type import EnumType
|
|
11
11
|
from .base import BaseType
|
|
12
12
|
from .imports import FileImport, ImportType
|
|
13
|
-
from .utils import add_to_description, add_to_pylint_disable
|
|
13
|
+
from .utils import add_to_description, add_to_pylint_disable, NamespaceType
|
|
14
14
|
|
|
15
15
|
if TYPE_CHECKING:
|
|
16
16
|
from .code_model import CodeModel
|
|
@@ -29,6 +29,7 @@ class Property(BaseModel): # pylint: disable=too-many-instance-attributes
|
|
|
29
29
|
self.client_name: str = self.yaml_data["clientName"]
|
|
30
30
|
self.type = type
|
|
31
31
|
self.optional: bool = self.yaml_data["optional"]
|
|
32
|
+
self.nullable: bool = self.yaml_data.get("nullable", False)
|
|
32
33
|
self.readonly: bool = self.yaml_data.get("readonly", False)
|
|
33
34
|
self.visibility: list[str] = self.yaml_data.get("visibility", [])
|
|
34
35
|
self.is_polymorphic: bool = self.yaml_data.get("isPolymorphic", False)
|
|
@@ -110,6 +111,12 @@ class Property(BaseModel): # pylint: disable=too-many-instance-attributes
|
|
|
110
111
|
if self.is_base_discriminator:
|
|
111
112
|
return "str"
|
|
112
113
|
types_type_annotation = self.type.type_annotation(is_operation_file=is_operation_file, **kwargs)
|
|
114
|
+
serialize_namespace_type = kwargs.get("serialize_namespace_type")
|
|
115
|
+
# In TypedDict types.py, Optional means nullable (not "not required" — that's handled by Required/total=False)
|
|
116
|
+
if serialize_namespace_type == NamespaceType.TYPES_FILE:
|
|
117
|
+
if self.nullable:
|
|
118
|
+
return f"Optional[{types_type_annotation}]"
|
|
119
|
+
return types_type_annotation
|
|
113
120
|
if (self.optional and self.client_default_value is None) or self.readonly:
|
|
114
121
|
return f"Optional[{types_type_annotation}]"
|
|
115
122
|
return types_type_annotation
|
|
@@ -152,15 +159,21 @@ class Property(BaseModel): # pylint: disable=too-many-instance-attributes
|
|
|
152
159
|
if self.is_discriminator and isinstance(self.type, EnumType):
|
|
153
160
|
return file_import
|
|
154
161
|
file_import.merge(self.type.imports(**kwargs))
|
|
155
|
-
|
|
162
|
+
serialize_namespace_type = kwargs.get("serialize_namespace_type")
|
|
163
|
+
if serialize_namespace_type == NamespaceType.TYPES_FILE:
|
|
164
|
+
# In TypedDict types.py, Optional means nullable
|
|
165
|
+
if self.nullable:
|
|
166
|
+
file_import.add_submodule_import("typing", "Optional", ImportType.STDLIB)
|
|
167
|
+
elif (self.optional and self.client_default_value is None) or self.readonly:
|
|
156
168
|
file_import.add_submodule_import("typing", "Optional", ImportType.STDLIB)
|
|
157
169
|
if self.code_model.options["models-mode"] == "dpg":
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
170
|
+
if serialize_namespace_type != NamespaceType.TYPES_FILE:
|
|
171
|
+
serialize_namespace = kwargs.get("serialize_namespace", self.code_model.namespace)
|
|
172
|
+
file_import.add_submodule_import(
|
|
173
|
+
self.code_model.get_relative_import_path(serialize_namespace, module_name="_utils.model_base"),
|
|
174
|
+
"rest_discriminator" if self.is_discriminator else "rest_field",
|
|
175
|
+
ImportType.LOCAL,
|
|
176
|
+
)
|
|
164
177
|
return file_import
|
|
165
178
|
|
|
166
179
|
@classmethod
|
|
@@ -68,7 +68,12 @@ class Response(BaseModel):
|
|
|
68
68
|
|
|
69
69
|
def get_polymorphic_subtypes(self, polymorphic_subtypes: list["ModelType"]) -> None:
|
|
70
70
|
if self.type:
|
|
71
|
-
self.type
|
|
71
|
+
if isinstance(self.type, CombinedType):
|
|
72
|
+
target = self.type.target_model_subtype((ModelType,))
|
|
73
|
+
if target:
|
|
74
|
+
target.get_polymorphic_subtypes(polymorphic_subtypes)
|
|
75
|
+
else:
|
|
76
|
+
self.type.get_polymorphic_subtypes(polymorphic_subtypes)
|
|
72
77
|
|
|
73
78
|
def get_json_template_representation(self) -> Any:
|
|
74
79
|
if not self.type:
|
|
@@ -95,6 +100,7 @@ class Response(BaseModel):
|
|
|
95
100
|
def type_annotation(self, **kwargs: Any) -> str:
|
|
96
101
|
if self.type:
|
|
97
102
|
kwargs["is_operation_file"] = True
|
|
103
|
+
kwargs["is_response"] = True
|
|
98
104
|
type_annotation = self.type.type_annotation(**kwargs)
|
|
99
105
|
if self.nullable:
|
|
100
106
|
return f"Optional[{type_annotation}]"
|
|
@@ -102,11 +108,13 @@ class Response(BaseModel):
|
|
|
102
108
|
return "None"
|
|
103
109
|
|
|
104
110
|
def docstring_text(self, **kwargs: Any) -> str:
|
|
111
|
+
kwargs["is_response"] = True
|
|
105
112
|
if self.nullable and self.type:
|
|
106
113
|
return f"{self.type.docstring_text(**kwargs)} or None"
|
|
107
114
|
return self.type.docstring_text(**kwargs) if self.type else "None"
|
|
108
115
|
|
|
109
116
|
def docstring_type(self, **kwargs: Any) -> str:
|
|
117
|
+
kwargs["is_response"] = True
|
|
110
118
|
if self.nullable and self.type:
|
|
111
119
|
return f"{self.type.docstring_type(**kwargs)} or None"
|
|
112
120
|
return self.type.docstring_type(**kwargs) if self.type else "None"
|
|
@@ -121,7 +129,7 @@ class Response(BaseModel):
|
|
|
121
129
|
serialize_namespace = kwargs.get("serialize_namespace", self.code_model.namespace)
|
|
122
130
|
file_import.add_submodule_import(
|
|
123
131
|
self.code_model.get_relative_import_path(serialize_namespace),
|
|
124
|
-
"
|
|
132
|
+
"_unions",
|
|
125
133
|
ImportType.LOCAL,
|
|
126
134
|
TypingSection.TYPING,
|
|
127
135
|
)
|
|
@@ -165,7 +173,12 @@ class PagingResponse(Response):
|
|
|
165
173
|
)
|
|
166
174
|
|
|
167
175
|
def get_polymorphic_subtypes(self, polymorphic_subtypes: list["ModelType"]) -> None:
|
|
168
|
-
|
|
176
|
+
if isinstance(self.item_type, CombinedType):
|
|
177
|
+
target = self.item_type.target_model_subtype((ModelType,))
|
|
178
|
+
if target:
|
|
179
|
+
target.get_polymorphic_subtypes(polymorphic_subtypes)
|
|
180
|
+
else:
|
|
181
|
+
self.item_type.get_polymorphic_subtypes(polymorphic_subtypes)
|
|
169
182
|
|
|
170
183
|
def get_json_template_representation(self) -> Any:
|
|
171
184
|
return self.item_type.get_json_template_representation()
|
|
@@ -7,23 +7,24 @@ from typing import TypeVar
|
|
|
7
7
|
|
|
8
8
|
from enum import Enum
|
|
9
9
|
|
|
10
|
+
from ...utils import description_ends_with_code_block
|
|
11
|
+
|
|
10
12
|
T = TypeVar("T")
|
|
11
13
|
OrderedSet = dict[T, None]
|
|
12
14
|
|
|
13
15
|
|
|
14
|
-
CODE_BLOCK_MARKER = ".. code-block::"
|
|
15
|
-
|
|
16
|
-
|
|
17
16
|
def add_to_description(description: str, entry: str) -> str:
|
|
18
17
|
if not description:
|
|
19
18
|
return entry
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
19
|
+
if not entry:
|
|
20
|
+
return description
|
|
21
|
+
# When the description ends with a code block, prepend the entry (e.g. "Required.") so
|
|
22
|
+
# it appears before the prose instead of after the block. Appending inline (e.g.
|
|
23
|
+
# "]. Required.") would land it inside the rendered literal block and break Sphinx, and a
|
|
24
|
+
# trailing paragraph reads awkwardly. The code block stays at the very end where it renders
|
|
25
|
+
# cleanly. (Code-block descriptions only ever carry a single annotation in practice.)
|
|
26
|
+
if description_ends_with_code_block(description):
|
|
27
|
+
return f"{entry} {description.lstrip()}"
|
|
27
28
|
return f"{description} {entry}"
|
|
28
29
|
|
|
29
30
|
|
|
@@ -40,6 +41,7 @@ class NamespaceType(str, Enum):
|
|
|
40
41
|
OPERATION = "operation"
|
|
41
42
|
CLIENT = "client"
|
|
42
43
|
TYPES_FILE = "types_file"
|
|
44
|
+
UNIONS_FILE = "unions_file"
|
|
43
45
|
|
|
44
46
|
|
|
45
47
|
LOCALS_LENGTH_LIMIT = 25
|
|
@@ -33,6 +33,7 @@ from .patch_serializer import PatchSerializer
|
|
|
33
33
|
from .sample_serializer import SampleSerializer
|
|
34
34
|
from .test_serializer import TestSerializer, TestGeneralSerializer
|
|
35
35
|
from .types_serializer import TypesSerializer
|
|
36
|
+
from .unions_serializer import UnionsSerializer
|
|
36
37
|
from ...utils import to_snake_case, VALID_PACKAGE_MODE
|
|
37
38
|
from .utils import extract_sample_name, get_namespace_from_package_name, get_namespace_config, hash_file_import
|
|
38
39
|
|
|
@@ -120,7 +121,7 @@ class JinjaSerializer(ReaderAndWriter):
|
|
|
120
121
|
# If parsing the version fails, we assume the version file is not valid and overwrite.
|
|
121
122
|
return False
|
|
122
123
|
|
|
123
|
-
# pylint: disable=too-many-branches
|
|
124
|
+
# pylint: disable=too-many-branches,too-many-statements
|
|
124
125
|
def serialize(self) -> None:
|
|
125
126
|
# remove existing folders when generate from tsp
|
|
126
127
|
if self.code_model.is_tsp and self.code_model.options.get("clear-output-folder"):
|
|
@@ -200,12 +201,13 @@ class JinjaSerializer(ReaderAndWriter):
|
|
|
200
201
|
general_serializer.serialize_pkgutil_init_file(),
|
|
201
202
|
)
|
|
202
203
|
|
|
203
|
-
# _utils/py.typed/
|
|
204
|
+
# _utils/py.typed/_unions.py/types.py/_validation.py
|
|
204
205
|
# is always put in top level namespace
|
|
205
206
|
if self.code_model.is_top_namespace(client_namespace):
|
|
206
207
|
self._serialize_and_write_top_level_folder(env=env, namespace=client_namespace)
|
|
207
208
|
|
|
208
209
|
# add models folder if there are models in this namespace
|
|
210
|
+
is_typeddict_mode = self.code_model.options["models-mode"] == "typeddict"
|
|
209
211
|
if (
|
|
210
212
|
self.code_model.has_non_json_models(client_namespace_type.models) or client_namespace_type.enums
|
|
211
213
|
) and self.code_model.options["models-mode"]:
|
|
@@ -213,7 +215,27 @@ class JinjaSerializer(ReaderAndWriter):
|
|
|
213
215
|
env=env,
|
|
214
216
|
namespace=client_namespace,
|
|
215
217
|
models=client_namespace_type.models,
|
|
216
|
-
enums=client_namespace_type.enums,
|
|
218
|
+
enums=[] if is_typeddict_mode else client_namespace_type.enums,
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
# write types.py per namespace (alongside models/)
|
|
222
|
+
# Only generate types.py if at least one model/enum would be imported via types
|
|
223
|
+
# in operations (the model itself volunteers this via is_used_in_operations_via_types)
|
|
224
|
+
has_types_models = any(
|
|
225
|
+
m.is_used_in_operations_via_types for m in client_namespace_type.models if m.base != "json"
|
|
226
|
+
)
|
|
227
|
+
has_types_enums = any(e.is_typeddict_mode for e in client_namespace_type.enums)
|
|
228
|
+
if has_types_models or has_types_enums:
|
|
229
|
+
generation_dir = self.code_model.get_generation_dir(client_namespace)
|
|
230
|
+
self.write_file(
|
|
231
|
+
generation_dir / Path("types.py"),
|
|
232
|
+
TypesSerializer(
|
|
233
|
+
code_model=self.code_model,
|
|
234
|
+
env=env,
|
|
235
|
+
client_namespace=client_namespace,
|
|
236
|
+
models=client_namespace_type.models,
|
|
237
|
+
enums=client_namespace_type.enums if is_typeddict_mode else None,
|
|
238
|
+
).serialize(),
|
|
217
239
|
)
|
|
218
240
|
|
|
219
241
|
if not self.code_model.options["models-mode"]:
|
|
@@ -298,11 +320,19 @@ class JinjaSerializer(ReaderAndWriter):
|
|
|
298
320
|
) -> None:
|
|
299
321
|
# Write the models folder
|
|
300
322
|
models_path = self.code_model.get_generation_dir(namespace) / "models"
|
|
301
|
-
|
|
302
|
-
if
|
|
323
|
+
models_mode = self.code_model.options["models-mode"]
|
|
324
|
+
if models_mode in ("dpg", "typeddict"):
|
|
325
|
+
serializer = DpgModelSerializer
|
|
326
|
+
else:
|
|
327
|
+
serializer = MsrestModelSerializer
|
|
328
|
+
# Filter out typed-dict-only models and typeddict copies — they only appear in types.py, not as model classes
|
|
329
|
+
class_models = [m for m in models if not m.is_typed_dict_only and m.base != "typeddict"]
|
|
330
|
+
if self.code_model.has_non_json_models(class_models):
|
|
303
331
|
self.write_file(
|
|
304
332
|
models_path / Path(f"{self.code_model.models_filename}.py"),
|
|
305
|
-
serializer(
|
|
333
|
+
serializer(
|
|
334
|
+
code_model=self.code_model, env=env, client_namespace=namespace, models=class_models
|
|
335
|
+
).serialize(),
|
|
306
336
|
)
|
|
307
337
|
if enums:
|
|
308
338
|
self.write_file(
|
|
@@ -313,7 +343,7 @@ class JinjaSerializer(ReaderAndWriter):
|
|
|
313
343
|
)
|
|
314
344
|
self.write_file(
|
|
315
345
|
models_path / Path("__init__.py"),
|
|
316
|
-
ModelInitSerializer(code_model=self.code_model, env=env, models=
|
|
346
|
+
ModelInitSerializer(code_model=self.code_model, env=env, models=class_models, enums=enums).serialize(),
|
|
317
347
|
)
|
|
318
348
|
|
|
319
349
|
self._keep_patch_file(models_path / Path("_patch.py"), env)
|
|
@@ -442,7 +472,9 @@ class JinjaSerializer(ReaderAndWriter):
|
|
|
442
472
|
# when there is client.py, there must be __init__.py
|
|
443
473
|
self.write_file(
|
|
444
474
|
generation_path / Path(f"{async_path}__init__.py"),
|
|
445
|
-
general_serializer.serialize_init_file(
|
|
475
|
+
general_serializer.serialize_init_file(
|
|
476
|
+
[c for c in clients if c.has_operations],
|
|
477
|
+
),
|
|
446
478
|
)
|
|
447
479
|
|
|
448
480
|
# if there was a patch file before, we keep it
|
|
@@ -519,11 +551,14 @@ class JinjaSerializer(ReaderAndWriter):
|
|
|
519
551
|
general_serializer.serialize_validation_file(),
|
|
520
552
|
)
|
|
521
553
|
|
|
522
|
-
# write
|
|
554
|
+
# write _unions.py
|
|
523
555
|
if self.code_model.named_unions:
|
|
524
556
|
self.write_file(
|
|
525
|
-
generation_dir / Path("
|
|
526
|
-
|
|
557
|
+
generation_dir / Path("_unions.py"),
|
|
558
|
+
UnionsSerializer(
|
|
559
|
+
code_model=self.code_model,
|
|
560
|
+
env=env,
|
|
561
|
+
).serialize(),
|
|
527
562
|
)
|
|
528
563
|
|
|
529
564
|
# pylint: disable=line-too-long
|