diff --git a/.chronus/changes/structured-streaming-2026-0-0.md b/.chronus/changes/structured-streaming-2026-0-0.md new file mode 100644 index 00000000000..afb8cd44096 --- /dev/null +++ b/.chronus/changes/structured-streaming-2026-0-0.md @@ -0,0 +1,12 @@ +--- +changeKind: feature +packages: + - "@typespec/http-client-python" +--- + +Generate structured JSONL (`application/jsonl`) and SSE (`text/event-stream`) response streams for the Azure flavor. Generated methods return standard `Generator[T, None, None]` or `AsyncGenerator[T, None]` values that yield deserialized model instances. Unbranded response streams remain raw-byte iterators. + +```python +for thing in client.receive(): + ... +``` diff --git a/cspell.yaml b/cspell.yaml index 4fdbe6d4b2f..6273e27f277 100644 --- a/cspell.yaml +++ b/cspell.yaml @@ -7,11 +7,15 @@ dictionaries: words: - Ablack - Adoptium + - aenter + - aexit - agentic - agentics - aiohttp + - aiter - alzimmer - amqp + - anext - AQID - Arize - arizeaiobservabilityeval @@ -120,6 +124,7 @@ words: - intrinsics - ints - IOHTTP + - isascii - isdigit - isinstance - issecret diff --git a/packages/http-client-python/README.md b/packages/http-client-python/README.md index 0c3335df5fa..f66e9ce5c40 100644 --- a/packages/http-client-python/README.md +++ b/packages/http-client-python/README.md @@ -153,3 +153,30 @@ Whether to clear the output folder before generating the code. Defaults to `fals **Type:** `boolean` Emit YAML code model only, without running Python generator. For batch processing. + +## Structured streaming (JSONL / SSE) + +For the **Azure flavor**, operations whose HTTP response is a JSONL (`application/jsonl`) or SSE (`text/event-stream`) stream generate client methods that return `Generator[T, None, None]` (sync) or `AsyncGenerator[T, None]` (async). The generators yield deserialized model instances as each record arrives. This behavior is driven by TCGC response stream metadata; there is no emitter option. The unbranded flavor retains its existing raw-byte response behavior (`Iterator[bytes]` / `AsyncIterator[bytes]`). + +Fully consuming a generator closes the underlying response: + +```python +for thing in client.receive(): + ... +``` + +When stopping early, explicitly close the generator so the response is released: + +```python +from contextlib import aclosing, closing + +with closing(client.receive()) as stream: + first = next(stream) + +async with aclosing(async_client.receive()) as stream: + first = await anext(stream) +``` + +The generated package keeps transport-neutral framing helpers private in `_utils/streaming.py`; generated operations own JSON parsing and model deserialization. The runtime uses only released `azure-core` APIs. + +SSE `@events` unions use TCGC event metadata to deserialize each named event into its corresponding generated model. Events marked with `@terminalEvent` stop iteration without being yielded. diff --git a/packages/http-client-python/emitter/src/http.ts b/packages/http-client-python/emitter/src/http.ts index 31d970d4b65..985864f4bc1 100644 --- a/packages/http-client-python/emitter/src/http.ts +++ b/packages/http-client-python/emitter/src/http.ts @@ -42,6 +42,89 @@ export enum ReferredByOperationTypes { NonPagingOnly = 2, } +type StructuredStreamKind = "jsonl" | "sse"; +type EmittedType = ReturnType; + +interface StructuredStreamEvent { + eventType: string | undefined; + itemType: EmittedType; +} + +interface StructuredStreamingInfo { + kind: StructuredStreamKind; + itemType: EmittedType; + events?: StructuredStreamEvent[]; + terminalEvent?: string; +} + +/** Whether pygen can deserialize the stream item type. */ +export function isStructuredStreamType(type: SdkType): boolean { + switch (type.kind) { + case "model": + case "union": + return true; + case "nullable": + return isStructuredStreamType(type.type); + default: + return false; + } +} + +export function getStructuredStreamKind( + response: SdkHttpResponse | SdkHttpErrorResponse, +): StructuredStreamKind | undefined { + const contentTypes = response.streamMetadata?.contentTypes ?? response.contentTypes ?? []; + for (const contentType of contentTypes) { + if (contentType === "text/event-stream") return "sse"; + if (contentType === "application/jsonl") return "jsonl"; + } + return undefined; +} + +function getStringConstantValue(type: SdkType): string | undefined { + if (type.kind === "nullable") return getStringConstantValue(type.type); + return type.kind === "constant" && typeof type.value === "string" ? type.value : undefined; +} + +export function emitStructuredStreamingInfo( + context: PythonSdkContext, + response: SdkHttpResponse | SdkHttpErrorResponse, +): StructuredStreamingInfo | undefined { + if ((context.emitContext.options as any).flavor !== "azure") return undefined; + + const streamMetadata = response.streamMetadata; + if (!streamMetadata || !isStructuredStreamType(streamMetadata.streamType)) return undefined; + + const kind = getStructuredStreamKind(response); + if (!kind) return undefined; + + const streaming: StructuredStreamingInfo = { + kind, + itemType: getType(context, streamMetadata.streamType), + }; + + const sseMetadata = response.sseMetadata; + if (!sseMetadata) return streaming; + + const events = sseMetadata.events + .filter((event) => !event.isTerminalEvent) + .map((event) => ({ + eventType: event.eventType, + itemType: getType(context, event.payloadType), + })); + if (events.length > 0) streaming.events = events; + + const terminalEvent = sseMetadata.events.find((event) => event.isTerminalEvent); + if (terminalEvent) { + const terminalEventValue = + getStringConstantValue(terminalEvent.payloadType) ?? + getStringConstantValue(terminalEvent.type); + if (terminalEventValue !== undefined) streaming.terminalEvent = terminalEventValue; + } + + return streaming; +} + function isEtagType(type: SdkType): boolean { if (type.kind === "nullable") return isEtagType(type.type); const raw = type.__raw; @@ -682,6 +765,7 @@ function emitHttpResponse( "invalid-lro-result", method, ), + streaming: isException ? undefined : emitStructuredStreamingInfo(context, response), }; } diff --git a/packages/http-client-python/emitter/test/streaming.test.ts b/packages/http-client-python/emitter/test/streaming.test.ts new file mode 100644 index 00000000000..dbe3b8647b2 --- /dev/null +++ b/packages/http-client-python/emitter/test/streaming.test.ts @@ -0,0 +1,159 @@ +import { deepStrictEqual, strictEqual } from "assert"; +import { describe, it } from "vitest"; +import { + emitStructuredStreamingInfo, + getStructuredStreamKind, + isStructuredStreamType, +} from "../src/http.js"; + +describe("typespec-python: structured streaming", () => { + it("treats model and union payloads as structured", () => { + strictEqual(isStructuredStreamType({ kind: "model" } as any), true); + strictEqual(isStructuredStreamType({ kind: "union" } as any), true); + }); + + it("unwraps nullable payloads", () => { + strictEqual(isStructuredStreamType({ kind: "nullable", type: { kind: "model" } } as any), true); + strictEqual( + isStructuredStreamType({ kind: "nullable", type: { kind: "bytes" } } as any), + false, + ); + }); + + it("treats bare byte/string payloads as unstructured", () => { + strictEqual(isStructuredStreamType({ kind: "bytes" } as any), false); + strictEqual(isStructuredStreamType({ kind: "string" } as any), false); + }); + + it("requires exact structured streaming media types", () => { + strictEqual( + getStructuredStreamKind({ + streamMetadata: { contentTypes: ["text/event-stream"] }, + } as any), + "sse", + ); + strictEqual( + getStructuredStreamKind({ + streamMetadata: { contentTypes: ["application/jsonl"] }, + } as any), + "jsonl", + ); + strictEqual( + getStructuredStreamKind({ + sseMetadata: { events: [] }, + streamMetadata: { contentTypes: ["text/event-stream; charset=utf-8"] }, + } as any), + undefined, + ); + strictEqual( + getStructuredStreamKind({ + streamMetadata: { contentTypes: ["application/json"] }, + } as any), + undefined, + ); + }); + + it("emits Azure JSONL metadata for a structured stream", () => { + const itemType = { kind: "model", name: "Item", properties: [{}] }; + const context = { + emitContext: { options: { flavor: "azure" } }, + __typesMap: new Map([[itemType, { type: "model", name: "Item" }]]), + __simpleTypesMap: new Map(), + }; + + deepStrictEqual( + emitStructuredStreamingInfo( + context as any, + { + streamMetadata: { + contentTypes: ["application/jsonl"], + streamType: itemType, + }, + } as any, + ), + { + kind: "jsonl", + itemType: { type: "model", name: "Item" }, + }, + ); + }); + + it("emits SSE event and terminal metadata", () => { + const itemType = { kind: "model", name: "Events", properties: [{}] }; + const connected = { kind: "model", name: "Connected", properties: [{}] }; + const message = { kind: "model", name: "Message", properties: [{}] }; + const terminal = { + kind: "constant", + value: "[DONE]", + valueType: { kind: "string" }, + }; + const emitted = new Map([ + [itemType, { type: "combined", name: "Events" }], + [connected, { type: "model", name: "Connected" }], + [message, { type: "model", name: "Message" }], + ]); + const context = { + emitContext: { options: { flavor: "azure" } }, + __typesMap: emitted, + __simpleTypesMap: new Map(), + }; + + deepStrictEqual( + emitStructuredStreamingInfo( + context as any, + { + streamMetadata: { + contentTypes: ["text/event-stream"], + streamType: itemType, + }, + sseMetadata: { + events: [ + { eventType: "connected", payloadType: connected }, + { eventType: undefined, payloadType: message }, + { + eventType: undefined, + payloadType: terminal, + type: terminal, + isTerminalEvent: true, + }, + ], + }, + } as any, + ), + { + kind: "sse", + itemType: { type: "combined", name: "Events" }, + events: [ + { + eventType: "connected", + itemType: { type: "model", name: "Connected" }, + }, + { + eventType: undefined, + itemType: { type: "model", name: "Message" }, + }, + ], + terminalEvent: "[DONE]", + }, + ); + }); + + it("explicitly excludes unbranded generation", () => { + strictEqual( + emitStructuredStreamingInfo( + { + emitContext: { options: { flavor: "unbranded" } }, + __typesMap: new Map(), + __simpleTypesMap: new Map(), + } as any, + { + streamMetadata: { + contentTypes: ["application/jsonl"], + streamType: { kind: "model" }, + }, + } as any, + ), + undefined, + ); + }); +}); diff --git a/packages/http-client-python/generator/pygen/codegen/models/code_model.py b/packages/http-client-python/generator/pygen/codegen/models/code_model.py index 73cd410eb84..e7c99f23c92 100644 --- a/packages/http-client-python/generator/pygen/codegen/models/code_model.py +++ b/packages/http-client-python/generator/pygen/codegen/models/code_model.py @@ -279,12 +279,27 @@ def need_utils_folder(self, async_mode: bool, client_namespace: str) -> bool: self.need_utils_utils(async_mode, client_namespace) or self.need_utils_serialization or self.options["models-mode"] == "dpg" + or self.need_streaming ) @property def need_utils_serialization(self) -> bool: return not self.options["client-side-validation"] + @property + def has_structured_stream(self) -> bool: + return any( + op.has_structured_stream_response + for client in self.clients + for og in client.operation_groups + for op in og.operations + ) + + @property + def need_streaming(self) -> bool: + """Whether to emit the private structured-stream framing helpers.""" + return self.has_structured_stream + def need_utils_utils(self, async_mode: bool, client_namespace: str) -> bool: return ( self.need_utils_form_data(async_mode, client_namespace) diff --git a/packages/http-client-python/generator/pygen/codegen/models/operation.py b/packages/http-client-python/generator/pygen/codegen/models/operation.py index 3c6d525f122..b442762cd12 100644 --- a/packages/http-client-python/generator/pygen/codegen/models/operation.py +++ b/packages/http-client-python/generator/pygen/codegen/models/operation.py @@ -98,12 +98,20 @@ def exact_name_params(self) -> set[str]: @property def stream_value(self) -> Union[str, bool]: + # Structured streams must always run the pipeline incrementally. + if self.has_structured_stream_response: + return True return ( f'kwargs.pop("stream", {self.has_stream_response})' if self.expose_stream_keyword and self.has_response_body and "stream" not in self.exact_name_params else self.has_stream_response ) + @property + def has_structured_stream_response(self) -> bool: + """Whether any success response is a structured JSONL or SSE stream.""" + return any(getattr(r, "is_structured_stream", False) for r in self.responses) + @property def has_form_data_body(self): return self.parameters.has_form_data_body diff --git a/packages/http-client-python/generator/pygen/codegen/models/response.py b/packages/http-client-python/generator/pygen/codegen/models/response.py index 99a90481319..853743f810b 100644 --- a/packages/http-client-python/generator/pygen/codegen/models/response.py +++ b/packages/http-client-python/generator/pygen/codegen/models/response.py @@ -58,6 +58,15 @@ def __init__( self.type = type self.nullable = yaml_data.get("nullable") self.default_content_type = yaml_data.get("defaultContentType") + streaming = yaml_data.get("streaming") + self.streaming_kind: Optional[str] = streaming["kind"] if streaming else None + self.streaming_events: list[tuple[Optional[str], BaseType]] = [] + self._streaming_terminal_event: Optional[str] = streaming.get("terminalEvent") if streaming else None + if streaming: + self.streaming_events = [ + (event.get("eventType"), self.code_model.lookup_type(id(event["itemType"]))) + for event in streaming.get("events", []) + ] @property def result_property(self) -> str: @@ -92,12 +101,71 @@ def is_stream_response(self) -> bool: ) return retval + @property + def is_structured_stream(self) -> bool: + """Whether the response is a structured JSONL or SSE stream.""" + return self.streaming_kind is not None + + @property + def terminal_event(self) -> Optional[str]: + """Terminal event marker for a heterogeneous SSE stream, if any. + + TCGC ``sseMetadata`` supplies this marker directly. For compatibility with + older metadata, a string-literal member of the item union is used as a fallback. + """ + if self.streaming_kind != "sse": + return None + if self._streaming_terminal_event is not None: + return self._streaming_terminal_event + if not isinstance(self.type, CombinedType): + return None + from .constant_type import ConstantType + + for member in self.type.types: + if isinstance(member, ConstantType) and isinstance(member.value, str): + return member.value + return None + + @property + def stream_item_types(self) -> list[BaseType]: + if self.streaming_events: + return list(dict.fromkeys(item_type for _, item_type in self.streaming_events)) + if isinstance(self.type, CombinedType) and self.terminal_event is not None: + from .constant_type import ConstantType + + return [ + item_type + for item_type in self.type.types + if not (isinstance(item_type, ConstantType) and item_type.value == self.terminal_event) + ] + return [self.type] if self.type else [] + def serialization_type(self, **kwargs: Any) -> str: if self.type: return self.type.serialization_type(**kwargs) return "None" + def stream_item_annotation(self, **kwargs: Any) -> str: + """Valid type expression for a structured stream's item type. + + Terminal literal members are omitted because they stop iteration rather than being + yielded. + """ + annotations = list(dict.fromkeys(item_type.type_annotation(**kwargs) for item_type in self.stream_item_types)) + if not annotations: + return "None" + if len(annotations) == 1: + return annotations[0] + return f"Union[{', '.join(annotations)}]" + def type_annotation(self, **kwargs: Any) -> str: + if self.is_structured_stream and self.type: + kwargs["is_operation_file"] = True + kwargs["is_response"] = True + item_annotation = self.stream_item_annotation(**kwargs) + if kwargs.get("async_mode", False): + return f"AsyncGenerator[{item_annotation}, None]" + return f"Generator[{item_annotation}, None, None]" if self.type: kwargs["is_operation_file"] = True kwargs["is_response"] = True @@ -109,30 +177,64 @@ def type_annotation(self, **kwargs: Any) -> str: def docstring_text(self, **kwargs: Any) -> str: kwargs["is_response"] = True + if self.is_structured_stream and self.type: + item_text = " or ".join( + dict.fromkeys(item_type.docstring_text(**kwargs) for item_type in self.stream_item_types) + ) + return f"An iterator that yields {item_text}" if self.nullable and self.type: return f"{self.type.docstring_text(**kwargs)} or None" return self.type.docstring_text(**kwargs) if self.type else "None" def docstring_type(self, **kwargs: Any) -> str: kwargs["is_response"] = True + if self.is_structured_stream and self.type: + item_type = " or ".join(dict.fromkeys(item.docstring_type(**kwargs) for item in self.stream_item_types)) + if kwargs.get("async_mode", False): + return f"AsyncGenerator[{item_type}, None]" + return f"Generator[{item_type}, None, None]" if self.nullable and self.type: return f"{self.type.docstring_type(**kwargs)} or None" return self.type.docstring_type(**kwargs) if self.type else "None" def imports(self, **kwargs: Any) -> FileImport: file_import = FileImport(self.code_model) - if self.type: - file_import.merge(self.type.imports(**kwargs)) + item_type = self.type + if self.is_structured_stream: + item_types = self.stream_item_types + for member in item_types: + file_import.merge(member.imports(**kwargs)) + if len(list(dict.fromkeys(member.type_annotation(**kwargs) for member in item_types))) > 1: + file_import.add_submodule_import("typing", "Union", ImportType.STDLIB) + elif item_type: + file_import.merge(item_type.imports(**kwargs)) + if not self.is_structured_stream and isinstance(item_type, CombinedType) and item_type.name: + serialize_namespace = kwargs.get("serialize_namespace", self.code_model.namespace) + file_import.add_submodule_import( + self.code_model.get_relative_import_path(serialize_namespace), + "_unions", + ImportType.LOCAL, + TypingSection.TYPING, + ) if self.nullable: file_import.add_submodule_import("typing", "Optional", ImportType.STDLIB) - if isinstance(self.type, CombinedType) and self.type.name: + if self.is_structured_stream: + async_mode = kwargs.get("async_mode", False) + generator_type = "AsyncGenerator" if async_mode else "Generator" + file_import.add_submodule_import("typing", generator_type, ImportType.STDLIB) serialize_namespace = kwargs.get("serialize_namespace", self.code_model.namespace) - file_import.add_submodule_import( - self.code_model.get_relative_import_path(serialize_namespace), - "_unions", - ImportType.LOCAL, - TypingSection.TYPING, + relative_path = self.code_model.get_relative_import_path( + serialize_namespace, module_name="_utils.streaming" ) + framing_helper = f"_aiter_{self.streaming_kind}" if async_mode else f"_iter_{self.streaming_kind}" + file_import.add_submodule_import(relative_path, framing_helper, ImportType.LOCAL) + file_import.add_import("json", ImportType.STDLIB) + if ( + self.streaming_kind == "sse" + and any(event_type is not None for event_type, _ in self.streaming_events) + and not any(event_type is None for event_type, _ in self.streaming_events) + ): + file_import.add_submodule_import("exceptions", "DeserializationError", ImportType.SDKCORE) return file_import def _get_import_type(self, input_path: str) -> ImportType: @@ -143,6 +245,14 @@ def _get_import_type(self, input_path: str) -> ImportType: @classmethod def from_yaml(cls, yaml_data: dict[str, Any], code_model: "CodeModel") -> "Response": + streaming = yaml_data.get("streaming") + if streaming: + return cls( + yaml_data=yaml_data, + code_model=code_model, + headers=[ResponseHeader.from_yaml(header, code_model) for header in yaml_data["headers"]], + type=code_model.lookup_type(id(streaming["itemType"])), + ) type = code_model.lookup_type(id(yaml_data["type"])) if yaml_data.get("type") else None # use ByteIteratorType if we are returning a binary type default_content_type = yaml_data.get("defaultContentType", "application/json") diff --git a/packages/http-client-python/generator/pygen/codegen/serializers/__init__.py b/packages/http-client-python/generator/pygen/codegen/serializers/__init__.py index c5d786a4a6e..6d0f15c3c1a 100644 --- a/packages/http-client-python/generator/pygen/codegen/serializers/__init__.py +++ b/packages/http-client-python/generator/pygen/codegen/serializers/__init__.py @@ -525,6 +525,13 @@ def _serialize_and_write_utils_folder(self, env: Environment, namespace: str): general_serializer.serialize_model_base_file(), ) + # write private transport-neutral JSONL/SSE framing helpers + if self.code_model.need_streaming: + self.write_file( + utils_folder_path / Path("streaming.py"), + general_serializer.serialize_streaming_file(), + ) + def _serialize_and_write_top_level_folder(self, env: Environment, namespace: str) -> None: root_dir = self.code_model.get_root_dir() generation_dir = self.code_model.get_generation_dir(namespace) diff --git a/packages/http-client-python/generator/pygen/codegen/serializers/builder_serializer.py b/packages/http-client-python/generator/pygen/codegen/serializers/builder_serializer.py index 152b840d260..e189164ea08 100644 --- a/packages/http-client-python/generator/pygen/codegen/serializers/builder_serializer.py +++ b/packages/http-client-python/generator/pygen/codegen/serializers/builder_serializer.py @@ -585,7 +585,9 @@ def example_template(self, builder: OperationType) -> list[str]: def make_pipeline_call(self, builder: OperationType) -> list[str]: retval = [] type_ignore = self.async_mode and builder.group_name == "" # is in a mixin - if builder.stream_value: + if builder.has_structured_stream_response: + retval.append("kwargs.pop('stream', None)") + elif builder.stream_value: retval.append("_decompress = kwargs.pop('decompress', True)") pylint_disable = " # pylint: disable=protected-access" if self.code_model.is_azure_flavor else "" retval.extend( @@ -1256,15 +1258,102 @@ def handle_error_response( # pylint: disable=too-many-statements, too-many-bran ) return retval - def handle_response(self, builder: OperationType) -> list[str]: - retval: list[str] = ["response = pipeline_response.http_response"] - retval.append("") - retval.extend(self.handle_error_response(builder)) - retval.append("") - if builder.has_optional_return_type: - retval.append("deserialized = None") - if builder.any_response_has_headers: - retval.append("response_headers = {}") + def handle_structured_stream_response(self, builder: OperationType) -> list[str]: + """Emit the body for an operation returning a structured (JSONL / SSE) stream. + + The generated operation owns JSON parsing, deserialization, terminal filtering, + event dispatch, customization, and response cleanup. + """ + response = next(r for r in builder.responses if getattr(r, "is_structured_stream", False)) + item_annotation = response.stream_item_annotation( + is_operation_file=True, + is_response=True, + serialize_namespace=self.serialize_namespace, + ) + terminal_event = getattr(response, "terminal_event", None) + streaming_events = getattr(response, "streaming_events", []) + generator_annotation = ( + f"AsyncGenerator[{item_annotation}, None]" + if self.async_mode + else f"Generator[{item_annotation}, None, None]" + ) + function_def = "async def" if self.async_mode else "def" + loop = "async for" if self.async_mode else "for" + helper = f"_aiter_{response.streaming_kind}" if self.async_mode else f"_iter_{response.streaming_kind}" + retval = [ + f"{function_def} _response_iterator() -> {generator_annotation}:", + " try:", + ] + + if response.streaming_kind == "sse": # type: ignore[attr-defined] + retval.append(f" {loop} _event in {helper}(response):") + indent = " " + if terminal_event is not None: + retval.append(f"{indent}if _event.data == {terminal_event!r}:") + retval.append(f"{indent} break") + retval.append(f"{indent}_event_json = json.loads(_event.data)") + named_events = [ + (event_type, event_item_type) + for event_type, event_item_type in streaming_events + if event_type is not None + ] + unnamed_events = [event_item_type for event_type, event_item_type in streaming_events if event_type is None] + if named_events: + for index, (event_type, event_item_type) in enumerate(named_events): + event_annotation = event_item_type.type_annotation( + is_operation_file=True, + serialize_namespace=self.serialize_namespace, + ) + keyword = "if" if index == 0 else "elif" + retval.append(f"{indent}{keyword} _event.event == {event_type!r}:") + retval.append(f"{indent} deserialized = _deserialize({event_annotation}, _event_json)") + retval.append(f"{indent}else:") + if len(unnamed_events) == 1: + event_annotation = unnamed_events[0].type_annotation( + is_operation_file=True, + serialize_namespace=self.serialize_namespace, + ) + retval.append(f"{indent} deserialized = _deserialize({event_annotation}, _event_json)") + else: + retval.append( + f'{indent} raise DeserializationError(f"Unexpected SSE event type: ' '{_event.event!r}")' + ) + elif len(unnamed_events) == 1: + event_annotation = unnamed_events[0].type_annotation( + is_operation_file=True, + serialize_namespace=self.serialize_namespace, + ) + retval.append(f"{indent}deserialized = _deserialize({event_annotation}, _event_json)") + else: + retval.append(f"{indent}deserialized = _deserialize({item_annotation}, _event_json)") + retval.append(f"{indent}yield deserialized") + else: + retval.extend( + [ + f" {loop} _data in {helper}(response):", + " _event_json = json.loads(_data)", + f" yield _deserialize({item_annotation}, _event_json)", + ] + ) + + retval.append(" finally:") + if self.async_mode: + retval.append(" await response.close()") + else: + retval.append(" response.close()") + retval.extend( + [ + "", + "_response_generator = _response_iterator()", + "if cls:", + " return cls(pipeline_response, _response_generator, {}) # type: ignore", + "return _response_generator", + ] + ) + return retval + + def _handle_response_body(self, builder: OperationType) -> list[str]: + retval: list[str] = [] if builder.has_response_body or builder.any_response_has_headers: # pylint: disable=too-many-nested-blocks if len(builder.responses) > 1: status_codes, res_headers, res_deserialization = [], [], [] @@ -1299,6 +1388,21 @@ def handle_response(self, builder: OperationType) -> list[str]: else: retval.extend(self.response_headers_and_deserialization(builder, builder.responses[0])) retval.append("") + return retval + + def handle_response(self, builder: OperationType) -> list[str]: + retval: list[str] = ["response = pipeline_response.http_response"] + retval.append("") + retval.extend(self.handle_error_response(builder)) + retval.append("") + if builder.has_structured_stream_response: + retval.extend(self.handle_structured_stream_response(builder)) + return retval + if builder.has_optional_return_type: + retval.append("deserialized = None") + if builder.any_response_has_headers: + retval.append("response_headers = {}") + retval.extend(self._handle_response_body(builder)) if ( builder.has_optional_return_type or self.code_model.options["models-mode"] diff --git a/packages/http-client-python/generator/pygen/codegen/serializers/general_serializer.py b/packages/http-client-python/generator/pygen/codegen/serializers/general_serializer.py index d44dbc8bc02..fba0ec6963c 100644 --- a/packages/http-client-python/generator/pygen/codegen/serializers/general_serializer.py +++ b/packages/http-client-python/generator/pygen/codegen/serializers/general_serializer.py @@ -318,6 +318,10 @@ def serialize_model_base_file(self) -> str: template = self.env.get_template("model_base.py.jinja2") return template.render(code_model=self.code_model, file_import=FileImport(self.code_model)) + def serialize_streaming_file(self) -> str: + template = self.env.get_template("streaming.py.jinja2") + return template.render(code_model=self.code_model, file_import=FileImport(self.code_model)) + def serialize_validation_file(self) -> str: template = self.env.get_template("validation.py.jinja2") return template.render( diff --git a/packages/http-client-python/generator/pygen/codegen/templates/streaming.py.jinja2 b/packages/http-client-python/generator/pygen/codegen/templates/streaming.py.jinja2 new file mode 100644 index 00000000000..d10a4f18c63 --- /dev/null +++ b/packages/http-client-python/generator/pygen/codegen/templates/streaming.py.jinja2 @@ -0,0 +1,178 @@ +# coding=utf-8 +{% if code_model.license_header %} +{{ code_model.license_header }} +{% endif %} +import codecs +from typing import Any, AsyncIterator, Iterator, List, Optional, Tuple + + +def _iter_jsonl(response: Any) -> Iterator[str]: + decoder = codecs.getincrementaldecoder("utf-8")() + buffered = "" + for chunk in response.iter_bytes(): + buffered += decoder.decode(chunk) + lines = buffered.split("\n") + buffered = lines.pop() + for line in lines: + line = line[:-1] if line.endswith("\r") else line + if line.strip(): + yield line + + buffered += decoder.decode(b"", final=True) + if buffered.endswith("\r"): + buffered = buffered[:-1] + if buffered.strip(): + yield buffered + + +async def _aiter_jsonl(response: Any) -> AsyncIterator[str]: + decoder = codecs.getincrementaldecoder("utf-8")() + buffered = "" + async for chunk in response.iter_bytes(): + buffered += decoder.decode(chunk) + lines = buffered.split("\n") + buffered = lines.pop() + for line in lines: + line = line[:-1] if line.endswith("\r") else line + if line.strip(): + yield line + + buffered += decoder.decode(b"", final=True) + if buffered.endswith("\r"): + buffered = buffered[:-1] + if buffered.strip(): + yield buffered + + +def _split_sse_lines(buffered: str) -> Tuple[List[str], str]: + lines: List[str] = [] + start = 0 + index = 0 + while index < len(buffered): + character = buffered[index] + if character == "\n": + lines.append(buffered[start:index]) + index += 1 + start = index + elif character == "\r": + if index + 1 == len(buffered): + break + lines.append(buffered[start:index]) + index += 2 if buffered[index + 1] == "\n" else 1 + start = index + else: + index += 1 + return lines, buffered[start:] + + +def _iter_sse_lines(response: Any) -> Iterator[str]: + decoder = codecs.getincrementaldecoder("utf-8-sig")(errors="replace") + buffered = "" + for chunk in response.iter_bytes(): + buffered += decoder.decode(chunk) + lines, buffered = _split_sse_lines(buffered) + yield from lines + + buffered += decoder.decode(b"", final=True) + lines, remainder = _split_sse_lines(buffered) + yield from lines + if remainder: + yield remainder[:-1] if remainder.endswith("\r") else remainder + + +async def _aiter_sse_lines(response: Any) -> AsyncIterator[str]: + decoder = codecs.getincrementaldecoder("utf-8-sig")(errors="replace") + buffered = "" + async for chunk in response.iter_bytes(): + buffered += decoder.decode(chunk) + lines, buffered = _split_sse_lines(buffered) + for line in lines: + yield line + + buffered += decoder.decode(b"", final=True) + lines, remainder = _split_sse_lines(buffered) + for line in lines: + yield line + if remainder: + yield remainder[:-1] if remainder.endswith("\r") else remainder + + +class _SSEEvent: + def __init__( + self, + *, + event: str, + data: str, + event_id: str, + retry: Optional[int], + ) -> None: + self.event = event + self.data = data + self.id = event_id + self.retry = retry + + +class _SSEEventBuilder: + def __init__(self) -> None: + self._data: List[str] = [] + self._event_type = "" + self._last_id = "" + self._retry: Optional[int] = None + + def add_line(self, line: str) -> Optional[_SSEEvent]: + if not line: + return self.dispatch() + if line.startswith(":"): + return None + + field, separator, value = line.partition(":") + if separator and value.startswith(" "): + value = value[1:] + if field == "event": + self._event_type = value + elif field == "data": + self._data.append(value) + elif field == "id" and "\x00" not in value: + self._last_id = value + elif field == "retry" and value.isascii() and value.isdigit(): + try: + self._retry = int(value) + except ValueError: + pass + return None + + def dispatch(self) -> Optional[_SSEEvent]: + if not self._data: + self._event_type = "" + return None + event = _SSEEvent( + event=self._event_type or "message", + data="\n".join(self._data), + event_id=self._last_id, + retry=self._retry, + ) + self._data = [] + self._event_type = "" + return event + + +def _iter_sse(response: Any) -> Iterator[_SSEEvent]: + builder = _SSEEventBuilder() + for line in _iter_sse_lines(response): + event = builder.add_line(line) + if event is not None: + yield event + event = builder.dispatch() + if event is not None: + yield event + + +async def _aiter_sse(response: Any) -> AsyncIterator[_SSEEvent]: + builder = _SSEEventBuilder() + async for line in _aiter_sse_lines(response): + event = builder.add_line(line) + if event is not None: + yield event + event = builder.dispatch() + if event is not None: + yield event diff --git a/packages/http-client-python/generator/pygen/preprocess/__init__.py b/packages/http-client-python/generator/pygen/preprocess/__init__.py index ff3d6f094e3..d79fcb42235 100644 --- a/packages/http-client-python/generator/pygen/preprocess/__init__.py +++ b/packages/http-client-python/generator/pygen/preprocess/__init__.py @@ -35,6 +35,8 @@ def update_overload_section( for overload_s, original_s in zip(overload[section], yaml_data[section]): if overload_s.get("type"): overload_s["type"] = original_s["type"] + if overload_s.get("streaming"): + overload_s["streaming"] = original_s["streaming"] if overload_s.get("headers"): for overload_h, original_h in zip(overload_s["headers"], original_s["headers"]): if overload_h.get("type"): diff --git a/packages/http-client-python/package-lock.json b/packages/http-client-python/package-lock.json index 41dd54e4b17..4533f7e1f3e 100644 --- a/packages/http-client-python/package-lock.json +++ b/packages/http-client-python/package-lock.json @@ -18,22 +18,22 @@ }, "devDependencies": { "@azure-tools/azure-http-specs": "0.1.0-alpha.43", - "@azure-tools/typespec-autorest": "~0.70.0", + "@azure-tools/typespec-autorest": "0.71.0-dev.4", "@azure-tools/typespec-azure-core": "~0.70.0", "@azure-tools/typespec-azure-resource-manager": "~0.70.0", - "@azure-tools/typespec-azure-rulesets": "~0.70.0", - "@azure-tools/typespec-client-generator-core": "~0.70.0", + "@azure-tools/typespec-azure-rulesets": "0.71.0-dev.5", + "@azure-tools/typespec-client-generator-core": "0.71.0-dev.11", "@types/js-yaml": "~4.0.5", "@types/node": "~25.0.2", "@types/semver": "7.5.8", - "@typespec/compiler": "^1.14.0", + "@typespec/compiler": "1.15.0-dev.17", "@typespec/events": "~0.84.0", "@typespec/http": "^1.14.0", - "@typespec/http-specs": "0.1.0-alpha.39", + "@typespec/http-specs": "0.1.0-alpha.40", "@typespec/openapi": "^1.14.0", "@typespec/rest": "~0.84.0", "@typespec/spec-api": "0.1.0-alpha.15", - "@typespec/spector": "0.1.0-alpha.26", + "@typespec/spector": "0.1.0-alpha.27", "@typespec/sse": "~0.84.0", "@typespec/streams": "~0.84.0", "@typespec/versioning": "~0.84.0", @@ -50,12 +50,12 @@ "node": ">=22.0.0" }, "peerDependencies": { - "@azure-tools/typespec-autorest": ">=0.70.0 <1.0.0", + "@azure-tools/typespec-autorest": ">=0.70.0 <1.0.0 || >=0.71.0-dev.4 <0.71.0", "@azure-tools/typespec-azure-core": ">=0.70.0 <1.0.0", "@azure-tools/typespec-azure-resource-manager": ">=0.70.0 <1.0.0", - "@azure-tools/typespec-azure-rulesets": ">=0.70.0 <1.0.0", - "@azure-tools/typespec-client-generator-core": ">=0.70.0 <1.0.0", - "@typespec/compiler": "^1.14.0", + "@azure-tools/typespec-azure-rulesets": ">=0.70.0 <1.0.0 || >=0.71.0-dev.5 <0.71.0", + "@azure-tools/typespec-client-generator-core": ">=0.70.0 <1.0.0 || >=0.71.0-dev.11 <0.71.0", + "@typespec/compiler": "^1.14.0 || >=1.15.0-dev.17 <1.15.0", "@typespec/events": ">=0.84.0 <1.0.0", "@typespec/http": "^1.14.0", "@typespec/openapi": "^1.14.0", @@ -89,9 +89,9 @@ } }, "node_modules/@azure-tools/typespec-autorest": { - "version": "0.70.0", - "resolved": "https://registry.npmjs.org/@azure-tools/typespec-autorest/-/typespec-autorest-0.70.0.tgz", - "integrity": "sha512-OaxLkgMcuOXAbaqTNpezmFF24jtkiIH1+2PBwAeRo3ZG7C1r7Hf8xZwCK6KVtBEgMbqnrd5eCqxsPl1zy3y9/Q==", + "version": "0.71.0-dev.4", + "resolved": "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-js/npm/registry/@azure-tools/typespec-autorest/-/typespec-autorest-0.71.0-dev.4.tgz", + "integrity": "sha1-p920uaoYRzfFVIeEUDW6+uUri1w=", "dev": true, "license": "MIT", "dependencies": { @@ -101,9 +101,9 @@ "node": ">=22.0.0" }, "peerDependencies": { - "@azure-tools/typespec-azure-core": "^0.70.0", - "@azure-tools/typespec-azure-resource-manager": "^0.70.0", - "@azure-tools/typespec-client-generator-core": "^0.70.0", + "@azure-tools/typespec-azure-core": "^0.70.0 || >= 0.71.0-dev.3", + "@azure-tools/typespec-azure-resource-manager": "^0.70.0 || >= 0.71.0-dev.10", + "@azure-tools/typespec-client-generator-core": "^0.70.0 || >= 0.71.0-dev.11", "@typespec/compiler": "^1.14.0", "@typespec/http": "^1.14.0", "@typespec/openapi": "^1.14.0", @@ -119,8 +119,8 @@ }, "node_modules/@azure-tools/typespec-azure-core": { "version": "0.70.0", - "resolved": "https://registry.npmjs.org/@azure-tools/typespec-azure-core/-/typespec-azure-core-0.70.0.tgz", - "integrity": "sha512-8MojHWRtTLKycJJ98IMoXX/5b9tTo3F0d3Iu20OKoCsORnSDG2NfjOWHJVW63oxA2t8VTlqC6J8BDcnRihygQQ==", + "resolved": "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-js/npm/registry/@azure-tools/typespec-azure-core/-/typespec-azure-core-0.70.0.tgz", + "integrity": "sha1-k3VYRkt7yNAA1kiBElz71YbZH7A=", "dev": true, "license": "MIT", "engines": { @@ -134,8 +134,8 @@ }, "node_modules/@azure-tools/typespec-azure-resource-manager": { "version": "0.70.0", - "resolved": "https://registry.npmjs.org/@azure-tools/typespec-azure-resource-manager/-/typespec-azure-resource-manager-0.70.0.tgz", - "integrity": "sha512-hVrbbsOhU3EQ2yQTppCqsGQwY/HcVZPOINtFkoUo+PUVBmCFXyqLkTO4jvUbsp/LvJEwoQ8aEA8Y35f7VWT5uw==", + "resolved": "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-js/npm/registry/@azure-tools/typespec-azure-resource-manager/-/typespec-azure-resource-manager-0.70.0.tgz", + "integrity": "sha1-+Skz0cnW1LADE60g/JnqYk0nixg=", "dev": true, "license": "MIT", "dependencies": { @@ -155,25 +155,25 @@ } }, "node_modules/@azure-tools/typespec-azure-rulesets": { - "version": "0.70.0", - "resolved": "https://registry.npmjs.org/@azure-tools/typespec-azure-rulesets/-/typespec-azure-rulesets-0.70.0.tgz", - "integrity": "sha512-Uxxl/18oryDwk2S+aYx6cIqiyjmoMeFDGmjuQ72a+aw6u8mZjgahMxNsY0ShvGLSchjsDqsVGaUlazXGXakVrw==", + "version": "0.71.0-dev.5", + "resolved": "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-js/npm/registry/@azure-tools/typespec-azure-rulesets/-/typespec-azure-rulesets-0.71.0-dev.5.tgz", + "integrity": "sha1-bpzvEq9boKSAhUwBc+EKXvxNOwI=", "dev": true, "license": "MIT", "engines": { "node": ">=22.0.0" }, "peerDependencies": { - "@azure-tools/typespec-azure-core": "^0.70.0", - "@azure-tools/typespec-azure-resource-manager": "^0.70.0", - "@azure-tools/typespec-client-generator-core": "^0.70.0", + "@azure-tools/typespec-azure-core": "^0.70.0 || >= 0.71.0-dev.4", + "@azure-tools/typespec-azure-resource-manager": "^0.70.0 || >= 0.71.0-dev.11", + "@azure-tools/typespec-client-generator-core": "^0.70.0 || >= 0.71.0-dev.11", "@typespec/compiler": "^1.14.0" } }, "node_modules/@azure-tools/typespec-client-generator-core": { - "version": "0.70.0", - "resolved": "https://registry.npmjs.org/@azure-tools/typespec-client-generator-core/-/typespec-client-generator-core-0.70.0.tgz", - "integrity": "sha512-8yxOYJfID3wp3FLQYNIa3kbmR5YLWjYtpB+i4u66quHTTQWWANHV1/o9f8xymAf+8fO9jbLo5tw1JerumxISWg==", + "version": "0.71.0-dev.11", + "resolved": "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-js/npm/registry/@azure-tools/typespec-client-generator-core/-/typespec-client-generator-core-0.71.0-dev.11.tgz", + "integrity": "sha1-uBy0avXoMit1nkolVuXXLg2gbmw=", "dev": true, "license": "MIT", "dependencies": { @@ -185,7 +185,7 @@ "node": ">=22.0.0" }, "peerDependencies": { - "@azure-tools/typespec-azure-core": "^0.70.0", + "@azure-tools/typespec-azure-core": "^0.70.0 || >= 0.71.0-dev.3", "@typespec/compiler": "^1.14.0", "@typespec/events": "^0.84.0", "@typespec/http": "^1.14.0", @@ -999,8 +999,8 @@ }, "node_modules/@eslint/config-array": { "version": "0.21.2", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", - "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", + "resolved": "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-js/npm/registry/@eslint/config-array/-/config-array-0.21.2.tgz", + "integrity": "sha1-8p4iBXrVMWzyODbO6aNMgf/8t+Y=", "dev": true, "license": "Apache-2.0", "peer": true, @@ -1014,9 +1014,9 @@ } }, "node_modules/@eslint/config-array/node_modules/brace-expansion": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", - "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", + "version": "1.1.18", + "resolved": "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-js/npm/registry/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha1-POdNiYhRNr4VNTQfjD1EJcKaXKs=", "dev": true, "license": "MIT", "peer": true, @@ -1027,8 +1027,8 @@ }, "node_modules/@eslint/config-array/node_modules/minimatch": { "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "resolved": "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-js/npm/registry/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha1-WAyI+NVEXyvWqo88re+g3nn71p4=", "dev": true, "license": "ISC", "peer": true, @@ -1041,8 +1041,8 @@ }, "node_modules/@eslint/config-helpers": { "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", - "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "resolved": "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-js/npm/registry/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha1-G9AGzut+LlWyt3OrMY0wDhpmrto=", "dev": true, "license": "Apache-2.0", "peer": true, @@ -1055,8 +1055,8 @@ }, "node_modules/@eslint/core": { "version": "0.17.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", - "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "resolved": "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-js/npm/registry/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha1-dyJYIEE9lhdQnak0IZCiAZ54dhw=", "dev": true, "license": "Apache-2.0", "peer": true, @@ -1068,9 +1068,9 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "3.3.5", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", - "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", + "version": "3.3.6", + "resolved": "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-js/npm/registry/@eslint/eslintrc/-/eslintrc-3.3.6.tgz", + "integrity": "sha1-0iv9azp9jh8sCy8ubeERtT7G4T4=", "dev": true, "license": "MIT", "peer": true, @@ -1081,7 +1081,7 @@ "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", - "js-yaml": "^4.1.1", + "js-yaml": "^4.3.0", "minimatch": "^3.1.5", "strip-json-comments": "^3.1.1" }, @@ -1093,9 +1093,9 @@ } }, "node_modules/@eslint/eslintrc/node_modules/ajv": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", - "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "version": "6.15.0", + "resolved": "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-js/npm/registry/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha1-B+mCx0YmFnqnoklcU4F4ktcTlJI=", "dev": true, "license": "MIT", "peer": true, @@ -1111,9 +1111,9 @@ } }, "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", - "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", + "version": "1.1.18", + "resolved": "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-js/npm/registry/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha1-POdNiYhRNr4VNTQfjD1EJcKaXKs=", "dev": true, "license": "MIT", "peer": true, @@ -1124,16 +1124,16 @@ }, "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "resolved": "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-js/npm/registry/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha1-afaofZUTq4u4/mO9sJecRI5oRmA=", "dev": true, "license": "MIT", "peer": true }, "node_modules/@eslint/eslintrc/node_modules/minimatch": { "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "resolved": "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-js/npm/registry/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha1-WAyI+NVEXyvWqo88re+g3nn71p4=", "dev": true, "license": "ISC", "peer": true, @@ -1145,9 +1145,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.39.4", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", - "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", + "version": "9.39.5", + "resolved": "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-js/npm/registry/@eslint/js/-/js-9.39.5.tgz", + "integrity": "sha1-by+8/3VQDSKdU14KlJrhNHLIR4c=", "dev": true, "license": "MIT", "peer": true, @@ -1160,8 +1160,8 @@ }, "node_modules/@eslint/object-schema": { "version": "2.1.7", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", - "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "resolved": "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-js/npm/registry/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha1-biEmoTR+hqTe34cG7Gf/jhB+u60=", "dev": true, "license": "Apache-2.0", "peer": true, @@ -1171,8 +1171,8 @@ }, "node_modules/@eslint/plugin-kit": { "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", - "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "resolved": "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-js/npm/registry/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha1-l3nj/Zt+4zVxpXQ1z0M1oXlKbLI=", "dev": true, "license": "Apache-2.0", "peer": true, @@ -1185,48 +1185,52 @@ } }, "node_modules/@humanfs/core": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "version": "0.19.2", + "resolved": "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-js/npm/registry/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha1-qCcsoDsqz0kmcCIrIyC2xCG/3mA=", "dev": true, + "license": "Apache-2.0", "peer": true, + "dependencies": { + "@humanfs/types": "^0.15.0" + }, "engines": { "node": ">=18.18.0" } }, "node_modules/@humanfs/node": { - "version": "0.16.6", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", - "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "version": "0.16.8", + "resolved": "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-js/npm/registry/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha1-j4AMzME/T4zTEW4tnAqUk52j4+0=", "dev": true, + "license": "Apache-2.0", "peer": true, "dependencies": { - "@humanfs/core": "^0.19.1", - "@humanwhocodes/retry": "^0.3.0" + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" }, "engines": { "node": ">=18.18.0" } }, - "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", - "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-js/npm/registry/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha1-8qCfYgEjkLK/8/xvskjd7IwJoJA=", "dev": true, + "license": "Apache-2.0", "peer": true, "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" + "node": ">=18.18.0" } }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "resolved": "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-js/npm/registry/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha1-r1smkaIrRL6EewyoFkHF+2rQFyw=", "dev": true, + "license": "Apache-2.0", "peer": true, "engines": { "node": ">=12.22" @@ -1237,10 +1241,11 @@ } }, "node_modules/@humanwhocodes/retry": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.2.tgz", - "integrity": "sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==", + "version": "0.4.3", + "resolved": "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-js/npm/registry/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha1-wrnS43TuYsWG062+qHGZsdenpro=", "dev": true, + "license": "Apache-2.0", "peer": true, "engines": { "node": ">=18.18" @@ -2061,8 +2066,8 @@ }, "node_modules/@types/json-schema": { "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "resolved": "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-js/npm/registry/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha1-WWoXRyM2lNUPatinhp/Lb1bPWEE=", "dev": true, "license": "MIT", "peer": true @@ -2327,9 +2332,9 @@ } }, "node_modules/@typespec/compiler": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@typespec/compiler/-/compiler-1.14.0.tgz", - "integrity": "sha512-RRN0LGVDlonG/IbB2b4mvRjdCo6LywwB9/J8lOp6UaH7vtaFnKe5FL+rpxhof4rXx/zI/4OWnQO6c01bTCz4/Q==", + "version": "1.15.0-dev.17", + "resolved": "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-js/npm/registry/@typespec/compiler/-/compiler-1.15.0-dev.17.tgz", + "integrity": "sha1-hrlL0q3kmaR0fWNddCfI7LA2BCY=", "dev": true, "license": "MIT", "dependencies": { @@ -2341,7 +2346,7 @@ "is-unicode-supported": "^2.1.0", "mustache": "^4.2.0", "picocolors": "^1.1.1", - "prettier": "^3.8.1", + "prettier": "^3.9.5", "semver": "^7.7.4", "tar": "^7.5.13", "temporal-polyfill": "^1.0.1", @@ -2441,8 +2446,8 @@ }, "node_modules/@typespec/events": { "version": "0.84.0", - "resolved": "https://registry.npmjs.org/@typespec/events/-/events-0.84.0.tgz", - "integrity": "sha512-UroDIu6t6Z+cOLyX8I+GJWhSFmYGrp1L93F7ZVt0Ypmj0ndmC9YYa4cpeEyS5PDDIC8u49WfCIwfGegxt4rPVQ==", + "resolved": "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-js/npm/registry/@typespec/events/-/events-0.84.0.tgz", + "integrity": "sha1-U6JW0cqeb0+n5fCjTbaW3DlOYPk=", "dev": true, "license": "MIT", "engines": { @@ -2454,8 +2459,8 @@ }, "node_modules/@typespec/http": { "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@typespec/http/-/http-1.14.0.tgz", - "integrity": "sha512-W+heCzu8K63AVcoX8MachVWaRxSAMFWOI1yBTc2Kq8QHaJeDiLL5JbU8VfTZ4tL/6EoGSdKfIT5ZNRW7oVCzhg==", + "resolved": "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-js/npm/registry/@typespec/http/-/http-1.14.0.tgz", + "integrity": "sha1-La9yB2Ny8FhnXSBbst9wYQBiOq4=", "dev": true, "license": "MIT", "engines": { @@ -2472,30 +2477,32 @@ } }, "node_modules/@typespec/http-specs": { - "version": "0.1.0-alpha.39", - "resolved": "https://registry.npmjs.org/@typespec/http-specs/-/http-specs-0.1.0-alpha.39.tgz", - "integrity": "sha512-x3ORyF/qLSLt+QDyievKT90STB0tT9J4s0Yu/Isio8zK16U+eRbCFHi69T7FZ94ZrRpPDWJ2u9+dEZv/SCPj+w==", + "version": "0.1.0-alpha.40", + "resolved": "https://ms-feed-17.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@typespec/http-specs/-/http-specs-0.1.0-alpha.40.tgz", + "integrity": "sha1-Jbgrft+poBvuGMqqGR+shaf07Kk=", "dev": true, "license": "MIT", "dependencies": { "@typespec/spec-api": "^0.1.0-alpha.15", - "@typespec/spector": "^0.1.0-alpha.26" + "@typespec/spector": "^0.1.0-alpha.27" }, "engines": { "node": ">=22.0.0" }, "peerDependencies": { "@typespec/compiler": "^1.14.0", + "@typespec/events": "^0.84.0", "@typespec/http": "^1.14.0", "@typespec/rest": "^0.84.0", + "@typespec/sse": "^0.84.0", "@typespec/versioning": "^0.84.0", "@typespec/xml": "^0.84.0" } }, "node_modules/@typespec/openapi": { "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@typespec/openapi/-/openapi-1.14.0.tgz", - "integrity": "sha512-KL7kImPhCXRmxpHVt1k7TWaa4bb3NbSeUx2rxyxeq7lYZFllI6/NYRCTOI/5JOrbElWmmSxrajU9K9IAKI6PkQ==", + "resolved": "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-js/npm/registry/@typespec/openapi/-/openapi-1.14.0.tgz", + "integrity": "sha1-uwjWOV3VwWP59UXlR2xVUuzyCGc=", "dev": true, "license": "MIT", "engines": { @@ -2508,8 +2515,8 @@ }, "node_modules/@typespec/rest": { "version": "0.84.0", - "resolved": "https://registry.npmjs.org/@typespec/rest/-/rest-0.84.0.tgz", - "integrity": "sha512-9s5dDfRoHRPdtbVvkBasUx/RnMvwWMTuXRieSQDEji4gWGgxVu4Zt4MiEEKSfQrkMr3Aw0QjRCSxBxjMCHIOmA==", + "resolved": "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-js/npm/registry/@typespec/rest/-/rest-0.84.0.tgz", + "integrity": "sha1-kMLB39G8geZbiA3EEdQBaqteuuA=", "dev": true, "license": "MIT", "engines": { @@ -2582,9 +2589,9 @@ "license": "MIT" }, "node_modules/@typespec/spector": { - "version": "0.1.0-alpha.26", - "resolved": "https://registry.npmjs.org/@typespec/spector/-/spector-0.1.0-alpha.26.tgz", - "integrity": "sha512-WtaWIJE+Xh80G11Bi/3zC1GIrK4EVYBSCnkmSvm9bEYcBMH8QuryPDbUeTXYUyozcCrMYXaKnBboHN91IEi8ug==", + "version": "0.1.0-alpha.27", + "resolved": "https://ms-feed-12.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@typespec/spector/-/spector-0.1.0-alpha.27.tgz", + "integrity": "sha1-aA4cucLEbAIR6ZLH0llZuaVOMMY=", "dev": true, "license": "MIT", "dependencies": { @@ -2682,8 +2689,8 @@ }, "node_modules/@typespec/sse": { "version": "0.84.0", - "resolved": "https://registry.npmjs.org/@typespec/sse/-/sse-0.84.0.tgz", - "integrity": "sha512-9joNgVisRCWDFfV1d79iTAuR1W/6r+AKJrKUfcjsaTrq5A8OWW3v5TTsfxbHAZArn7n2WxQkqhNGgNyc8LjEng==", + "resolved": "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-js/npm/registry/@typespec/sse/-/sse-0.84.0.tgz", + "integrity": "sha1-dkP/3P+tvI4Q2SV3oRz/F2JMVXo=", "dev": true, "license": "MIT", "engines": { @@ -2698,8 +2705,8 @@ }, "node_modules/@typespec/streams": { "version": "0.84.0", - "resolved": "https://registry.npmjs.org/@typespec/streams/-/streams-0.84.0.tgz", - "integrity": "sha512-SDneR8+zY+ueOpzg9yJtttfDe/ikB99JgddZSXKPwiDPlAIEeEvI8auipcYfB58EEOB21h8Oq0tEm8HqiAAWdQ==", + "resolved": "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-js/npm/registry/@typespec/streams/-/streams-0.84.0.tgz", + "integrity": "sha1-Bm3D76chBKlcou2jj913xFfTBI4=", "dev": true, "license": "MIT", "engines": { @@ -2726,8 +2733,8 @@ }, "node_modules/@typespec/versioning": { "version": "0.84.0", - "resolved": "https://registry.npmjs.org/@typespec/versioning/-/versioning-0.84.0.tgz", - "integrity": "sha512-ZoDasTDj4z0mgFK+0cJL2+7DduCaTjvICHL2nQ/RBWc7nLgObaIYCjvXLno8WneDXnpxCAr7larN4/nlHEv9fg==", + "resolved": "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-js/npm/registry/@typespec/versioning/-/versioning-0.84.0.tgz", + "integrity": "sha1-YS06C7uMMWXKp7vwuy8qV8kjr38=", "dev": true, "license": "MIT", "engines": { @@ -2739,8 +2746,8 @@ }, "node_modules/@typespec/xml": { "version": "0.84.0", - "resolved": "https://registry.npmjs.org/@typespec/xml/-/xml-0.84.0.tgz", - "integrity": "sha512-3x0spgIrr4u3azkYaOxrlumtjoqPiUnJ/G5RwGBmUCAeE5F413MHf/AeIkmZ2ULT1gY3myabfZp8bOijTbMk7A==", + "resolved": "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-js/npm/registry/@typespec/xml/-/xml-0.84.0.tgz", + "integrity": "sha1-aP99jZ3+wHS7f9mMJbEUT4Py7+g=", "dev": true, "license": "MIT", "engines": { @@ -2878,9 +2885,9 @@ } }, "node_modules/acorn": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", - "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "version": "8.18.0", + "resolved": "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-js/npm/registry/acorn/-/acorn-8.18.0.tgz", + "integrity": "sha1-T68BstbTJr/u2XrqH1IiC19MGUA=", "dev": true, "license": "MIT", "peer": true, @@ -2893,8 +2900,8 @@ }, "node_modules/acorn-jsx": { "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "resolved": "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-js/npm/registry/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha1-ftW7VZCLOy8bxVxq8WU7rafweTc=", "dev": true, "license": "MIT", "peer": true, @@ -3216,8 +3223,8 @@ }, "node_modules/callsites": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "resolved": "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-js/npm/registry/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha1-s2MKvYlDQy9Us/BRkjjjPNffL3M=", "dev": true, "license": "MIT", "peer": true, @@ -3235,6 +3242,24 @@ "node": ">=18" } }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-js/npm/registry/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha1-qsTit3NKdAhnrrFr8CqtVWoeegE=", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/change-case": { "version": "5.4.4", "resolved": "https://registry.npmjs.org/change-case/-/change-case-5.4.4.tgz", @@ -3342,8 +3367,8 @@ }, "node_modules/concat-map": { "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "resolved": "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-js/npm/registry/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true, "license": "MIT", "peer": true @@ -3448,9 +3473,10 @@ }, "node_modules/deep-is": { "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "resolved": "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-js/npm/registry/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha1-pvLc5hL63S7x9Rm3NVHxfoUZmDE=", "dev": true, + "license": "MIT", "peer": true }, "node_modules/default-browser": { @@ -3685,9 +3711,10 @@ }, "node_modules/escape-string-regexp": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "resolved": "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-js/npm/registry/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha1-FLqDpdNz49MR5a/KKc9b+tllvzQ=", "dev": true, + "license": "MIT", "peer": true, "engines": { "node": ">=10" @@ -3697,9 +3724,9 @@ } }, "node_modules/eslint": { - "version": "9.39.4", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz", - "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", + "version": "9.39.5", + "resolved": "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-js/npm/registry/eslint/-/eslint-9.39.5.tgz", + "integrity": "sha1-Kk48iw91MZbvrpQ8j/qocw/Go/o=", "dev": true, "license": "MIT", "peer": true, @@ -3709,8 +3736,8 @@ "@eslint/config-array": "^0.21.2", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", - "@eslint/eslintrc": "^3.3.5", - "@eslint/js": "9.39.4", + "@eslint/eslintrc": "^3.3.6", + "@eslint/js": "9.39.5", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", @@ -3759,8 +3786,8 @@ }, "node_modules/eslint-scope": { "version": "8.4.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", - "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "resolved": "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-js/npm/registry/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha1-iOZGogf61hQ2/6OetQUUcgBlXII=", "dev": true, "license": "BSD-2-Clause", "peer": true, @@ -3789,9 +3816,9 @@ } }, "node_modules/eslint/node_modules/ajv": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", - "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "version": "6.15.0", + "resolved": "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-js/npm/registry/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha1-B+mCx0YmFnqnoklcU4F4ktcTlJI=", "dev": true, "license": "MIT", "peer": true, @@ -3807,9 +3834,9 @@ } }, "node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", - "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", + "version": "1.1.18", + "resolved": "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-js/npm/registry/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha1-POdNiYhRNr4VNTQfjD1EJcKaXKs=", "dev": true, "license": "MIT", "peer": true, @@ -3818,35 +3845,18 @@ "concat-map": "0.0.1" } }, - "node_modules/eslint/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "peer": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/eslint/node_modules/json-schema-traverse": { "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "resolved": "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-js/npm/registry/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha1-afaofZUTq4u4/mO9sJecRI5oRmA=", "dev": true, "license": "MIT", "peer": true }, "node_modules/eslint/node_modules/minimatch": { "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "resolved": "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-js/npm/registry/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha1-WAyI+NVEXyvWqo88re+g3nn71p4=", "dev": true, "license": "ISC", "peer": true, @@ -3859,8 +3869,8 @@ }, "node_modules/espree": { "version": "10.4.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", - "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "resolved": "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-js/npm/registry/espree/-/espree-10.4.0.tgz", + "integrity": "sha1-1U9JSdRikAWh+haNk3w/8ffiqDc=", "dev": true, "license": "BSD-2-Clause", "peer": true, @@ -3877,10 +3887,11 @@ } }, "node_modules/esquery": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", - "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "version": "1.7.0", + "resolved": "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-js/npm/registry/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha1-CNBI8mHw3e21uulfRoCUY9nJSW0=", "dev": true, + "license": "BSD-3-Clause", "peer": true, "dependencies": { "estraverse": "^5.1.0" @@ -3891,8 +3902,8 @@ }, "node_modules/esrecurse": { "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "resolved": "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-js/npm/registry/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha1-eteWTWeauyi+5yzsY3WLHF0smSE=", "dev": true, "license": "BSD-2-Clause", "peer": true, @@ -3905,9 +3916,10 @@ }, "node_modules/estraverse": { "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "resolved": "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-js/npm/registry/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha1-LupSkHAvJquP5TcDcP+GyWXSESM=", "dev": true, + "license": "BSD-2-Clause", "peer": true, "engines": { "node": ">=4.0" @@ -3925,9 +3937,10 @@ }, "node_modules/esutils": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "resolved": "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-js/npm/registry/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha1-dNLrTeC42hKTcRkQ1Qd1ubcQ72Q=", "dev": true, + "license": "BSD-2-Clause", "peer": true, "engines": { "node": ">=0.10.0" @@ -4015,17 +4028,18 @@ }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "resolved": "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-js/npm/registry/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha1-h0v2nG9ATCtdmcSBNBOZ/VWJJjM=", "dev": true, "license": "MIT", "peer": true }, "node_modules/fast-levenshtein": { "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "resolved": "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-js/npm/registry/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", "dev": true, + "license": "MIT", "peer": true }, "node_modules/fast-string-truncated-width": { @@ -4115,9 +4129,10 @@ }, "node_modules/file-entry-cache": { "version": "8.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", - "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "resolved": "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-js/npm/registry/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha1-d4e93PETG/+5JjbGlFe7wO3W2B8=", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "flat-cache": "^4.0.0" @@ -4179,9 +4194,10 @@ }, "node_modules/flat-cache": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", - "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "resolved": "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-js/npm/registry/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha1-Ds45/LFO4BL0sEEL0z3ZwfAREnw=", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "flatted": "^3.2.9", @@ -4192,9 +4208,9 @@ } }, "node_modules/flatted": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", - "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "version": "3.4.4", + "resolved": "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-js/npm/registry/flatted/-/flatted-3.4.4.tgz", + "integrity": "sha1-ruyipQYwPwzuYcWebJ8qiNLyn8Y=", "dev": true, "license": "ISC", "peer": true @@ -4350,9 +4366,10 @@ }, "node_modules/glob-parent": { "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "resolved": "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-js/npm/registry/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha1-bSN9mQg5UMeSkPJMdkKj3poo+eM=", "dev": true, + "license": "ISC", "peer": true, "dependencies": { "is-glob": "^4.0.3" @@ -4402,8 +4419,8 @@ }, "node_modules/globals": { "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "resolved": "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-js/npm/registry/globals/-/globals-14.0.0.tgz", + "integrity": "sha1-iY10E8Kbq89rr+Vvyt3thYrack4=", "dev": true, "license": "MIT", "peer": true, @@ -4536,8 +4553,8 @@ }, "node_modules/ignore": { "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "resolved": "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-js/npm/registry/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha1-PNQOcp82Q/2HywTlC/DrcivFlvU=", "dev": true, "license": "MIT", "peer": true, @@ -4547,8 +4564,8 @@ }, "node_modules/import-fresh": { "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "resolved": "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-js/npm/registry/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha1-nOy1ZQPAraHydB271lRuSxO1fM8=", "dev": true, "license": "MIT", "peer": true, @@ -4565,8 +4582,8 @@ }, "node_modules/imurmurhash": { "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "resolved": "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-js/npm/registry/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", "dev": true, "license": "MIT", "peer": true, @@ -4609,9 +4626,10 @@ }, "node_modules/is-extglob": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "resolved": "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-js/npm/registry/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", "dev": true, + "license": "MIT", "peer": true, "engines": { "node": ">=0.10.0" @@ -4628,9 +4646,10 @@ }, "node_modules/is-glob": { "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "resolved": "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-js/npm/registry/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha1-ZPYeQsu7LuwgcanawLKLoeZdUIQ=", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "is-extglob": "^2.1.1" @@ -4779,9 +4798,9 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz", - "integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==", + "version": "4.3.0", + "resolved": "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-js/npm/registry/js-yaml/-/js-yaml-4.3.0.tgz", + "integrity": "sha1-0ZAFcqf3zwtfVAyDZz5gutNDZZI=", "funding": [ { "type": "github", @@ -4802,9 +4821,10 @@ }, "node_modules/json-buffer": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "resolved": "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-js/npm/registry/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha1-kziAKjDTtmBfvgYT4JQAjKjAWhM=", "dev": true, + "license": "MIT", "peer": true }, "node_modules/json-schema-traverse": { @@ -4816,9 +4836,10 @@ }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "resolved": "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-js/npm/registry/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", "dev": true, + "license": "MIT", "peer": true }, "node_modules/jsonwebtoken": { @@ -4869,9 +4890,10 @@ }, "node_modules/keyv": { "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "resolved": "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-js/npm/registry/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha1-qHmpnilFL5QkOfKkBeOvizHU3pM=", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "json-buffer": "3.0.1" @@ -4879,9 +4901,10 @@ }, "node_modules/levn": { "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "resolved": "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-js/npm/registry/levn/-/levn-0.4.1.tgz", + "integrity": "sha1-rkViwAdHO5MqYgDUAyaN0v/8at4=", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "prelude-ls": "^1.2.1", @@ -5211,9 +5234,10 @@ }, "node_modules/lodash.merge": { "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "resolved": "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-js/npm/registry/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha1-VYqlO0O2YeGSWgr9+japoQhf5Xo=", "dev": true, + "license": "MIT", "peer": true }, "node_modules/lodash.once": { @@ -5625,9 +5649,10 @@ }, "node_modules/optionator": { "version": "0.9.4", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "resolved": "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-js/npm/registry/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha1-fqHBpdkddk+yghOciP4R4YKjpzQ=", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "deep-is": "^0.1.3", @@ -5679,8 +5704,8 @@ }, "node_modules/parent-module": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "resolved": "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-js/npm/registry/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha1-aR0nCeeMefrjoVZiJFLQB2LKqqI=", "dev": true, "license": "MIT", "peer": true, @@ -5829,9 +5854,10 @@ }, "node_modules/prelude-ls": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "resolved": "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-js/npm/registry/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha1-3rxkidem5rDnYRiIzsiAM30xY5Y=", "dev": true, + "license": "MIT", "peer": true, "engines": { "node": ">= 0.8.0" @@ -5869,8 +5895,8 @@ }, "node_modules/punycode": { "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "resolved": "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-js/npm/registry/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha1-AnQi4vrsCyXhVJw+G9gwm5EztuU=", "dev": true, "license": "MIT", "peer": true, @@ -5972,8 +5998,8 @@ }, "node_modules/resolve-from": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "resolved": "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-js/npm/registry/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha1-SrzYUq0y3Xuqv+m0DgCjbbXzkuY=", "dev": true, "license": "MIT", "peer": true, @@ -6452,8 +6478,8 @@ }, "node_modules/strip-json-comments": { "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "resolved": "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-js/npm/registry/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha1-MfEoGzgyYwQ0gxwxDAHMzajL4AY=", "dev": true, "license": "MIT", "peer": true, @@ -6744,9 +6770,10 @@ }, "node_modules/type-check": { "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "resolved": "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-js/npm/registry/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha1-B7ggO/pwVsBlcFDjzNLDdzC6uPE=", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "prelude-ls": "^1.2.1" @@ -6852,8 +6879,8 @@ }, "node_modules/uri-js": { "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "resolved": "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-js/npm/registry/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha1-mxpSWVIlhZ5V9mnZKPiMbFfyp34=", "dev": true, "license": "BSD-2-Clause", "peer": true, @@ -7158,9 +7185,10 @@ }, "node_modules/word-wrap": { "version": "1.2.5", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "resolved": "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-js/npm/registry/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha1-0sRcbdT7zmIaZvE2y+Mor9BBCzQ=", "dev": true, + "license": "MIT", "peer": true, "engines": { "node": ">=0.10.0" diff --git a/packages/http-client-python/package.json b/packages/http-client-python/package.json index d3d837fcad5..1074cb3e58d 100644 --- a/packages/http-client-python/package.json +++ b/packages/http-client-python/package.json @@ -66,12 +66,12 @@ "emitter" ], "peerDependencies": { - "@azure-tools/typespec-autorest": ">=0.70.0 <1.0.0", + "@azure-tools/typespec-autorest": ">=0.70.0 <1.0.0 || >=0.71.0-dev.4 <0.71.0", "@azure-tools/typespec-azure-core": ">=0.70.0 <1.0.0", "@azure-tools/typespec-azure-resource-manager": ">=0.70.0 <1.0.0", - "@azure-tools/typespec-azure-rulesets": ">=0.70.0 <1.0.0", - "@azure-tools/typespec-client-generator-core": ">=0.70.0 <1.0.0", - "@typespec/compiler": "^1.14.0", + "@azure-tools/typespec-azure-rulesets": ">=0.70.0 <1.0.0 || >=0.71.0-dev.5 <0.71.0", + "@azure-tools/typespec-client-generator-core": ">=0.70.0 <1.0.0 || >=0.71.0-dev.11 <0.71.0", + "@typespec/compiler": "^1.14.0 || >=1.15.0-dev.17 <1.15.0", "@typespec/http": "^1.14.0", "@typespec/openapi": "^1.14.0", "@typespec/rest": ">=0.84.0 <1.0.0", @@ -104,24 +104,24 @@ "tsx": "^4.21.0" }, "devDependencies": { - "@azure-tools/typespec-autorest": "~0.70.0", + "@azure-tools/typespec-autorest": "0.71.0-dev.4", "@azure-tools/typespec-azure-core": "~0.70.0", "@azure-tools/typespec-azure-resource-manager": "~0.70.0", - "@azure-tools/typespec-azure-rulesets": "~0.70.0", - "@azure-tools/typespec-client-generator-core": "~0.70.0", + "@azure-tools/typespec-azure-rulesets": "0.71.0-dev.5", + "@azure-tools/typespec-client-generator-core": "0.71.0-dev.11", "@azure-tools/azure-http-specs": "0.1.0-alpha.43", - "@typespec/compiler": "^1.14.0", + "@typespec/compiler": "1.15.0-dev.17", "@typespec/http": "^1.14.0", "@typespec/openapi": "^1.14.0", "@typespec/rest": "~0.84.0", "@typespec/versioning": "~0.84.0", "@typespec/events": "~0.84.0", - "@typespec/spector": "0.1.0-alpha.26", + "@typespec/spector": "0.1.0-alpha.27", "@typespec/spec-api": "0.1.0-alpha.15", "@typespec/sse": "~0.84.0", "@typespec/streams": "~0.84.0", "@typespec/xml": "~0.84.0", - "@typespec/http-specs": "0.1.0-alpha.39", + "@typespec/http-specs": "0.1.0-alpha.40", "@types/js-yaml": "~4.0.5", "@types/node": "~25.0.2", "@types/semver": "7.5.8", @@ -132,5 +132,8 @@ "typescript-eslint": "^8.49.0", "vitest": "^4.0.15", "prettier": "^3.9.5" + }, + "overrides": { + "@typespec/compiler": "$@typespec/compiler" } } diff --git a/packages/http-client-python/tests/mock_api/azure/asynctests/test_streaming_async.py b/packages/http-client-python/tests/mock_api/azure/asynctests/test_streaming_async.py new file mode 100644 index 00000000000..c966e4a3a2e --- /dev/null +++ b/packages/http-client-python/tests/mock_api/azure/asynctests/test_streaming_async.py @@ -0,0 +1,53 @@ +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# -------------------------------------------------------------------------- +import pytest + +from streaming.jsonl.aio import JsonlClient +from streaming.jsonl.basic.models import Info as JsonlInfo +from streaming.sse.aio import SseClient +from streaming.sse.named.models import ResponseCreated, ResponseDelta +from streaming.sse.unnamed.models import Info as SseInfo + + +@pytest.mark.asyncio +async def test_jsonl_response_yields_models_and_ignores_stream_kwarg(): + async with JsonlClient(endpoint="http://localhost:3000") as client: + items = [item async for item in await client.basic.receive(stream=False)] + + assert all(isinstance(item, JsonlInfo) for item in items) + assert [item.desc for item in items] == ["one", "two", "three"] + + +@pytest.mark.asyncio +async def test_unnamed_sse_response_yields_models(): + async with SseClient(endpoint="http://localhost:3000") as client: + items = [item async for item in await client.unnamed.receive()] + + assert all(isinstance(item, SseInfo) for item in items) + assert [item.desc for item in items] == ["one", "two", "three"] + + +@pytest.mark.asyncio +async def test_named_sse_dispatches_models_stops_at_terminal_and_calls_cls_once(): + calls = [] + + def cls(pipeline_response, generator, headers): + calls.append((pipeline_response, generator, headers)) + return generator + + async with SseClient(endpoint="http://localhost:3000") as client: + generator = await client.named.receive(cls=cls) + items = [item async for item in generator] + + assert len(calls) == 1 + assert calls[0][1] is generator + assert [type(item) for item in items] == [ + ResponseCreated, + ResponseDelta, + ResponseDelta, + ] + assert items[0].id == "resp_1" + assert [item.delta for item in items[1:]] == ["Hello", " world"] diff --git a/packages/http-client-python/tests/mock_api/azure/test_streaming.py b/packages/http-client-python/tests/mock_api/azure/test_streaming.py new file mode 100644 index 00000000000..fb3c508f943 --- /dev/null +++ b/packages/http-client-python/tests/mock_api/azure/test_streaming.py @@ -0,0 +1,62 @@ +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# -------------------------------------------------------------------------- +from streaming.jsonl import JsonlClient +from streaming.jsonl.basic.models import Info as JsonlInfo +from streaming.sse import SseClient +from streaming.sse.named.models import ResponseCreated, ResponseDelta +from streaming.sse.retrieve.models import FinalResult, PartialResult +from streaming.sse.unnamed.models import Info as SseInfo + + +def test_jsonl_response_yields_models_and_ignores_stream_kwarg(): + with JsonlClient(endpoint="http://localhost:3000") as client: + items = list(client.basic.receive(stream=False)) + + assert all(isinstance(item, JsonlInfo) for item in items) + assert [item.desc for item in items] == ["one", "two", "three"] + + +def test_unnamed_sse_response_yields_models(): + with SseClient(endpoint="http://localhost:3000") as client: + items = list(client.unnamed.receive()) + + assert all(isinstance(item, SseInfo) for item in items) + assert [item.desc for item in items] == ["one", "two", "three"] + + +def test_named_sse_dispatches_models_stops_at_terminal_and_calls_cls_once(): + calls = [] + + def cls(pipeline_response, generator, headers): + calls.append((pipeline_response, generator, headers)) + return generator + + with SseClient(endpoint="http://localhost:3000") as client: + generator = client.named.receive(cls=cls) + items = list(generator) + + assert len(calls) == 1 + assert calls[0][1] is generator + assert [type(item) for item in items] == [ + ResponseCreated, + ResponseDelta, + ResponseDelta, + ] + assert items[0].id == "resp_1" + assert [item.delta for item in items[1:]] == ["Hello", " world"] + + +def test_request_body_operation_can_return_structured_sse(): + with SseClient(endpoint="http://localhost:3000") as client: + items = list(client.retrieve.stream({"query": "what is typespec?"})) + + assert [type(item) for item in items] == [ + PartialResult, + PartialResult, + FinalResult, + ] + assert [item.text for item in items[:2]] == ["partial one", "partial two"] + assert items[2].references == ["doc1", "doc2"] diff --git a/packages/http-client-python/tests/mock_api/shared/asynctests/test_streaming_jsonl_async.py b/packages/http-client-python/tests/mock_api/shared/asynctests/test_streaming_jsonl_async.py index 74e05cebd14..20288f960ac 100644 --- a/packages/http-client-python/tests/mock_api/shared/asynctests/test_streaming_jsonl_async.py +++ b/packages/http-client-python/tests/mock_api/shared/asynctests/test_streaming_jsonl_async.py @@ -21,8 +21,3 @@ async def client(): @pytest.mark.asyncio async def test_basic_send(client: JsonlClient): await client.basic.send(JSONL) - - -@pytest.mark.asyncio -async def test_basic_recv(client: JsonlClient): - assert b"".join([d async for d in (await client.basic.receive())]) == JSONL diff --git a/packages/http-client-python/tests/mock_api/shared/test_streaming_jsonl.py b/packages/http-client-python/tests/mock_api/shared/test_streaming_jsonl.py index 494c17a3493..d035530c054 100644 --- a/packages/http-client-python/tests/mock_api/shared/test_streaming_jsonl.py +++ b/packages/http-client-python/tests/mock_api/shared/test_streaming_jsonl.py @@ -19,7 +19,3 @@ def client(): def test_basic_send(client: JsonlClient): client.basic.send(JSONL) - - -def test_basic_recv(client: JsonlClient): - assert b"".join(client.basic.receive()) == JSONL diff --git a/packages/http-client-python/tests/mock_api/unbranded/asynctests/test_streaming_async.py b/packages/http-client-python/tests/mock_api/unbranded/asynctests/test_streaming_async.py new file mode 100644 index 00000000000..f9e08791cd5 --- /dev/null +++ b/packages/http-client-python/tests/mock_api/unbranded/asynctests/test_streaming_async.py @@ -0,0 +1,26 @@ +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# -------------------------------------------------------------------------- +import pytest + +from streaming.jsonl.aio import JsonlClient +from streaming.sse.aio import SseClient + + +@pytest.mark.asyncio +async def test_jsonl_response_remains_raw_bytes(): + async with JsonlClient(endpoint="http://localhost:3000") as client: + payload = b"".join([chunk async for chunk in await client.basic.receive()]) + + assert payload == b'{"desc": "one"}\n{"desc": "two"}\n{"desc": "three"}' + + +@pytest.mark.asyncio +async def test_sse_response_remains_raw_bytes(): + async with SseClient(endpoint="http://localhost:3000") as client: + payload = b"".join([chunk async for chunk in await client.named.receive()]) + + assert b"event: responseCreated" in payload + assert b"data: [DONE]" in payload diff --git a/packages/http-client-python/tests/mock_api/unbranded/test_streaming.py b/packages/http-client-python/tests/mock_api/unbranded/test_streaming.py new file mode 100644 index 00000000000..5b1d7315a7e --- /dev/null +++ b/packages/http-client-python/tests/mock_api/unbranded/test_streaming.py @@ -0,0 +1,22 @@ +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# -------------------------------------------------------------------------- +from streaming.jsonl import JsonlClient +from streaming.sse import SseClient + + +def test_jsonl_response_remains_raw_bytes(): + with JsonlClient(endpoint="http://localhost:3000") as client: + payload = b"".join(client.basic.receive()) + + assert payload == b'{"desc": "one"}\n{"desc": "two"}\n{"desc": "three"}' + + +def test_sse_response_remains_raw_bytes(): + with SseClient(endpoint="http://localhost:3000") as client: + payload = b"".join(client.named.receive()) + + assert b"event: responseCreated" in payload + assert b"data: [DONE]" in payload diff --git a/packages/http-client-python/tests/unit/test_streaming.py b/packages/http-client-python/tests/unit/test_streaming.py new file mode 100644 index 00000000000..96d311f7a00 --- /dev/null +++ b/packages/http-client-python/tests/unit/test_streaming.py @@ -0,0 +1,332 @@ +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# -------------------------------------------------------------------------- +import asyncio +from contextlib import aclosing, closing +from types import ModuleType, SimpleNamespace + +from jinja2 import Environment, PackageLoader + +from pygen.codegen.models import Response +from pygen.codegen.serializers.builder_serializer import OperationSerializer +from pygen.codegen.serializers.general_serializer import GeneralSerializer + + +def _env() -> Environment: + return Environment( + loader=PackageLoader("pygen.codegen", "templates"), + keep_trailing_newline=True, + trim_blocks=True, + lstrip_blocks=True, + ) + + +def _streaming_module() -> ModuleType: + source = _env().get_template("streaming.py.jinja2").render(code_model=SimpleNamespace(license_header="")) + module = ModuleType("streaming") + exec(compile(source, "streaming.py", "exec"), module.__dict__) + return module + + +class _SyncResponse: + def __init__(self, chunks): + self._chunks = chunks + self.closed = False + + def iter_bytes(self): + yield from self._chunks + + def close(self): + self.closed = True + + +class _AsyncResponse: + def __init__(self, chunks): + self._chunks = chunks + self.closed = False + + async def iter_bytes(self): + for chunk in self._chunks: + yield chunk + + async def close(self): + self.closed = True + + +def test_jsonl_framing_handles_chunked_utf8_blank_and_trailing_lines(): + streaming = _streaming_module() + response = _SyncResponse( + [ + b"\n", + b'{"name":"caf\xc3', + b'\xa9"}\r\n \r\n{"name":"last"}', + ] + ) + + assert list(streaming._iter_jsonl(response)) == [ + '{"name":"caf\u00e9"}', + '{"name":"last"}', + ] + + +def test_sse_framing_handles_bom_crlf_comments_fields_and_eof_dispatch(): + streaming = _streaming_module() + response = _SyncResponse( + [ + b"\xef", + b"\xbb\xbf: keepalive\r", + b"\nevent: connected\rdata: first\r", + b"\ndata: second\rid: 42\rretry: 1500\r\n\r", + b"\nevent: message\ndata: trailing", + ] + ) + + events = list(streaming._iter_sse(response)) + assert [(event.event, event.data, event.id, event.retry) for event in events] == [ + ("connected", "first\nsecond", "42", 1500), + ("message", "trailing", "42", 1500), + ] + + +def test_async_framing_matches_sync_behavior(): + async def run(): + streaming = _streaming_module() + jsonl = _AsyncResponse([b'{"value":1}\n\n{"value":', b"2}"]) + sse = _AsyncResponse([b"data: one\n\n", b"event: two\ndata: two"]) + jsonl_records = [record async for record in streaming._aiter_jsonl(jsonl)] + sse_events = [event async for event in streaming._aiter_sse(sse)] + return jsonl_records, sse_events + + records, events = asyncio.run(run()) + assert records == ['{"value":1}', '{"value":2}'] + assert [(event.event, event.data) for event in events] == [ + ("message", "one"), + ("two", "two"), + ] + + +class _FakeType: + type = "model" + + def __init__(self, name): + self.name = name + + def type_annotation(self, **_kwargs): + return f"_models.{self.name}" + + def docstring_text(self, **_kwargs): + return self.name + + def docstring_type(self, **_kwargs): + return f"~sample.models.{self.name}" + + def imports(self, **_kwargs): + from pygen.codegen.models.imports import FileImport + + return FileImport(SimpleNamespace()) + + +def _response(*, kind="sse", events=None, terminal="[DONE]"): + response = Response( + yaml_data={"statusCodes": [200]}, + code_model=SimpleNamespace(namespace="sample"), + headers=[], + type=_FakeType("Events"), + ) + response.streaming_kind = kind + response.streaming_events = events or [] + response._streaming_terminal_event = terminal + return response + + +def test_structured_response_uses_standard_generator_annotations(): + response = _response(events=[("message", _FakeType("Message"))]) + + assert response.type_annotation(async_mode=False) == ("Generator[_models.Message, None, None]") + assert response.type_annotation(async_mode=True) == "AsyncGenerator[_models.Message, None]" + assert response.docstring_type(async_mode=False) == ("Generator[~sample.models.Message, None, None]") + assert "[DONE]" not in response.type_annotation(async_mode=False) + assert "[DONE]" not in response.docstring_text(async_mode=False) + + +def _serializer(*, async_mode=False): + code_model = SimpleNamespace( + options={"version-tolerant": True}, + namespace="sample", + core_library="azure.core", + is_azure_flavor=True, + get_serialize_namespace=lambda namespace, **_kwargs: namespace, + ) + return OperationSerializer(code_model, async_mode=async_mode, client_namespace="sample") + + +def test_pipeline_forces_stream_and_consumes_conflicting_kwarg(): + serializer = _serializer() + builder = SimpleNamespace( + stream_value=True, + has_structured_stream_response=True, + group_name="operations", + ) + + source = "\n".join(serializer.make_pipeline_call(builder)) + assert "kwargs.pop('stream', None)" in source + assert "_stream = True" in source + assert "stream=_stream" in source + + +def test_sync_adapter_owns_deserialization_cls_dispatch_and_cleanup(): + serializer = _serializer() + response = _response( + events=[ + ("connected", _FakeType("Connected")), + (None, _FakeType("Message")), + ] + ) + source = "\n".join(serializer.handle_structured_stream_response(SimpleNamespace(responses=[response]))) + + assert "def _response_iterator() -> Generator[" in source + assert "for _event in _iter_sse(response):" in source + assert "json.loads(_event.data)" in source + assert "_deserialize(_models.Connected" in source + assert "_deserialize(_models.Message" in source + assert "raise DeserializationError" not in source + assert "finally:" in source + assert "response.close()" in source + assert source.count("cls(") == 1 + assert "return cls(pipeline_response, _response_generator, {})" in source + + +def test_async_adapter_is_strict_without_unnamed_fallback(): + serializer = _serializer(async_mode=True) + response = _response(events=[("connected", _FakeType("Connected"))]) + source = "\n".join(serializer.handle_structured_stream_response(SimpleNamespace(responses=[response]))) + + assert "async def _response_iterator() -> AsyncGenerator[" in source + assert "async for _event in _aiter_sse(response):" in source + assert "raise DeserializationError" in source + assert "finally:" in source + assert "await response.close()" in source + assert source.count("cls(") == 1 + + +def _compile_adapter(*, async_mode, response_model, http_response, serializer): + adapter_source = serializer.handle_structured_stream_response(SimpleNamespace(responses=[response_model])) + function_def = "async def" if async_mode else "def" + source = [f"{function_def} operation(cls=None):"] + source.extend(f" {line}" if line else "" for line in adapter_source) + streaming = _streaming_module() + + class DeserializationError(Exception): + pass + + namespace = { + "AsyncGenerator": __import__("typing").AsyncGenerator, + "DeserializationError": DeserializationError, + "Generator": __import__("typing").Generator, + "_aiter_sse": streaming._aiter_sse, + "_iter_sse": streaming._iter_sse, + "_models": SimpleNamespace(Connected="Connected", Message="Message"), + "_deserialize": lambda model, value: (model, value), + "json": __import__("json"), + "pipeline_response": SimpleNamespace(http_response=http_response), + "response": http_response, + } + exec(compile("\n".join(source), "operation.py", "exec"), namespace) + return namespace["operation"], DeserializationError + + +def test_sync_adapter_calls_cls_once_and_closes_on_partial_consumption(): + response = _response(events=[("connected", _FakeType("Connected"))]) + http_response = _SyncResponse( + [ + b'event: connected\ndata: {"value": 1}\n\n', + b'event: connected\ndata: {"value": 2}\n\n', + ] + ) + operation, _ = _compile_adapter( + async_mode=False, + response_model=response, + http_response=http_response, + serializer=_serializer(), + ) + calls = [] + + def cls(_pipeline_response, generator, _headers): + calls.append(generator) + return generator + + with closing(operation(cls)) as generator: + assert next(generator) == ("Connected", {"value": 1}) + assert calls == [generator] + assert http_response.closed + + +def test_sync_adapter_raises_for_unknown_named_event_and_closes(): + response = _response(events=[("connected", _FakeType("Connected"))]) + http_response = _SyncResponse([b'event: unknown\ndata: {"value": 1}\n\n']) + operation, deserialization_error = _compile_adapter( + async_mode=False, + response_model=response, + http_response=http_response, + serializer=_serializer(), + ) + + try: + list(operation()) + except deserialization_error: + pass + else: + raise AssertionError("Unknown SSE event did not raise DeserializationError") + assert http_response.closed + + +def test_async_adapter_calls_cls_once_and_closes_on_partial_consumption(): + async def run(): + response = _response(events=[("connected", _FakeType("Connected"))]) + http_response = _AsyncResponse( + [ + b'event: connected\ndata: {"value": 1}\n\n', + b'event: connected\ndata: {"value": 2}\n\n', + ] + ) + operation, _ = _compile_adapter( + async_mode=True, + response_model=response, + http_response=http_response, + serializer=_serializer(async_mode=True), + ) + calls = [] + + def cls(_pipeline_response, generator, _headers): + calls.append(generator) + return generator + + generator = await operation(cls) + async with aclosing(generator): + assert await anext(generator) == ("Connected", {"value": 1}) + return calls, generator, http_response + + calls, generator, http_response = asyncio.run(run()) + assert calls == [generator] + assert http_response.closed + + +def test_private_streaming_helper_is_emitted_without_public_exports(): + code_model = SimpleNamespace( + namespace="sample", + license_header="", + core_library="azure.core", + options={"package-version": None}, + get_serialize_namespace=lambda namespace, async_mode: (f"{namespace}.aio" if async_mode else namespace), + get_relative_import_path=lambda _namespace, module_name: f".{module_name}", + is_top_namespace=lambda namespace: namespace == "sample", + ) + client = SimpleNamespace(filename="_client", name="SampleClient") + serializer = GeneralSerializer(code_model=code_model, env=_env(), async_mode=False) + + init_file = serializer.serialize_init_file([client]) + assert "Stream" not in init_file + assert "AsyncStream" not in init_file + assert "_iter_jsonl" in serializer.serialize_streaming_file()