@typespec/http-client-python 0.37.3 → 0.38.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/emitter/temp/tsconfig.tsbuildinfo +1 -1
- package/eng/scripts/ci/regenerate-common.ts +3 -0
- package/eng/scripts/setup/__pycache__/package_manager.cpython-311.pyc +0 -0
- package/eng/scripts/setup/__pycache__/venvtools.cpython-311.pyc +0 -0
- package/generator/build/lib/pygen/codegen/models/response.py +5 -0
- package/generator/build/lib/pygen/codegen/serializers/builder_serializer.py +76 -23
- package/generator/build/lib/pygen/codegen/serializers/unions_serializer.py +14 -3
- package/generator/build/lib/pygen/codegen/templates/enum.py.jinja2 +2 -2
- package/generator/build/lib/pygen/codegen/templates/operation_tools.jinja2 +5 -0
- package/generator/build/lib/pygen/codegen/templates/streaming_base.py.jinja2 +235 -54
- package/generator/build/lib/pygen/codegen/templates/types.py.jinja2 +1 -1
- package/generator/build/lib/pygen/codegen/templates/unions.py.jinja2 +1 -1
- package/generator/build/lib/pygen/preprocess/__init__.py +4 -3
- package/generator/dist/pygen-0.1.0-py3-none-any.whl +0 -0
- package/generator/pygen/codegen/models/response.py +5 -0
- package/generator/pygen/codegen/serializers/builder_serializer.py +76 -23
- package/generator/pygen/codegen/serializers/unions_serializer.py +14 -3
- package/generator/pygen/codegen/templates/enum.py.jinja2 +2 -2
- package/generator/pygen/codegen/templates/operation_tools.jinja2 +5 -0
- package/generator/pygen/codegen/templates/streaming_base.py.jinja2 +235 -54
- package/generator/pygen/codegen/templates/types.py.jinja2 +1 -1
- package/generator/pygen/codegen/templates/unions.py.jinja2 +1 -1
- package/generator/pygen/preprocess/__init__.py +4 -3
- package/package.json +32 -32
|
@@ -33,7 +33,8 @@ from typing import (
|
|
|
33
33
|
|
|
34
34
|
from typing_extensions import Self
|
|
35
35
|
|
|
36
|
-
from {{ code_model.core_library }}.
|
|
36
|
+
from {{ code_model.core_library }}.exceptions import StreamClosedError, StreamConsumedError
|
|
37
|
+
from {{ code_model.core_library }}.rest import AsyncHttpResponse, HttpRequest, HttpResponse
|
|
37
38
|
|
|
38
39
|
DecodedType = TypeVar("DecodedType")
|
|
39
40
|
ReturnType_co = TypeVar("ReturnType_co", covariant=True)
|
|
@@ -435,13 +436,56 @@ async def _aiter_sse_lines(iter_bytes: AsyncIterator[bytes]) -> AsyncGenerator[s
|
|
|
435
436
|
yield line
|
|
436
437
|
|
|
437
438
|
|
|
439
|
+
def _read_sse_response(response: HttpResponse) -> None:
|
|
440
|
+
"""Read an SSE response body, ignoring responses that are already consumed or closed.
|
|
441
|
+
|
|
442
|
+
:param response: The response to read.
|
|
443
|
+
:type response: HttpResponse
|
|
444
|
+
"""
|
|
445
|
+
try:
|
|
446
|
+
response.read()
|
|
447
|
+
except (StreamConsumedError, StreamClosedError):
|
|
448
|
+
pass
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
async def _read_sse_response_async(response: AsyncHttpResponse) -> None:
|
|
452
|
+
"""Read an SSE response body, ignoring responses that are already consumed or closed.
|
|
453
|
+
|
|
454
|
+
:param response: The response to read.
|
|
455
|
+
:type response: AsyncHttpResponse
|
|
456
|
+
"""
|
|
457
|
+
try:
|
|
458
|
+
await response.read()
|
|
459
|
+
except (StreamConsumedError, StreamClosedError):
|
|
460
|
+
pass
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
def _update_sse_request_headers(request: HttpRequest, last_event_id: Optional[str]) -> None:
|
|
464
|
+
"""Update the last event ID header for an SSE reconnection request.
|
|
465
|
+
|
|
466
|
+
:param request: The reconnection request to update.
|
|
467
|
+
:type request: HttpRequest
|
|
468
|
+
:param last_event_id: The current value of the SSE last event ID buffer.
|
|
469
|
+
:type last_event_id: str or None
|
|
470
|
+
"""
|
|
471
|
+
if last_event_id:
|
|
472
|
+
request.headers["Last-Event-ID"] = last_event_id
|
|
473
|
+
else:
|
|
474
|
+
request.headers.pop("Last-Event-ID", None)
|
|
475
|
+
|
|
476
|
+
|
|
438
477
|
class _SSEEventBuilder:
|
|
439
|
-
"""Accumulates SSE field lines and builds :class:`ServerSentEvent` instances.
|
|
478
|
+
"""Accumulates SSE field lines and builds :class:`ServerSentEvent` instances.
|
|
440
479
|
|
|
441
|
-
|
|
480
|
+
:param last_event_id: The initial value of the SSE last event ID buffer.
|
|
481
|
+
:type last_event_id: str
|
|
482
|
+
"""
|
|
483
|
+
|
|
484
|
+
def __init__(self, last_event_id: str = "") -> None:
|
|
442
485
|
self._data: List[str] = []
|
|
443
486
|
self._event_type = ""
|
|
444
|
-
self._last_id =
|
|
487
|
+
self._last_id = last_event_id
|
|
488
|
+
self._pending_last_id = last_event_id
|
|
445
489
|
self._retry: Optional[int] = None
|
|
446
490
|
|
|
447
491
|
def add_line(self, line: str) -> Optional[ServerSentEvent]:
|
|
@@ -469,7 +513,7 @@ class _SSEEventBuilder:
|
|
|
469
513
|
self._data.append(value)
|
|
470
514
|
elif field == "id":
|
|
471
515
|
if "\x00" not in value:
|
|
472
|
-
self.
|
|
516
|
+
self._pending_last_id = value
|
|
473
517
|
elif field == "retry":
|
|
474
518
|
if value.isascii() and value.isdigit():
|
|
475
519
|
try:
|
|
@@ -482,6 +526,7 @@ class _SSEEventBuilder:
|
|
|
482
526
|
return None
|
|
483
527
|
|
|
484
528
|
def _dispatch(self) -> Optional[ServerSentEvent]:
|
|
529
|
+
self._last_id = self._pending_last_id
|
|
485
530
|
if not self._data:
|
|
486
531
|
# No data accumulated: reset and dispatch nothing.
|
|
487
532
|
self._event_type = ""
|
|
@@ -496,13 +541,55 @@ class _SSEEventBuilder:
|
|
|
496
541
|
self._event_type = ""
|
|
497
542
|
return event
|
|
498
543
|
|
|
544
|
+
@property
|
|
545
|
+
def last_event_id(self) -> str:
|
|
546
|
+
"""Return the current value of the last event ID buffer.
|
|
547
|
+
|
|
548
|
+
:return: The current value of the last event ID buffer.
|
|
549
|
+
:rtype: str
|
|
550
|
+
"""
|
|
551
|
+
return self._last_id
|
|
552
|
+
|
|
553
|
+
@property
|
|
554
|
+
def retry(self) -> Optional[int]:
|
|
555
|
+
"""Return the most recently received valid reconnection time.
|
|
556
|
+
|
|
557
|
+
:return: The reconnection time in milliseconds, if one was provided.
|
|
558
|
+
:rtype: int or None
|
|
559
|
+
"""
|
|
560
|
+
return self._retry
|
|
561
|
+
|
|
499
562
|
|
|
500
563
|
class SSEDecoder:
|
|
501
564
|
"""Decoder for Server-Sent Events (SSE).
|
|
502
565
|
|
|
503
566
|
https://html.spec.whatwg.org/multipage/server-sent-events.html
|
|
567
|
+
|
|
568
|
+
:keyword last_event_id: The initial value of the SSE last event ID buffer.
|
|
569
|
+
:paramtype last_event_id: str or None
|
|
504
570
|
"""
|
|
505
571
|
|
|
572
|
+
def __init__(self, *, last_event_id: Optional[str] = None) -> None:
|
|
573
|
+
self._builder = _SSEEventBuilder(last_event_id or "")
|
|
574
|
+
|
|
575
|
+
@property
|
|
576
|
+
def last_event_id(self) -> str:
|
|
577
|
+
"""Return the current value of the last event ID buffer.
|
|
578
|
+
|
|
579
|
+
:return: The current value of the last event ID buffer.
|
|
580
|
+
:rtype: str
|
|
581
|
+
"""
|
|
582
|
+
return self._builder.last_event_id
|
|
583
|
+
|
|
584
|
+
@property
|
|
585
|
+
def retry(self) -> Optional[int]:
|
|
586
|
+
"""Return the most recently received valid reconnection time.
|
|
587
|
+
|
|
588
|
+
:return: The reconnection time in milliseconds, if one was provided.
|
|
589
|
+
:rtype: int or None
|
|
590
|
+
"""
|
|
591
|
+
return self._builder.retry
|
|
592
|
+
|
|
506
593
|
def iter_events(self, iter_bytes: Iterator[bytes]) -> Iterator[ServerSentEvent]:
|
|
507
594
|
"""Iterate over SSE events from a byte iterator.
|
|
508
595
|
|
|
@@ -511,9 +598,8 @@ class SSEDecoder:
|
|
|
511
598
|
:rtype: Iterator[ServerSentEvent]
|
|
512
599
|
:return: An iterator of server-sent events.
|
|
513
600
|
"""
|
|
514
|
-
builder = _SSEEventBuilder()
|
|
515
601
|
for line in _iter_sse_lines(iter_bytes):
|
|
516
|
-
event =
|
|
602
|
+
event = self._builder.add_line(line)
|
|
517
603
|
if event is not None:
|
|
518
604
|
yield event
|
|
519
605
|
|
|
@@ -522,8 +608,32 @@ class AsyncSSEDecoder:
|
|
|
522
608
|
"""Asynchronous decoder for Server-Sent Events (SSE).
|
|
523
609
|
|
|
524
610
|
https://html.spec.whatwg.org/multipage/server-sent-events.html
|
|
611
|
+
|
|
612
|
+
:keyword last_event_id: The initial value of the SSE last event ID buffer.
|
|
613
|
+
:paramtype last_event_id: str or None
|
|
525
614
|
"""
|
|
526
615
|
|
|
616
|
+
def __init__(self, *, last_event_id: Optional[str] = None) -> None:
|
|
617
|
+
self._builder = _SSEEventBuilder(last_event_id or "")
|
|
618
|
+
|
|
619
|
+
@property
|
|
620
|
+
def last_event_id(self) -> str:
|
|
621
|
+
"""Return the current value of the last event ID buffer.
|
|
622
|
+
|
|
623
|
+
:return: The current value of the last event ID buffer.
|
|
624
|
+
:rtype: str
|
|
625
|
+
"""
|
|
626
|
+
return self._builder.last_event_id
|
|
627
|
+
|
|
628
|
+
@property
|
|
629
|
+
def retry(self) -> Optional[int]:
|
|
630
|
+
"""Return the most recently received valid reconnection time.
|
|
631
|
+
|
|
632
|
+
:return: The reconnection time in milliseconds, if one was provided.
|
|
633
|
+
:rtype: int or None
|
|
634
|
+
"""
|
|
635
|
+
return self._builder.retry
|
|
636
|
+
|
|
527
637
|
# pylint: disable=invalid-overridden-method
|
|
528
638
|
async def aiter_events(self, iter_bytes: AsyncIterator[bytes]) -> AsyncIterator[ServerSentEvent]:
|
|
529
639
|
"""Asynchronously iterate over SSE events from a byte iterator.
|
|
@@ -533,10 +643,9 @@ class AsyncSSEDecoder:
|
|
|
533
643
|
:rtype: AsyncIterator[ServerSentEvent]
|
|
534
644
|
:return: An asynchronous iterator of server-sent events.
|
|
535
645
|
"""
|
|
536
|
-
builder = _SSEEventBuilder()
|
|
537
646
|
async with aclosing(_aiter_sse_lines(iter_bytes)) as lines:
|
|
538
647
|
async for line in lines:
|
|
539
|
-
event =
|
|
648
|
+
event = self._builder.add_line(line)
|
|
540
649
|
if event is not None:
|
|
541
650
|
yield event
|
|
542
651
|
|
|
@@ -563,6 +672,14 @@ class Stream(Iterator[ReturnType_co]):
|
|
|
563
672
|
:keyword terminal_event_predicate: Optional predicate that identifies a payload-bearing
|
|
564
673
|
terminal event. The event is yielded before iteration stops.
|
|
565
674
|
:paramtype terminal_event_predicate: Callable[[Any], bool] or None
|
|
675
|
+
:keyword last_event_id: Optional event ID from which this stream is resuming.
|
|
676
|
+
:paramtype last_event_id: str or None
|
|
677
|
+
:keyword reconnect_callback: Optional callback used to re-establish the underlying response
|
|
678
|
+
when the stream disconnects before a terminal event is seen. Reconnection uses a
|
|
679
|
+
three-second delay unless the server provides an SSE ``retry`` value. The callback is
|
|
680
|
+
called with the last-seen SSE event ID (or ``None``) and the reconnect delay in seconds,
|
|
681
|
+
and must return a new response.
|
|
682
|
+
:paramtype reconnect_callback: Callable[[str or None, float], ~{{ code_model.core_library }}.rest.HttpResponse] or None
|
|
566
683
|
"""
|
|
567
684
|
|
|
568
685
|
def __init__(
|
|
@@ -574,17 +691,26 @@ class Stream(Iterator[ReturnType_co]):
|
|
|
574
691
|
terminal_event: Optional[str] = None,
|
|
575
692
|
terminal_event_names: Optional[Sequence[str]] = None,
|
|
576
693
|
terminal_event_predicate: Optional[Callable[[DecodedType], bool]] = None,
|
|
694
|
+
last_event_id: Optional[str] = None,
|
|
695
|
+
reconnect_callback: Optional[Callable[[Optional[str], float], HttpResponse]] = None,
|
|
577
696
|
) -> None:
|
|
578
697
|
self._response = response
|
|
579
698
|
content_type = response.headers.get("Content-Type", "").split(";", 1)[0].strip().lower()
|
|
580
699
|
self._decoder: StreamDecoder[Any] = (
|
|
581
|
-
decoder
|
|
700
|
+
decoder
|
|
701
|
+
if decoder is not None
|
|
702
|
+
else (
|
|
703
|
+
SSEDecoder(last_event_id=last_event_id)
|
|
704
|
+
if content_type == "text/event-stream"
|
|
705
|
+
else JSONLDecoder()
|
|
706
|
+
)
|
|
582
707
|
)
|
|
583
708
|
self._deserialization_callback = deserialization_callback
|
|
584
709
|
self._terminal_event = terminal_event
|
|
585
710
|
self._terminal_event_names = frozenset(terminal_event_names or ())
|
|
586
711
|
self._terminal_event_predicate = terminal_event_predicate
|
|
587
|
-
self.
|
|
712
|
+
self._reconnect_callback = reconnect_callback
|
|
713
|
+
self._last_event_id = last_event_id
|
|
588
714
|
self._retry: Optional[int] = None
|
|
589
715
|
self._iterator = self._iter_results()
|
|
590
716
|
|
|
@@ -605,26 +731,46 @@ class Stream(Iterator[ReturnType_co]):
|
|
|
605
731
|
return self
|
|
606
732
|
|
|
607
733
|
def _iter_results(self) -> Generator[ReturnType_co, None, None]:
|
|
734
|
+
terminal = False
|
|
608
735
|
try:
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
if event_id is not None:
|
|
612
|
-
self._last_event_id = event_id
|
|
613
|
-
event_retry = getattr(event, "retry", None)
|
|
614
|
-
if event_retry is not None:
|
|
615
|
-
self._retry = event_retry
|
|
616
|
-
if self._terminal_event is not None and getattr(event, "data", None) == self._terminal_event:
|
|
736
|
+
while True:
|
|
737
|
+
if getattr(self._response, "status_code", None) == 204:
|
|
617
738
|
break
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
739
|
+
for event in self._decoder.iter_events(self._response.iter_bytes()):
|
|
740
|
+
event_id = getattr(event, "id", None)
|
|
741
|
+
if event_id is not None:
|
|
742
|
+
self._last_event_id = event_id
|
|
743
|
+
event_retry = getattr(event, "retry", None)
|
|
744
|
+
if event_retry is not None:
|
|
745
|
+
self._retry = event_retry
|
|
746
|
+
if self._terminal_event is not None and getattr(event, "data", None) == self._terminal_event:
|
|
747
|
+
terminal = True
|
|
748
|
+
break
|
|
749
|
+
result = self._deserialization_callback(self._response, cast(Any, event))
|
|
750
|
+
yield result
|
|
751
|
+
if (
|
|
752
|
+
self._terminal_event_names
|
|
753
|
+
and getattr(event, "event", None) in self._terminal_event_names
|
|
754
|
+
) or (
|
|
755
|
+
self._terminal_event_predicate is not None
|
|
756
|
+
and self._terminal_event_predicate(cast(Any, event))
|
|
757
|
+
):
|
|
758
|
+
terminal = True
|
|
759
|
+
break
|
|
760
|
+
if terminal or self._reconnect_callback is None:
|
|
627
761
|
break
|
|
762
|
+
if isinstance(self._decoder, SSEDecoder):
|
|
763
|
+
self._last_event_id = self._decoder.last_event_id
|
|
764
|
+
if self._decoder.retry is not None:
|
|
765
|
+
self._retry = self._decoder.retry
|
|
766
|
+
self._response.close()
|
|
767
|
+
reconnect_delay = (self._retry if self._retry is not None else 3000) / 1000
|
|
768
|
+
self._response = self._reconnect_callback(self._last_event_id, reconnect_delay)
|
|
769
|
+
self._decoder = (
|
|
770
|
+
SSEDecoder(last_event_id=self._last_event_id)
|
|
771
|
+
if isinstance(self._decoder, SSEDecoder)
|
|
772
|
+
else type(self._decoder)()
|
|
773
|
+
)
|
|
628
774
|
finally:
|
|
629
775
|
self._response.close()
|
|
630
776
|
|
|
@@ -668,6 +814,14 @@ class AsyncStream(AsyncIterator[ReturnType_co]):
|
|
|
668
814
|
:keyword terminal_event_predicate: Optional predicate that identifies a payload-bearing
|
|
669
815
|
terminal event. The event is yielded before iteration stops.
|
|
670
816
|
:paramtype terminal_event_predicate: Callable[[Any], bool] or None
|
|
817
|
+
:keyword last_event_id: Optional event ID from which this stream is resuming.
|
|
818
|
+
:paramtype last_event_id: str or None
|
|
819
|
+
:keyword reconnect_callback: Optional callback used to re-establish the underlying response
|
|
820
|
+
when the stream disconnects before a terminal event is seen. Reconnection uses a
|
|
821
|
+
three-second delay unless the server provides an SSE ``retry`` value. The callback is
|
|
822
|
+
called with the last-seen SSE event ID (or ``None``) and the reconnect delay in seconds,
|
|
823
|
+
and must resolve to a new response.
|
|
824
|
+
:paramtype reconnect_callback: Callable[[str or None, float], Any] or None
|
|
671
825
|
"""
|
|
672
826
|
|
|
673
827
|
def __init__(
|
|
@@ -679,19 +833,26 @@ class AsyncStream(AsyncIterator[ReturnType_co]):
|
|
|
679
833
|
terminal_event: Optional[str] = None,
|
|
680
834
|
terminal_event_names: Optional[Sequence[str]] = None,
|
|
681
835
|
terminal_event_predicate: Optional[Callable[[DecodedType], bool]] = None,
|
|
836
|
+
last_event_id: Optional[str] = None,
|
|
837
|
+
reconnect_callback: Optional[Callable[[Optional[str], float], Any]] = None,
|
|
682
838
|
) -> None:
|
|
683
839
|
self._response = response
|
|
684
840
|
content_type = response.headers.get("Content-Type", "").split(";", 1)[0].strip().lower()
|
|
685
841
|
self._decoder: AsyncStreamDecoder[Any] = (
|
|
686
842
|
decoder
|
|
687
843
|
if decoder is not None
|
|
688
|
-
else (
|
|
844
|
+
else (
|
|
845
|
+
AsyncSSEDecoder(last_event_id=last_event_id)
|
|
846
|
+
if content_type == "text/event-stream"
|
|
847
|
+
else AsyncJSONLDecoder()
|
|
848
|
+
)
|
|
689
849
|
)
|
|
690
850
|
self._deserialization_callback = deserialization_callback
|
|
691
851
|
self._terminal_event = terminal_event
|
|
692
852
|
self._terminal_event_names = frozenset(terminal_event_names or ())
|
|
693
853
|
self._terminal_event_predicate = terminal_event_predicate
|
|
694
|
-
self.
|
|
854
|
+
self._reconnect_callback = reconnect_callback
|
|
855
|
+
self._last_event_id = last_event_id
|
|
695
856
|
self._retry: Optional[int] = None
|
|
696
857
|
self._iterator = self._iter_results()
|
|
697
858
|
|
|
@@ -712,34 +873,54 @@ class AsyncStream(AsyncIterator[ReturnType_co]):
|
|
|
712
873
|
return self
|
|
713
874
|
|
|
714
875
|
async def _iter_results(self) -> AsyncGenerator[ReturnType_co, None]:
|
|
715
|
-
|
|
876
|
+
terminal = False
|
|
716
877
|
try:
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
if event_id is not None:
|
|
720
|
-
self._last_event_id = event_id
|
|
721
|
-
event_retry = getattr(event, "retry", None)
|
|
722
|
-
if event_retry is not None:
|
|
723
|
-
self._retry = event_retry
|
|
724
|
-
if self._terminal_event is not None and getattr(event, "data", None) == self._terminal_event:
|
|
878
|
+
while True:
|
|
879
|
+
if getattr(self._response, "status_code", None) == 204:
|
|
725
880
|
break
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
881
|
+
events = self._decoder.aiter_events(self._response.iter_bytes())
|
|
882
|
+
try:
|
|
883
|
+
async for event in events:
|
|
884
|
+
event_id = getattr(event, "id", None)
|
|
885
|
+
if event_id is not None:
|
|
886
|
+
self._last_event_id = event_id
|
|
887
|
+
event_retry = getattr(event, "retry", None)
|
|
888
|
+
if event_retry is not None:
|
|
889
|
+
self._retry = event_retry
|
|
890
|
+
if self._terminal_event is not None and getattr(event, "data", None) == self._terminal_event:
|
|
891
|
+
terminal = True
|
|
892
|
+
break
|
|
893
|
+
result = self._deserialization_callback(self._response, cast(Any, event))
|
|
894
|
+
yield result
|
|
895
|
+
if (
|
|
896
|
+
self._terminal_event_names
|
|
897
|
+
and getattr(event, "event", None) in self._terminal_event_names
|
|
898
|
+
) or (
|
|
899
|
+
self._terminal_event_predicate is not None
|
|
900
|
+
and self._terminal_event_predicate(cast(Any, event))
|
|
901
|
+
):
|
|
902
|
+
terminal = True
|
|
903
|
+
break
|
|
904
|
+
finally:
|
|
905
|
+
aclose = getattr(events, "aclose", None)
|
|
906
|
+
if aclose is not None:
|
|
907
|
+
await aclose()
|
|
908
|
+
if terminal or self._reconnect_callback is None:
|
|
735
909
|
break
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
await aclose()
|
|
741
|
-
finally:
|
|
910
|
+
if isinstance(self._decoder, AsyncSSEDecoder):
|
|
911
|
+
self._last_event_id = self._decoder.last_event_id
|
|
912
|
+
if self._decoder.retry is not None:
|
|
913
|
+
self._retry = self._decoder.retry
|
|
742
914
|
await self._response.close()
|
|
915
|
+
reconnect_delay = (self._retry if self._retry is not None else 3000) / 1000
|
|
916
|
+
self._response = await self._reconnect_callback(self._last_event_id, reconnect_delay)
|
|
917
|
+
self._decoder = (
|
|
918
|
+
AsyncSSEDecoder(last_event_id=self._last_event_id)
|
|
919
|
+
if isinstance(self._decoder, AsyncSSEDecoder)
|
|
920
|
+
else type(self._decoder)()
|
|
921
|
+
)
|
|
922
|
+
finally:
|
|
923
|
+
await self._response.close()
|
|
743
924
|
|
|
744
925
|
async def __aexit__(
|
|
745
926
|
self,
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
{{ serializer.declare_literal_enum(enum) }}
|
|
12
12
|
{% if enum.yaml_data.get("description") %}
|
|
13
|
-
"""{{ op_tools.
|
|
13
|
+
"""{{ op_tools.wrap_docstring(enum.yaml_data["description"], "\n") }}"""
|
|
14
14
|
{% endif %}
|
|
15
15
|
{% endfor %}
|
|
16
16
|
{% for model in models %}
|
|
@@ -54,13 +54,14 @@ def add_overload(yaml_data: dict[str, Any], body_type: dict[str, Any], for_flatt
|
|
|
54
54
|
if yaml_data.get("initialOperation"):
|
|
55
55
|
overload["initialOperation"] = yaml_data["initialOperation"]
|
|
56
56
|
|
|
57
|
+
# Reattach shared type objects before filtering parameters so positional
|
|
58
|
+
# alignment with the original operation is preserved.
|
|
59
|
+
for overload_p, original_p in zip(overload["parameters"], yaml_data["parameters"]):
|
|
60
|
+
overload_p["type"] = original_p["type"]
|
|
57
61
|
if for_flatten_params:
|
|
58
62
|
overload["bodyParameter"]["flattened"] = True
|
|
59
63
|
else:
|
|
60
64
|
overload["parameters"] = [p for p in overload["parameters"] if not p.get("inFlattenedBody")]
|
|
61
|
-
# for yaml sync, we need to make sure all of the responses, parameters, and exceptions' types have the same yaml id
|
|
62
|
-
for overload_p, original_p in zip(overload["parameters"], yaml_data["parameters"]):
|
|
63
|
-
overload_p["type"] = original_p["type"]
|
|
64
65
|
update_overload_section(overload, yaml_data, "responses")
|
|
65
66
|
update_overload_section(overload, yaml_data, "exceptions")
|
|
66
67
|
|
|
Binary file
|
|
@@ -267,6 +267,11 @@ class Response(BaseModel):
|
|
|
267
267
|
file_import.add_submodule_import(relative_path, stream_class, ImportType.LOCAL)
|
|
268
268
|
if self.streaming_kind == "sse":
|
|
269
269
|
file_import.add_import("json", ImportType.STDLIB)
|
|
270
|
+
read_response_helper = (
|
|
271
|
+
"_read_sse_response_async" if kwargs.get("async_mode", False) else "_read_sse_response"
|
|
272
|
+
)
|
|
273
|
+
file_import.add_submodule_import(relative_path, read_response_helper, ImportType.LOCAL)
|
|
274
|
+
file_import.add_submodule_import(relative_path, "_update_sse_request_headers", ImportType.LOCAL)
|
|
270
275
|
for event in self.streaming_events:
|
|
271
276
|
file_import.merge(event.payload_type.imports(**kwargs))
|
|
272
277
|
return file_import
|
|
@@ -1150,22 +1150,36 @@ class _OperationSerializer(_BuilderBaseSerializer[OperationType]):
|
|
|
1150
1150
|
return retval
|
|
1151
1151
|
|
|
1152
1152
|
def handle_error_response( # pylint: disable=too-many-statements, too-many-branches
|
|
1153
|
-
self,
|
|
1153
|
+
self,
|
|
1154
|
+
builder: OperationType,
|
|
1155
|
+
*,
|
|
1156
|
+
response_name: str = "response",
|
|
1157
|
+
pipeline_response_name: str = "pipeline_response",
|
|
1158
|
+
success_status_codes: Optional[list[int]] = None,
|
|
1159
|
+
stream_value: Optional[Union[str, bool]] = None,
|
|
1160
|
+
response_read_name: Optional[str] = None,
|
|
1154
1161
|
) -> list[str]:
|
|
1155
1162
|
async_await = "await " if self.async_mode else ""
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
"
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1163
|
+
success_status_codes = builder.success_status_codes if success_status_codes is None else success_status_codes
|
|
1164
|
+
stream_value = builder.stream_value if stream_value is None else stream_value
|
|
1165
|
+
retval = [f"if {response_name}.status_code not in {str(success_status_codes)}:"]
|
|
1166
|
+
if response_read_name:
|
|
1167
|
+
response_read = [f" {async_await}{response_read_name}({response_name})"]
|
|
1168
|
+
else:
|
|
1169
|
+
response_read = [
|
|
1170
|
+
" try:",
|
|
1171
|
+
f" {async_await}{response_name}.read() # Load the body in memory and close the socket",
|
|
1172
|
+
" except (StreamConsumedError, StreamClosedError):",
|
|
1173
|
+
" pass",
|
|
1174
|
+
]
|
|
1175
|
+
if stream_value is True: # _stream is True so no need to judge it
|
|
1164
1176
|
retval.extend(response_read)
|
|
1165
|
-
elif isinstance(
|
|
1177
|
+
elif isinstance(stream_value, str): # _stream is not sure, so we need to judge it
|
|
1166
1178
|
retval.append(" if _stream:")
|
|
1167
1179
|
retval.extend([f" {l}" for l in response_read])
|
|
1168
|
-
retval.append(
|
|
1180
|
+
retval.append(
|
|
1181
|
+
f" map_error(status_code={response_name}.status_code, " f"response={response_name}, error_map=error_map)"
|
|
1182
|
+
)
|
|
1169
1183
|
error_model = ""
|
|
1170
1184
|
if builder.non_default_errors and ( # pylint: disable=too-many-nested-blocks
|
|
1171
1185
|
self.code_model.options["models-mode"] or self.code_model.generate_typeddict_only
|
|
@@ -1181,7 +1195,7 @@ class _OperationSerializer(_BuilderBaseSerializer[OperationType]):
|
|
|
1181
1195
|
pylint_disable = ""
|
|
1182
1196
|
if isinstance(e.status_codes[0], int):
|
|
1183
1197
|
for status_code in e.status_codes:
|
|
1184
|
-
retval.append(f" {condition}
|
|
1198
|
+
retval.append(f" {condition} {response_name}.status_code == {status_code}:")
|
|
1185
1199
|
type_annotation = e.type.type_annotation( # type: ignore
|
|
1186
1200
|
is_operation_file=True, skip_quote=True, serialize_namespace=self.serialize_namespace
|
|
1187
1201
|
)
|
|
@@ -1190,13 +1204,15 @@ class _OperationSerializer(_BuilderBaseSerializer[OperationType]):
|
|
|
1190
1204
|
fn = "_failsafe_deserialize_xml"
|
|
1191
1205
|
else:
|
|
1192
1206
|
fn = "_failsafe_deserialize"
|
|
1193
|
-
retval.append(
|
|
1207
|
+
retval.append(
|
|
1208
|
+
f" error = {fn}({type_annotation},{pylint_disable}\n {response_name})"
|
|
1209
|
+
)
|
|
1194
1210
|
else:
|
|
1195
1211
|
retval.extend(
|
|
1196
1212
|
[
|
|
1197
1213
|
" error = self._deserialize.failsafe_deserialize(",
|
|
1198
1214
|
f" {type_annotation},{pylint_disable}",
|
|
1199
|
-
"
|
|
1215
|
+
f" {pipeline_response_name},",
|
|
1200
1216
|
" )",
|
|
1201
1217
|
]
|
|
1202
1218
|
)
|
|
@@ -1209,7 +1225,8 @@ class _OperationSerializer(_BuilderBaseSerializer[OperationType]):
|
|
|
1209
1225
|
# ranged status code only exist in typespec and will not have multiple status codes
|
|
1210
1226
|
else:
|
|
1211
1227
|
retval.append(
|
|
1212
|
-
f" {condition} {e.status_codes[0][0]} <=
|
|
1228
|
+
f" {condition} {e.status_codes[0][0]} <= "
|
|
1229
|
+
f"{response_name}.status_code <= {e.status_codes[0][1]}:"
|
|
1213
1230
|
)
|
|
1214
1231
|
type_annotation = e.type.type_annotation( # type: ignore
|
|
1215
1232
|
is_operation_file=True, skip_quote=True, serialize_namespace=self.serialize_namespace
|
|
@@ -1218,19 +1235,19 @@ class _OperationSerializer(_BuilderBaseSerializer[OperationType]):
|
|
|
1218
1235
|
if xml_serializable(str(e.default_content_type)):
|
|
1219
1236
|
retval.append(
|
|
1220
1237
|
" error = _failsafe_deserialize_xml("
|
|
1221
|
-
f"{type_annotation},{pylint_disable}\n
|
|
1238
|
+
f"{type_annotation},{pylint_disable}\n {response_name})"
|
|
1222
1239
|
)
|
|
1223
1240
|
else:
|
|
1224
1241
|
retval.append(
|
|
1225
|
-
" error = _failsafe_deserialize("
|
|
1226
|
-
f"{type_annotation},{pylint_disable}\n
|
|
1242
|
+
f" error = _failsafe_deserialize("
|
|
1243
|
+
f"{type_annotation},{pylint_disable}\n {response_name})"
|
|
1227
1244
|
)
|
|
1228
1245
|
else:
|
|
1229
1246
|
retval.extend(
|
|
1230
1247
|
[
|
|
1231
1248
|
" error = self._deserialize.failsafe_deserialize(",
|
|
1232
1249
|
f" {type_annotation},{pylint_disable}",
|
|
1233
|
-
"
|
|
1250
|
+
f" {pipeline_response_name},",
|
|
1234
1251
|
" )",
|
|
1235
1252
|
]
|
|
1236
1253
|
)
|
|
@@ -1254,7 +1271,7 @@ class _OperationSerializer(_BuilderBaseSerializer[OperationType]):
|
|
|
1254
1271
|
[
|
|
1255
1272
|
f"{indent}error = {fn}(",
|
|
1256
1273
|
f"{indent} {default_error_deserialization}",
|
|
1257
|
-
f"{indent}
|
|
1274
|
+
f"{indent} {response_name},",
|
|
1258
1275
|
f"{indent})",
|
|
1259
1276
|
]
|
|
1260
1277
|
)
|
|
@@ -1263,12 +1280,13 @@ class _OperationSerializer(_BuilderBaseSerializer[OperationType]):
|
|
|
1263
1280
|
[
|
|
1264
1281
|
f"{indent}error = self._deserialize.failsafe_deserialize(",
|
|
1265
1282
|
f"{indent} {default_error_deserialization}",
|
|
1266
|
-
f"{indent}
|
|
1283
|
+
f"{indent} {pipeline_response_name},",
|
|
1267
1284
|
f"{indent})",
|
|
1268
1285
|
]
|
|
1269
1286
|
)
|
|
1270
1287
|
retval.append(
|
|
1271
|
-
" raise HttpResponseError(response=
|
|
1288
|
+
" raise HttpResponseError(response={}{}{})".format(
|
|
1289
|
+
response_name,
|
|
1272
1290
|
error_model,
|
|
1273
1291
|
(", error_format=ARMErrorFormat" if self.code_model.options["azure-arm"] else ""),
|
|
1274
1292
|
)
|
|
@@ -1378,6 +1396,41 @@ class _OperationSerializer(_BuilderBaseSerializer[OperationType]):
|
|
|
1378
1396
|
stream_kwargs.append(f"terminal_event={terminal_event!r}")
|
|
1379
1397
|
if terminal_event_names:
|
|
1380
1398
|
stream_kwargs.append(f"terminal_event_names={terminal_event_names!r}")
|
|
1399
|
+
if response.streaming_kind == "sse": # type: ignore[attr-defined]
|
|
1400
|
+
retval.append("")
|
|
1401
|
+
retval.append(
|
|
1402
|
+
"async def _reconnect(_last_event_id, _reconnect_delay):"
|
|
1403
|
+
if self.async_mode
|
|
1404
|
+
else "def _reconnect(_last_event_id, _reconnect_delay):"
|
|
1405
|
+
)
|
|
1406
|
+
retval.append(" _transport: Any = pipeline_response.context.transport")
|
|
1407
|
+
retval.append(f" {'await ' if self.async_mode else ''}_transport.sleep(_reconnect_delay)")
|
|
1408
|
+
retval.append(" _update_sse_request_headers(_request, _last_event_id)")
|
|
1409
|
+
retval.append(
|
|
1410
|
+
f" _reconnect_response = {self._call_method}self._client.send_request("
|
|
1411
|
+
"_request, stream=True, **kwargs)"
|
|
1412
|
+
)
|
|
1413
|
+
reconnect_status_codes = list(builder.success_status_codes)
|
|
1414
|
+
if 204 not in reconnect_status_codes:
|
|
1415
|
+
reconnect_status_codes.append(204)
|
|
1416
|
+
retval.extend(
|
|
1417
|
+
[
|
|
1418
|
+
f" {line}"
|
|
1419
|
+
for line in self.handle_error_response(
|
|
1420
|
+
builder,
|
|
1421
|
+
response_name="_reconnect_response",
|
|
1422
|
+
pipeline_response_name=(
|
|
1423
|
+
"PipelineResponse(_request, _reconnect_response, pipeline_response.context)"
|
|
1424
|
+
),
|
|
1425
|
+
success_status_codes=reconnect_status_codes,
|
|
1426
|
+
stream_value=True,
|
|
1427
|
+
response_read_name=("_read_sse_response_async" if self.async_mode else "_read_sse_response"),
|
|
1428
|
+
)
|
|
1429
|
+
]
|
|
1430
|
+
)
|
|
1431
|
+
retval.append(" return _reconnect_response")
|
|
1432
|
+
stream_kwargs.append("last_event_id=_last_event_id")
|
|
1433
|
+
stream_kwargs.append("reconnect_callback=_reconnect")
|
|
1381
1434
|
unnamed_terminal_values = (
|
|
1382
1435
|
[discriminator_value for discriminator_value, event in unnamed_discriminator[1] if event.is_terminal]
|
|
1383
1436
|
if unnamed_discriminator is not None
|
|
@@ -1843,7 +1896,7 @@ class _LROOperationSerializer(_OperationSerializer[LROOperationType]):
|
|
|
1843
1896
|
if builder.lro_response.headers:
|
|
1844
1897
|
retval.append(" response_headers = {}")
|
|
1845
1898
|
if (
|
|
1846
|
-
|
|
1899
|
+
not self.code_model.options["models-mode"]
|
|
1847
1900
|
or self.code_model.options["models-mode"] == "dpg"
|
|
1848
1901
|
or builder.lro_response.headers
|
|
1849
1902
|
):
|