@typespec/http-client-python 0.29.0 → 0.29.2
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/types.js +1 -1
- package/dist/emitter/types.js.map +1 -1
- package/emitter/src/types.ts +1 -1
- package/emitter/temp/tsconfig.tsbuildinfo +1 -1
- package/eng/scripts/ci/regenerate-common.ts +21 -13
- 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/codegen/models/response.py +15 -1
- package/generator/build/lib/pygen/codegen/serializers/model_serializer.py +113 -1
- package/generator/build/lib/pygen/codegen/templates/model_base.py.jinja2 +329 -45
- package/generator/build/lib/pygen/codegen/templates/serialization.py.jinja2 +22 -1
- package/generator/dist/pygen-0.1.0-py3-none-any.whl +0 -0
- package/generator/pygen/codegen/models/response.py +15 -1
- package/generator/pygen/codegen/serializers/model_serializer.py +113 -1
- package/generator/pygen/codegen/templates/model_base.py.jinja2 +329 -45
- package/generator/pygen/codegen/templates/serialization.py.jinja2 +22 -1
- package/package.json +32 -32
|
@@ -677,7 +677,7 @@ export async function preprocess(flavor: string, generatedFolder: string): Promi
|
|
|
677
677
|
|
|
678
678
|
/**
|
|
679
679
|
* Resets the `tests/generated/{azure,unbranded}` baseline by sparse-checking-out
|
|
680
|
-
* `eng/tools/emitter/
|
|
680
|
+
* `eng/tools/azure-sdk-tools/emitter/generated` from the Azure/azure-sdk-for-python repo, then
|
|
681
681
|
* deleting a couple of fully-generated package folders so regeneration has to
|
|
682
682
|
* recreate them from scratch (smoke test of full-emit path).
|
|
683
683
|
*
|
|
@@ -686,8 +686,8 @@ export async function preprocess(flavor: string, generatedFolder: string): Promi
|
|
|
686
686
|
*/
|
|
687
687
|
export async function prepareBaselineOfGeneratedCode(generatedFolder: string): Promise<void> {
|
|
688
688
|
const repoUrl = "https://github.com/Azure/azure-sdk-for-python.git";
|
|
689
|
-
const branch = "
|
|
690
|
-
const sourceSubdir = "eng/tools/emitter/
|
|
689
|
+
const branch = "typespec-python-generated-tests";
|
|
690
|
+
const sourceSubdir = "eng/tools/azure-sdk-tools/emitter/generated";
|
|
691
691
|
const testsGeneratedDir = resolve(generatedFolder, "../tests/generated");
|
|
692
692
|
|
|
693
693
|
console.log(pc.cyan(`\n${"=".repeat(60)}`));
|
|
@@ -708,6 +708,7 @@ export async function prepareBaselineOfGeneratedCode(generatedFolder: string): P
|
|
|
708
708
|
execSync(cmd, { cwd: tempDir, stdio: ["ignore", "ignore", "inherit"] });
|
|
709
709
|
|
|
710
710
|
run(`git init`);
|
|
711
|
+
run(`git config core.longpaths true`);
|
|
711
712
|
run(`git remote add origin ${repoUrl}`);
|
|
712
713
|
run(`git config core.sparseCheckout true`);
|
|
713
714
|
run(`git sparse-checkout init --cone`);
|
|
@@ -732,16 +733,23 @@ export async function prepareBaselineOfGeneratedCode(generatedFolder: string): P
|
|
|
732
733
|
rmSync(tempDir, { recursive: true, force: true });
|
|
733
734
|
}
|
|
734
735
|
|
|
735
|
-
//
|
|
736
|
-
//
|
|
737
|
-
const
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
736
|
+
// Smoke test the full-emit path: delete a couple of fully-generated package
|
|
737
|
+
// folders and every README.md so regeneration has to recreate them.
|
|
738
|
+
const deleteIfExists = (path: string) => {
|
|
739
|
+
if (!existsSync(path)) return;
|
|
740
|
+
console.log(pc.dim(`Deleting ${path}`));
|
|
741
|
+
rmSync(path, { recursive: true, force: true });
|
|
742
|
+
};
|
|
743
|
+
|
|
744
|
+
deleteIfExists(join(testsGeneratedDir, "azure", "authentication-http-custom"));
|
|
745
|
+
deleteIfExists(join(testsGeneratedDir, "unbranded", "encode-array"));
|
|
746
|
+
|
|
747
|
+
if (existsSync(testsGeneratedDir)) {
|
|
748
|
+
const entries = await readdir(testsGeneratedDir, { recursive: true, withFileTypes: true });
|
|
749
|
+
for (const entry of entries) {
|
|
750
|
+
if (entry.isFile() && entry.name === "README.md") {
|
|
751
|
+
deleteIfExists(join(entry.parentPath, entry.name));
|
|
752
|
+
}
|
|
745
753
|
}
|
|
746
754
|
}
|
|
747
755
|
}
|
|
Binary file
|
|
Binary file
|
|
@@ -181,7 +181,21 @@ class PagingResponse(Response):
|
|
|
181
181
|
|
|
182
182
|
def type_annotation(self, **kwargs: Any) -> str:
|
|
183
183
|
iterable = "AsyncItemPaged" if kwargs["async_mode"] else "ItemPaged"
|
|
184
|
-
return f"{iterable}[{self.
|
|
184
|
+
return f"{iterable}[{self._item_type_annotation(**kwargs)}]"
|
|
185
|
+
|
|
186
|
+
def _item_type_annotation(self, **kwargs: Any) -> str:
|
|
187
|
+
# When the page item is a ListType, render the outer `List`/`list`
|
|
188
|
+
# wrapper here using the operation-file alias decision so a list page
|
|
189
|
+
# item rendered inside an operation file named `list` uses the `List`
|
|
190
|
+
# alias (avoiding the built-in `list` shadowed by `List = list`).
|
|
191
|
+
# Recurse into the element type without is_operation_file so nested
|
|
192
|
+
# generated model types keep their forward-reference quoting
|
|
193
|
+
# (e.g. ItemPaged[List["_models.Product"]]).
|
|
194
|
+
if isinstance(self.item_type, ListType):
|
|
195
|
+
use_list_import = self.code_model.has_operation_named_list
|
|
196
|
+
list_type = "List" if use_list_import else "list"
|
|
197
|
+
return f"{list_type}[{self.item_type.element_type.type_annotation(**kwargs)}]"
|
|
198
|
+
return self.item_type.type_annotation(**kwargs)
|
|
185
199
|
|
|
186
200
|
def docstring_text(self, **kwargs: Any) -> str:
|
|
187
201
|
base_description = "An iterator like instance of "
|
|
@@ -6,13 +6,73 @@
|
|
|
6
6
|
from typing import Optional
|
|
7
7
|
from abc import ABC, abstractmethod
|
|
8
8
|
|
|
9
|
-
from ..models import ModelType, Property, ConstantType, EnumValue
|
|
9
|
+
from ..models import ModelType, Property, ConstantType, EnumValue, EnumType
|
|
10
10
|
from ..models.imports import FileImport, TypingSection, MsrestImportType, ImportType
|
|
11
|
+
from ..models.primitive_types import (
|
|
12
|
+
BooleanType,
|
|
13
|
+
IntegerType,
|
|
14
|
+
FloatType,
|
|
15
|
+
DecimalType,
|
|
16
|
+
StringType,
|
|
17
|
+
DatetimeType,
|
|
18
|
+
UnixTimeType,
|
|
19
|
+
TimeType,
|
|
20
|
+
DateType,
|
|
21
|
+
DurationType,
|
|
22
|
+
ByteArraySchema,
|
|
23
|
+
)
|
|
11
24
|
from .import_serializer import FileImportSerializer
|
|
12
25
|
from .base_serializer import BaseSerializer
|
|
13
26
|
from ..models.utils import NamespaceType
|
|
14
27
|
|
|
15
28
|
|
|
29
|
+
def _get_xml_deserializer_name(prop: Property) -> Optional[str]: # pylint: disable=too-many-return-statements
|
|
30
|
+
"""Return the _xml_deser_* function name for a scalar XML property, or None."""
|
|
31
|
+
prop_type = prop.type
|
|
32
|
+
# Unwrap ConstantType to get the underlying value type
|
|
33
|
+
if isinstance(prop_type, ConstantType):
|
|
34
|
+
prop_type = prop_type.value_type
|
|
35
|
+
|
|
36
|
+
if isinstance(prop_type, StringType):
|
|
37
|
+
return "_xml_deser_str"
|
|
38
|
+
if isinstance(prop_type, IntegerType):
|
|
39
|
+
return "_xml_deser_int"
|
|
40
|
+
if isinstance(prop_type, FloatType):
|
|
41
|
+
return "_xml_deser_float"
|
|
42
|
+
if isinstance(prop_type, BooleanType):
|
|
43
|
+
return "_xml_deser_bool"
|
|
44
|
+
if isinstance(prop_type, DecimalType):
|
|
45
|
+
return "_xml_deser_decimal"
|
|
46
|
+
if isinstance(prop_type, DatetimeType):
|
|
47
|
+
if prop_type.encode == "rfc7231":
|
|
48
|
+
return "_xml_deser_datetime_rfc7231"
|
|
49
|
+
return "_xml_deser_datetime"
|
|
50
|
+
if isinstance(prop_type, UnixTimeType):
|
|
51
|
+
return "_xml_deser_datetime_unix_timestamp"
|
|
52
|
+
if isinstance(prop_type, TimeType):
|
|
53
|
+
return "_xml_deser_time"
|
|
54
|
+
if isinstance(prop_type, DateType):
|
|
55
|
+
return "_xml_deser_date"
|
|
56
|
+
if isinstance(prop_type, DurationType):
|
|
57
|
+
return "_xml_deser_duration"
|
|
58
|
+
if isinstance(prop_type, ByteArraySchema):
|
|
59
|
+
encode = getattr(prop_type, "encode", None) or getattr(prop, "encode", None)
|
|
60
|
+
if encode == "base64url":
|
|
61
|
+
return "_xml_deser_bytes_base64url"
|
|
62
|
+
return "_xml_deser_bytes"
|
|
63
|
+
if isinstance(prop_type, EnumType):
|
|
64
|
+
return f"enum:{prop_type.name}"
|
|
65
|
+
return None
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _get_xml_deserializer_enum_type(prop: Property) -> Optional[EnumType]:
|
|
69
|
+
"""Return the EnumType for an XML enum property (unwrapping ConstantType), or None."""
|
|
70
|
+
prop_type = prop.type
|
|
71
|
+
if isinstance(prop_type, ConstantType):
|
|
72
|
+
prop_type = prop_type.value_type
|
|
73
|
+
return prop_type if isinstance(prop_type, EnumType) else None
|
|
74
|
+
|
|
75
|
+
|
|
16
76
|
def _documentation_string(prop: Property, description_keyword: str, docstring_type_keyword: str) -> list[str]:
|
|
17
77
|
retval: list[str] = []
|
|
18
78
|
sphinx_prefix = f":{description_keyword} {prop.client_name}:"
|
|
@@ -253,6 +313,9 @@ class DpgModelSerializer(_ModelSerializer):
|
|
|
253
313
|
TypingSection.REGULAR,
|
|
254
314
|
alias="_Model",
|
|
255
315
|
)
|
|
316
|
+
# Collect XML deserializer functions needed by models in this file
|
|
317
|
+
xml_deser_names: set[str] = set()
|
|
318
|
+
xml_deser_enums: dict[str, EnumType] = {}
|
|
256
319
|
for model in self.models:
|
|
257
320
|
if model.base == "json":
|
|
258
321
|
continue
|
|
@@ -271,6 +334,15 @@ class DpgModelSerializer(_ModelSerializer):
|
|
|
271
334
|
called_by_property=True,
|
|
272
335
|
)
|
|
273
336
|
)
|
|
337
|
+
# Track XML deserializer functions needed
|
|
338
|
+
if prop.xml_metadata:
|
|
339
|
+
deser_name = _get_xml_deserializer_name(prop)
|
|
340
|
+
if deser_name:
|
|
341
|
+
xml_deser_names.add(deser_name)
|
|
342
|
+
if deser_name and deser_name.startswith("enum:"):
|
|
343
|
+
enum_type = _get_xml_deserializer_enum_type(prop)
|
|
344
|
+
if enum_type is not None:
|
|
345
|
+
xml_deser_enums[enum_type.name] = enum_type
|
|
274
346
|
for parent in model.parents:
|
|
275
347
|
if parent.client_namespace != model.client_namespace:
|
|
276
348
|
file_import.add_submodule_import(
|
|
@@ -285,6 +357,38 @@ class DpgModelSerializer(_ModelSerializer):
|
|
|
285
357
|
file_import.add_submodule_import("typing", "overload", ImportType.STDLIB)
|
|
286
358
|
file_import.add_submodule_import("typing", "Mapping", ImportType.STDLIB)
|
|
287
359
|
file_import.add_submodule_import("typing", "Any", ImportType.STDLIB)
|
|
360
|
+
# Add imports for XML deserializer functions
|
|
361
|
+
has_enum_deser = False
|
|
362
|
+
for deser_name in sorted(xml_deser_names):
|
|
363
|
+
if deser_name.startswith("enum:"):
|
|
364
|
+
has_enum_deser = True
|
|
365
|
+
continue
|
|
366
|
+
file_import.add_submodule_import(
|
|
367
|
+
self.code_model.get_relative_import_path(self.serialize_namespace, module_name="_utils.model_base"),
|
|
368
|
+
deser_name,
|
|
369
|
+
ImportType.LOCAL,
|
|
370
|
+
)
|
|
371
|
+
if has_enum_deser:
|
|
372
|
+
file_import.add_import("functools", ImportType.STDLIB)
|
|
373
|
+
file_import.add_submodule_import(
|
|
374
|
+
self.code_model.get_relative_import_path(self.serialize_namespace, module_name="_utils.model_base"),
|
|
375
|
+
"_xml_deser_enum_or_str",
|
|
376
|
+
ImportType.LOCAL,
|
|
377
|
+
)
|
|
378
|
+
# Ensure each referenced enum class is imported at runtime (not just
|
|
379
|
+
# under TYPE_CHECKING), so functools.partial can bind the class at
|
|
380
|
+
# class-body evaluation time.
|
|
381
|
+
for enum_name, enum_type in xml_deser_enums.items():
|
|
382
|
+
file_import.add_submodule_import(
|
|
383
|
+
self.code_model.get_relative_import_path(
|
|
384
|
+
self.serialize_namespace,
|
|
385
|
+
self.code_model.get_imported_namespace_for_model(enum_type.client_namespace),
|
|
386
|
+
module_name=self.code_model.enums_filename,
|
|
387
|
+
),
|
|
388
|
+
enum_name,
|
|
389
|
+
ImportType.LOCAL,
|
|
390
|
+
TypingSection.REGULAR,
|
|
391
|
+
)
|
|
288
392
|
# if there is a property named `list` we have to make sure there's no conflict with the built-in `list`
|
|
289
393
|
if self.code_model.has_property_named_list:
|
|
290
394
|
file_import.define_mypy_type("List", "list")
|
|
@@ -337,6 +441,14 @@ class DpgModelSerializer(_ModelSerializer):
|
|
|
337
441
|
|
|
338
442
|
if prop.xml_metadata:
|
|
339
443
|
args.append(f"xml={prop.xml_metadata}")
|
|
444
|
+
# Add fast-path deserializer for scalar XML fields
|
|
445
|
+
deser_name = _get_xml_deserializer_name(prop)
|
|
446
|
+
if deser_name:
|
|
447
|
+
if deser_name.startswith("enum:"):
|
|
448
|
+
enum_name = deser_name[5:]
|
|
449
|
+
args.append(f"deserializer=functools.partial(_xml_deser_enum_or_str, {enum_name})")
|
|
450
|
+
else:
|
|
451
|
+
args.append(f"deserializer={deser_name}")
|
|
340
452
|
|
|
341
453
|
if prop.original_tsp_name:
|
|
342
454
|
args.append(f'original_tsp_name="{prop.original_tsp_name}"')
|