@typespec/http-client-python 0.36.0 → 0.37.1
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.d.ts.map +1 -1
- package/dist/emitter/emitter.js +6 -5
- package/dist/emitter/emitter.js.map +1 -1
- package/dist/emitter/external-process.d.ts +0 -14
- package/dist/emitter/external-process.d.ts.map +1 -1
- package/dist/emitter/external-process.js +1 -17
- package/dist/emitter/external-process.js.map +1 -1
- package/dist/emitter/http.d.ts +44 -1
- package/dist/emitter/http.d.ts.map +1 -1
- package/dist/emitter/http.js +96 -0
- package/dist/emitter/http.js.map +1 -1
- package/dist/emitter/lib.d.ts +5 -5
- package/dist/emitter/lib.js +2 -2
- package/dist/emitter/lib.js.map +1 -1
- package/dist/emitter/yaml-utils.d.ts +19 -0
- package/dist/emitter/yaml-utils.d.ts.map +1 -0
- package/dist/emitter/yaml-utils.js +22 -0
- package/dist/emitter/yaml-utils.js.map +1 -0
- package/emitter/src/emitter.ts +7 -5
- package/emitter/src/external-process.ts +1 -18
- package/emitter/src/http.ts +154 -0
- package/emitter/src/lib.ts +2 -2
- package/emitter/src/yaml-utils.ts +22 -0
- package/emitter/temp/tsconfig.tsbuildinfo +1 -1
- package/emitter/test/emitter.test.ts +32 -0
- package/emitter/test/streaming.test.ts +155 -0
- package/emitter/test/test-host.ts +83 -0
- package/emitter/test/{external-process.test.ts → yaml-utils.test.ts} +2 -2
- package/eng/scripts/ci/regenerate-common.ts +1 -5
- package/eng/scripts/ci/run_pylint.py +1 -0
- 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/code_model.py +22 -8
- package/generator/build/lib/pygen/codegen/models/enum_type.py +14 -2
- package/generator/build/lib/pygen/codegen/models/operation.py +6 -1
- package/generator/build/lib/pygen/codegen/models/response.py +152 -8
- package/generator/build/lib/pygen/codegen/serializers/__init__.py +7 -0
- package/generator/build/lib/pygen/codegen/serializers/builder_serializer.py +197 -58
- package/generator/build/lib/pygen/codegen/serializers/general_serializer.py +4 -0
- package/generator/build/lib/pygen/codegen/serializers/import_serializer.py +27 -0
- package/generator/build/lib/pygen/codegen/serializers/types_serializer.py +40 -3
- package/generator/build/lib/pygen/codegen/templates/streaming_base.py.jinja2 +767 -0
- package/generator/build/lib/pygen/preprocess/__init__.py +2 -0
- package/generator/component-detection-pip-report.json +5 -5
- package/generator/dist/pygen-0.1.0-py3-none-any.whl +0 -0
- package/generator/pygen/codegen/models/code_model.py +22 -8
- package/generator/pygen/codegen/models/enum_type.py +14 -2
- package/generator/pygen/codegen/models/operation.py +6 -1
- package/generator/pygen/codegen/models/response.py +152 -8
- package/generator/pygen/codegen/serializers/__init__.py +7 -0
- package/generator/pygen/codegen/serializers/builder_serializer.py +197 -58
- package/generator/pygen/codegen/serializers/general_serializer.py +4 -0
- package/generator/pygen/codegen/serializers/import_serializer.py +27 -0
- package/generator/pygen/codegen/serializers/types_serializer.py +40 -3
- package/generator/pygen/codegen/templates/streaming_base.py.jinja2 +767 -0
- package/generator/pygen/preprocess/__init__.py +2 -0
- package/generator/pygen.egg-info/SOURCES.txt +1 -0
- package/package.json +6 -6
|
@@ -35,6 +35,8 @@ def update_overload_section(
|
|
|
35
35
|
for overload_s, original_s in zip(overload[section], yaml_data[section]):
|
|
36
36
|
if overload_s.get("type"):
|
|
37
37
|
overload_s["type"] = original_s["type"]
|
|
38
|
+
if overload_s.get("streaming"):
|
|
39
|
+
overload_s["streaming"] = original_s["streaming"]
|
|
38
40
|
if overload_s.get("headers"):
|
|
39
41
|
for overload_h, original_h in zip(overload_s["headers"], original_s["headers"]):
|
|
40
42
|
if overload_h.get("type"):
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"install": [
|
|
5
5
|
{
|
|
6
6
|
"download_info": {
|
|
7
|
-
"url": "https://
|
|
7
|
+
"url": "https://pkgs.dev.azure.com/azure-sdk/29ec6040-b234-4e31-b139-33dc4287b756/_packaging/3572dbf9-b5ef-433b-9137-fc4d7768e7cc/pypi/download/setuptools/84/setuptools-84.0.0-py3-none-any.whl",
|
|
8
8
|
"archive_info": {
|
|
9
9
|
"hashes": {
|
|
10
10
|
"sha256": "51a52592b3b99e102b609654876bd65f19f999935166d1352678931132b0c670"
|
|
@@ -122,13 +122,13 @@
|
|
|
122
122
|
],
|
|
123
123
|
"environment": {
|
|
124
124
|
"implementation_name": "cpython",
|
|
125
|
-
"implementation_version": "3.11.
|
|
125
|
+
"implementation_version": "3.11.16",
|
|
126
126
|
"os_name": "posix",
|
|
127
127
|
"platform_machine": "x86_64",
|
|
128
|
-
"platform_release": "6.17.0-
|
|
128
|
+
"platform_release": "6.17.0-1022-azure",
|
|
129
129
|
"platform_system": "Linux",
|
|
130
|
-
"platform_version": "#
|
|
131
|
-
"python_full_version": "3.11.
|
|
130
|
+
"platform_version": "#22-Ubuntu SMP Mon Jul 27 17:24:03 UTC 2026",
|
|
131
|
+
"python_full_version": "3.11.16",
|
|
132
132
|
"platform_python_implementation": "CPython",
|
|
133
133
|
"python_version": "3.11",
|
|
134
134
|
"sys_platform": "linux"
|
|
Binary file
|
|
@@ -279,12 +279,22 @@ class CodeModel: # pylint: disable=too-many-public-methods, disable=too-many-in
|
|
|
279
279
|
self.need_utils_utils(async_mode, client_namespace)
|
|
280
280
|
or self.need_utils_serialization
|
|
281
281
|
or self.options["models-mode"] == "dpg"
|
|
282
|
+
or self.has_structured_stream
|
|
282
283
|
)
|
|
283
284
|
|
|
284
285
|
@property
|
|
285
286
|
def need_utils_serialization(self) -> bool:
|
|
286
287
|
return not self.options["client-side-validation"]
|
|
287
288
|
|
|
289
|
+
@property
|
|
290
|
+
def has_structured_stream(self) -> bool:
|
|
291
|
+
return any(
|
|
292
|
+
op.has_structured_stream_response
|
|
293
|
+
for client in self.clients
|
|
294
|
+
for og in client.operation_groups
|
|
295
|
+
for op in og.operations
|
|
296
|
+
)
|
|
297
|
+
|
|
288
298
|
def need_utils_utils(self, async_mode: bool, client_namespace: str) -> bool:
|
|
289
299
|
return (
|
|
290
300
|
self.need_utils_form_data(async_mode, client_namespace)
|
|
@@ -395,22 +405,26 @@ class CodeModel: # pylint: disable=too-many-public-methods, disable=too-many-in
|
|
|
395
405
|
def _sort_model_types_helper(
|
|
396
406
|
self,
|
|
397
407
|
current: ModelType,
|
|
398
|
-
|
|
408
|
+
seen_schema_keys: set[tuple[str, str]],
|
|
399
409
|
seen_schema_yaml_ids: set[int],
|
|
400
410
|
):
|
|
401
411
|
if current.id in seen_schema_yaml_ids:
|
|
402
412
|
return []
|
|
403
|
-
|
|
404
|
-
|
|
413
|
+
current_schema_key = (current.client_namespace, current.name)
|
|
414
|
+
if current_schema_key in seen_schema_keys:
|
|
415
|
+
raise ValueError(
|
|
416
|
+
f"We have already generated a schema with name {current.name} "
|
|
417
|
+
f"in namespace {current.client_namespace}"
|
|
418
|
+
)
|
|
405
419
|
ancestors = [current]
|
|
406
420
|
if current.parents:
|
|
407
421
|
for parent in current.parents:
|
|
408
422
|
if parent.id in seen_schema_yaml_ids:
|
|
409
423
|
continue
|
|
410
|
-
|
|
424
|
+
seen_schema_keys.add(current_schema_key)
|
|
411
425
|
seen_schema_yaml_ids.add(current.id)
|
|
412
|
-
ancestors = self._sort_model_types_helper(parent,
|
|
413
|
-
|
|
426
|
+
ancestors = self._sort_model_types_helper(parent, seen_schema_keys, seen_schema_yaml_ids) + ancestors
|
|
427
|
+
seen_schema_keys.add(current_schema_key)
|
|
414
428
|
seen_schema_yaml_ids.add(current.id)
|
|
415
429
|
return ancestors
|
|
416
430
|
|
|
@@ -420,11 +434,11 @@ class CodeModel: # pylint: disable=too-many-public-methods, disable=too-many-in
|
|
|
420
434
|
:return: None
|
|
421
435
|
:rtype: None
|
|
422
436
|
"""
|
|
423
|
-
|
|
437
|
+
seen_schema_keys: set[tuple[str, str]] = set()
|
|
424
438
|
seen_schema_yaml_ids: set[int] = set()
|
|
425
439
|
sorted_object_schemas: list[ModelType] = []
|
|
426
440
|
for schema in sorted(self.model_types, key=lambda x: x.name.lower()):
|
|
427
|
-
sorted_object_schemas.extend(self._sort_model_types_helper(schema,
|
|
441
|
+
sorted_object_schemas.extend(self._sort_model_types_helper(schema, seen_schema_keys, seen_schema_yaml_ids))
|
|
428
442
|
self.model_types = sorted_object_schemas
|
|
429
443
|
|
|
430
444
|
@property
|
|
@@ -203,6 +203,10 @@ class EnumType(BaseType):
|
|
|
203
203
|
model_alias = self.code_model.get_unique_models_alias(serialize_namespace, self.client_namespace)
|
|
204
204
|
module_name = f"{model_alias}."
|
|
205
205
|
file_name = f"{self.code_model.enums_filename}." if self.internal else ""
|
|
206
|
+
if serialize_namespace_type == NamespaceType.TYPES_FILE:
|
|
207
|
+
# In types.py the enum symbol is imported directly (bare name), so no ``_enums.``
|
|
208
|
+
# module prefix even for internal enums — the prefix would be an undefined name.
|
|
209
|
+
file_name = ""
|
|
206
210
|
model_name = module_name + file_name + self.name
|
|
207
211
|
# we don't need quoted annotation in operation files, and need it in model folder files.
|
|
208
212
|
if not kwargs.get("is_operation_file", False):
|
|
@@ -305,9 +309,17 @@ class EnumType(BaseType):
|
|
|
305
309
|
typing_section=TypingSection.REGULAR,
|
|
306
310
|
)
|
|
307
311
|
elif serialize_namespace_type == NamespaceType.TYPES_FILE:
|
|
308
|
-
# Import enum
|
|
312
|
+
# Import the enum symbol directly to avoid dotted forward refs in TypedDict
|
|
313
|
+
# annotations. Internal enums are not re-exported from the public ``models``
|
|
314
|
+
# package, so import them from the private ``_enums`` submodule instead — the
|
|
315
|
+
# bare-symbol import (rather than the ``_enums`` module) also avoids name
|
|
316
|
+
# collisions when internal enums come from several sibling namespaces.
|
|
317
|
+
module_name = f"models.{self.code_model.enums_filename}" if self.internal else "models"
|
|
318
|
+
enums_module = self.code_model.get_relative_import_path(
|
|
319
|
+
serialize_namespace, self.client_namespace, module_name=module_name
|
|
320
|
+
)
|
|
309
321
|
file_import.add_submodule_import(
|
|
310
|
-
|
|
322
|
+
enums_module,
|
|
311
323
|
self.name,
|
|
312
324
|
ImportType.LOCAL,
|
|
313
325
|
typing_section=TypingSection.TYPING,
|
|
@@ -104,6 +104,11 @@ class OperationBase( # pylint: disable=too-many-public-methods,too-many-instanc
|
|
|
104
104
|
else self.has_stream_response
|
|
105
105
|
)
|
|
106
106
|
|
|
107
|
+
@property
|
|
108
|
+
def has_structured_stream_response(self) -> bool:
|
|
109
|
+
"""Whether any success response is a structured (JSONL / SSE) stream returning Stream[T]."""
|
|
110
|
+
return any(getattr(r, "is_structured_stream", False) for r in self.responses)
|
|
111
|
+
|
|
107
112
|
@property
|
|
108
113
|
def has_form_data_body(self):
|
|
109
114
|
return self.parameters.has_form_data_body
|
|
@@ -506,7 +511,7 @@ class OperationBase( # pylint: disable=too-many-public-methods,too-many-instanc
|
|
|
506
511
|
|
|
507
512
|
@property
|
|
508
513
|
def has_stream_response(self) -> bool:
|
|
509
|
-
return any(r.is_stream_response for r in self.responses)
|
|
514
|
+
return any(r.is_stream_response or r.is_structured_stream for r in self.responses)
|
|
510
515
|
|
|
511
516
|
@classmethod
|
|
512
517
|
def get_request_builder(cls, yaml_data: dict[str, Any], client: "Client"):
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
# Licensed under the MIT License. See License.txt in the project root for
|
|
4
4
|
# license information.
|
|
5
5
|
# --------------------------------------------------------------------------
|
|
6
|
+
from dataclasses import dataclass
|
|
6
7
|
from typing import Optional, Any, TYPE_CHECKING, Union
|
|
7
8
|
|
|
8
9
|
from .base import BaseModel
|
|
@@ -17,6 +18,53 @@ from .combined_type import CombinedType
|
|
|
17
18
|
if TYPE_CHECKING:
|
|
18
19
|
from .code_model import CodeModel
|
|
19
20
|
|
|
21
|
+
DEFAULT_SSE_EVENT_TYPE = "message"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass(frozen=True)
|
|
25
|
+
class StreamingEvent:
|
|
26
|
+
event_type: Optional[str]
|
|
27
|
+
payload_type: BaseType
|
|
28
|
+
payload_content_type: Optional[str]
|
|
29
|
+
is_terminal: bool
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def get_streaming_event_discriminator(
|
|
33
|
+
events: list[StreamingEvent],
|
|
34
|
+
) -> Optional[tuple[str, list[tuple[str, StreamingEvent]]]]:
|
|
35
|
+
discriminator_name: Optional[str] = None
|
|
36
|
+
variants: list[tuple[str, StreamingEvent]] = []
|
|
37
|
+
values: set[str] = set()
|
|
38
|
+
for event in events:
|
|
39
|
+
discriminator_property = getattr(event.payload_type, "discriminator_property", None)
|
|
40
|
+
discriminator_value = getattr(event.payload_type, "discriminator_value", None)
|
|
41
|
+
if discriminator_property is None or discriminator_value is None:
|
|
42
|
+
return None
|
|
43
|
+
if discriminator_name is None:
|
|
44
|
+
discriminator_name = discriminator_property.wire_name
|
|
45
|
+
elif discriminator_property.wire_name != discriminator_name:
|
|
46
|
+
return None
|
|
47
|
+
if discriminator_value in values:
|
|
48
|
+
return None
|
|
49
|
+
values.add(discriminator_value)
|
|
50
|
+
variants.append((discriminator_value, event))
|
|
51
|
+
if discriminator_name is None:
|
|
52
|
+
return None
|
|
53
|
+
return discriminator_name, variants
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _get_terminal_event_names(events: list[StreamingEvent]) -> list[str]:
|
|
57
|
+
unnamed_events = [event for event in events if event.event_type is None]
|
|
58
|
+
terminal_event_names: list[str] = []
|
|
59
|
+
for event in events:
|
|
60
|
+
if not event.is_terminal:
|
|
61
|
+
continue
|
|
62
|
+
if event.event_type is not None:
|
|
63
|
+
terminal_event_names.append(event.event_type)
|
|
64
|
+
elif len(unnamed_events) == 1:
|
|
65
|
+
terminal_event_names.append(DEFAULT_SSE_EVENT_TYPE)
|
|
66
|
+
return list(dict.fromkeys(terminal_event_names))
|
|
67
|
+
|
|
20
68
|
|
|
21
69
|
class ResponseHeader(BaseModel):
|
|
22
70
|
def __init__(
|
|
@@ -58,6 +106,25 @@ class Response(BaseModel):
|
|
|
58
106
|
self.type = type
|
|
59
107
|
self.nullable = yaml_data.get("nullable")
|
|
60
108
|
self.default_content_type = yaml_data.get("defaultContentType")
|
|
109
|
+
streaming = yaml_data.get("streaming")
|
|
110
|
+
self.streaming_kind: Optional[str] = streaming["kind"] if streaming else None
|
|
111
|
+
self.streaming_events: list[StreamingEvent] = []
|
|
112
|
+
self.terminal_event: Optional[str] = streaming.get("terminalEvent") if streaming else None
|
|
113
|
+
# Named / model ``@terminalEvent`` events: deserialized and yielded like any other event,
|
|
114
|
+
# then iteration stops. The bare string-constant sentinel (``terminal_event``)
|
|
115
|
+
# stops WITHOUT yielding and is matched on event ``data`` instead of the event name.
|
|
116
|
+
self.terminal_event_names: list[str] = []
|
|
117
|
+
if streaming:
|
|
118
|
+
self.streaming_events = [
|
|
119
|
+
StreamingEvent(
|
|
120
|
+
event_type=event.get("eventType"),
|
|
121
|
+
payload_type=self.code_model.lookup_type(id(event["payloadType"])),
|
|
122
|
+
payload_content_type=event.get("payloadContentType"),
|
|
123
|
+
is_terminal=event.get("isTerminal", False),
|
|
124
|
+
)
|
|
125
|
+
for event in streaming.get("events", [])
|
|
126
|
+
]
|
|
127
|
+
self.terminal_event_names = _get_terminal_event_names(self.streaming_events)
|
|
61
128
|
|
|
62
129
|
@property
|
|
63
130
|
def result_property(self) -> str:
|
|
@@ -92,12 +159,48 @@ class Response(BaseModel):
|
|
|
92
159
|
)
|
|
93
160
|
return retval
|
|
94
161
|
|
|
162
|
+
@property
|
|
163
|
+
def is_structured_stream(self) -> bool:
|
|
164
|
+
"""Is the response a structured (JSONL / SSE) stream rendered as Stream[T] / AsyncStream[T]."""
|
|
165
|
+
return self.streaming_kind is not None
|
|
166
|
+
|
|
167
|
+
def stream_class_name(self, async_mode: bool) -> str:
|
|
168
|
+
return "AsyncStream" if async_mode else "Stream"
|
|
169
|
+
|
|
170
|
+
@property
|
|
171
|
+
def stream_item_type(self) -> Optional[BaseType]:
|
|
172
|
+
event_item_types = list(dict.fromkeys(event.payload_type for event in self.streaming_events))
|
|
173
|
+
if len(event_item_types) == 1:
|
|
174
|
+
return event_item_types[0]
|
|
175
|
+
if event_item_types:
|
|
176
|
+
return CombinedType({"type": "combined"}, self.code_model, event_item_types)
|
|
177
|
+
return self.type
|
|
178
|
+
|
|
95
179
|
def serialization_type(self, **kwargs: Any) -> str:
|
|
96
180
|
if self.type:
|
|
97
181
|
return self.type.serialization_type(**kwargs)
|
|
98
182
|
return "None"
|
|
99
183
|
|
|
184
|
+
def stream_item_annotation(self, **kwargs: Any) -> str:
|
|
185
|
+
"""Valid type expression for a structured stream's item type.
|
|
186
|
+
|
|
187
|
+
A named ``CombinedType`` (``@events`` union) renders its ``type_annotation`` as the
|
|
188
|
+
``_unions.<Name>`` alias, which is a module-level variable and therefore rejected by
|
|
189
|
+
pyright/mypy inside ``Stream[...]`` ("Variable not allowed in type expression"). Expand
|
|
190
|
+
the union inline (``Union[Model, ...]`` / the single member) so the annotation is a
|
|
191
|
+
valid type expression.
|
|
192
|
+
"""
|
|
193
|
+
item_type = self.stream_item_type
|
|
194
|
+
if isinstance(item_type, CombinedType):
|
|
195
|
+
return item_type.type_definition(**kwargs)
|
|
196
|
+
return item_type.type_annotation(**kwargs) if item_type else "None"
|
|
197
|
+
|
|
100
198
|
def type_annotation(self, **kwargs: Any) -> str:
|
|
199
|
+
if self.is_structured_stream and self.type:
|
|
200
|
+
kwargs["is_operation_file"] = True
|
|
201
|
+
kwargs["is_response"] = True
|
|
202
|
+
stream_class = self.stream_class_name(kwargs.get("async_mode", False))
|
|
203
|
+
return f"{stream_class}[{self.stream_item_annotation(**kwargs)}]"
|
|
101
204
|
if self.type:
|
|
102
205
|
kwargs["is_operation_file"] = True
|
|
103
206
|
kwargs["is_response"] = True
|
|
@@ -109,30 +212,63 @@ class Response(BaseModel):
|
|
|
109
212
|
|
|
110
213
|
def docstring_text(self, **kwargs: Any) -> str:
|
|
111
214
|
kwargs["is_response"] = True
|
|
215
|
+
if self.is_structured_stream and self.type:
|
|
216
|
+
stream_class = self.stream_class_name(kwargs.get("async_mode", False))
|
|
217
|
+
item_type = self.stream_item_type or self.type
|
|
218
|
+
return f"An instance of {stream_class} that iterates over {item_type.docstring_text(**kwargs)}"
|
|
112
219
|
if self.nullable and self.type:
|
|
113
220
|
return f"{self.type.docstring_text(**kwargs)} or None"
|
|
114
221
|
return self.type.docstring_text(**kwargs) if self.type else "None"
|
|
115
222
|
|
|
116
223
|
def docstring_type(self, **kwargs: Any) -> str:
|
|
117
224
|
kwargs["is_response"] = True
|
|
225
|
+
if self.is_structured_stream and self.type:
|
|
226
|
+
stream_class = self.stream_class_name(kwargs.get("async_mode", False))
|
|
227
|
+
item_type = (self.stream_item_type or self.type).docstring_type(**kwargs)
|
|
228
|
+
return f"~{self.code_model.namespace}.{stream_class}[{item_type}]"
|
|
118
229
|
if self.nullable and self.type:
|
|
119
230
|
return f"{self.type.docstring_type(**kwargs)} or None"
|
|
120
231
|
return self.type.docstring_type(**kwargs) if self.type else "None"
|
|
121
232
|
|
|
122
233
|
def imports(self, **kwargs: Any) -> FileImport:
|
|
123
234
|
file_import = FileImport(self.code_model)
|
|
124
|
-
if self.type
|
|
125
|
-
|
|
235
|
+
item_type = self.stream_item_type if self.is_structured_stream else self.type
|
|
236
|
+
# For a structured stream whose item type is a named ``@events`` union, the annotation
|
|
237
|
+
# is expanded inline (see ``stream_item_annotation``), so import the union member types
|
|
238
|
+
# rather than the ``_unions`` alias.
|
|
239
|
+
if self.is_structured_stream and isinstance(item_type, CombinedType):
|
|
240
|
+
for member in item_type.types:
|
|
241
|
+
file_import.merge(member.imports(**kwargs))
|
|
242
|
+
# ``Union`` is only needed when the inline expansion actually yields a union of
|
|
243
|
+
# 2+ distinct member types (a single member collapses to that member; a union of
|
|
244
|
+
# only literals collapses to a single ``Literal[...]``).
|
|
245
|
+
distinct = list(dict.fromkeys(m.type_annotation(**kwargs) for m in item_type.types))
|
|
246
|
+
all_constant = all(t.type == "constant" for t in item_type.types)
|
|
247
|
+
if len(distinct) > 1 and not all_constant:
|
|
248
|
+
file_import.add_submodule_import("typing", "Union", ImportType.STDLIB)
|
|
249
|
+
elif item_type:
|
|
250
|
+
file_import.merge(item_type.imports(**kwargs))
|
|
251
|
+
if not self.is_structured_stream and isinstance(item_type, CombinedType) and item_type.name:
|
|
252
|
+
serialize_namespace = kwargs.get("serialize_namespace", self.code_model.namespace)
|
|
253
|
+
file_import.add_submodule_import(
|
|
254
|
+
self.code_model.get_relative_import_path(serialize_namespace),
|
|
255
|
+
"_unions",
|
|
256
|
+
ImportType.LOCAL,
|
|
257
|
+
TypingSection.TYPING,
|
|
258
|
+
)
|
|
126
259
|
if self.nullable:
|
|
127
260
|
file_import.add_submodule_import("typing", "Optional", ImportType.STDLIB)
|
|
128
|
-
if
|
|
261
|
+
if self.is_structured_stream:
|
|
262
|
+
stream_class = self.stream_class_name(kwargs.get("async_mode", False))
|
|
129
263
|
serialize_namespace = kwargs.get("serialize_namespace", self.code_model.namespace)
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
"_unions",
|
|
133
|
-
ImportType.LOCAL,
|
|
134
|
-
TypingSection.TYPING,
|
|
264
|
+
relative_path = self.code_model.get_relative_import_path(
|
|
265
|
+
serialize_namespace, module_name="_utils.streaming_base"
|
|
135
266
|
)
|
|
267
|
+
file_import.add_submodule_import(relative_path, stream_class, ImportType.LOCAL)
|
|
268
|
+
if self.streaming_kind == "sse":
|
|
269
|
+
file_import.add_import("json", ImportType.STDLIB)
|
|
270
|
+
for event in self.streaming_events:
|
|
271
|
+
file_import.merge(event.payload_type.imports(**kwargs))
|
|
136
272
|
return file_import
|
|
137
273
|
|
|
138
274
|
def _get_import_type(self, input_path: str) -> ImportType:
|
|
@@ -143,6 +279,14 @@ class Response(BaseModel):
|
|
|
143
279
|
|
|
144
280
|
@classmethod
|
|
145
281
|
def from_yaml(cls, yaml_data: dict[str, Any], code_model: "CodeModel") -> "Response":
|
|
282
|
+
streaming = yaml_data.get("streaming")
|
|
283
|
+
if streaming:
|
|
284
|
+
return cls(
|
|
285
|
+
yaml_data=yaml_data,
|
|
286
|
+
code_model=code_model,
|
|
287
|
+
headers=[ResponseHeader.from_yaml(header, code_model) for header in yaml_data["headers"]],
|
|
288
|
+
type=code_model.lookup_type(id(streaming["itemType"])),
|
|
289
|
+
)
|
|
146
290
|
type = code_model.lookup_type(id(yaml_data["type"])) if yaml_data.get("type") else None
|
|
147
291
|
# use ByteIteratorType if we are returning a binary type
|
|
148
292
|
default_content_type = yaml_data.get("defaultContentType", "application/json")
|
|
@@ -525,6 +525,13 @@ class JinjaSerializer(ReaderAndWriter):
|
|
|
525
525
|
general_serializer.serialize_model_base_file(),
|
|
526
526
|
)
|
|
527
527
|
|
|
528
|
+
# write _utils/streaming_base.py (vendored Stream/AsyncStream + JSONL/SSE decoders)
|
|
529
|
+
if self.code_model.has_structured_stream:
|
|
530
|
+
self.write_file(
|
|
531
|
+
utils_folder_path / Path("streaming_base.py"),
|
|
532
|
+
general_serializer.serialize_streaming_base_file(),
|
|
533
|
+
)
|
|
534
|
+
|
|
528
535
|
def _serialize_and_write_top_level_folder(self, env: Environment, namespace: str) -> None:
|
|
529
536
|
root_dir = self.code_model.get_root_dir()
|
|
530
537
|
generation_dir = self.code_model.get_generation_dir(namespace)
|