From 5e9d75915a10b12269ea2df6b87d55b8bea9e7a7 Mon Sep 17 00:00:00 2001 From: "fern-api[bot]" <115122769+fern-api[bot]@users.noreply.github.com> Date: Tue, 25 Nov 2025 19:28:19 +0000 Subject: [PATCH 01/13] SDK regeneration --- README.md | 24 +- poetry.lock | 12 +- pyproject.toml | 2 +- src/pipedream/accounts/client.py | 26 +- src/pipedream/actions/client.py | 16 +- src/pipedream/actions/raw_client.py | 4 +- src/pipedream/apps/__init__.py | 6 +- src/pipedream/apps/client.py | 38 +- src/pipedream/apps/raw_client.py | 20 +- src/pipedream/apps/types/__init__.py | 10 +- ...py => list_apps_request_sort_direction.py} | 2 +- ...t_key.py => list_apps_request_sort_key.py} | 2 +- src/pipedream/client.py | 2 +- src/pipedream/components/client.py | 18 +- src/pipedream/core/client_wrapper.py | 6 +- src/pipedream/deployed_triggers/client.py | 22 +- src/pipedream/deployed_triggers/raw_client.py | 10 + src/pipedream/proxy/raw_client.py | 683 +++++++++--------- src/pipedream/tokens/client.py | 2 - src/pipedream/triggers/client.py | 26 +- src/pipedream/triggers/raw_client.py | 10 + src/pipedream/types/__init__.py | 3 + src/pipedream/types/deployed_component.py | 5 + src/pipedream/types/emitter.py | 1 + src/pipedream/types/proxy_response_binary.py | 3 + 25 files changed, 475 insertions(+), 478 deletions(-) rename src/pipedream/apps/types/{apps_list_request_sort_direction.py => list_apps_request_sort_direction.py} (61%) rename src/pipedream/apps/types/{apps_list_request_sort_key.py => list_apps_request_sort_key.py} (65%) create mode 100644 src/pipedream/types/proxy_response_binary.py diff --git a/README.md b/README.md index ea4d0a3..4db038a 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,21 @@ The Pipedream Python library provides convenient access to the Pipedream APIs from Python. +## Table of Contents + +- [Installation](#installation) +- [Reference](#reference) +- [Usage](#usage) +- [Async Client](#async-client) +- [Exception Handling](#exception-handling) +- [Pagination](#pagination) +- [Advanced](#advanced) + - [Access Raw Response Data](#access-raw-response-data) + - [Retries](#retries) + - [Timeouts](#timeouts) + - [Custom Client](#custom-client) +- [Contributing](#contributing) + ## Installation ```sh @@ -89,14 +104,7 @@ client = Pipedream( client_id="YOUR_CLIENT_ID", client_secret="YOUR_CLIENT_SECRET", ) -response = client.apps.list( - after="after", - before="before", - limit=1, - q="q", - sort_key="name", - sort_direction="asc", -) +response = client.apps.list() for item in response: yield item # alternatively, you can paginate page-by-page diff --git a/poetry.lock b/poetry.lock index fef3795..a19d7e2 100644 --- a/poetry.lock +++ b/poetry.lock @@ -38,13 +38,13 @@ trio = ["trio (>=0.26.1)"] [[package]] name = "certifi" -version = "2025.10.5" +version = "2025.11.12" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" files = [ - {file = "certifi-2025.10.5-py3-none-any.whl", hash = "sha256:0f212c2744a9bb6de0c56639a6f68afe01ecd92d91f14ae897c4fe7bbeeef0de"}, - {file = "certifi-2025.10.5.tar.gz", hash = "sha256:47c09d31ccf2acf0be3f701ea53595ee7e0b8fa08801c6624be771df09ae7b43"}, + {file = "certifi-2025.11.12-py3-none-any.whl", hash = "sha256:97de8790030bbd5c2d96b7ec782fc2f7820ef8dba6db909ccf95449f2d062d4b"}, + {file = "certifi-2025.11.12.tar.gz", hash = "sha256:d8ab5478f2ecd78af242878415affce761ca6bc54a22a27e026d7c25357c3316"}, ] [[package]] @@ -60,13 +60,13 @@ files = [ [[package]] name = "exceptiongroup" -version = "1.3.0" +version = "1.3.1" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" files = [ - {file = "exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10"}, - {file = "exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88"}, + {file = "exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598"}, + {file = "exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219"}, ] [package.dependencies] diff --git a/pyproject.toml b/pyproject.toml index c93c57d..2af2770 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,7 @@ name = "pipedream" [tool.poetry] name = "pipedream" -version = "1.0.11" +version = "1.0.12" description = "" readme = "README.md" authors = [] diff --git a/src/pipedream/accounts/client.py b/src/pipedream/accounts/client.py index 84f3fdf..4c97f9d 100644 --- a/src/pipedream/accounts/client.py +++ b/src/pipedream/accounts/client.py @@ -82,15 +82,7 @@ def list( client_id="YOUR_CLIENT_ID", client_secret="YOUR_CLIENT_SECRET", ) - response = client.accounts.list( - external_user_id="external_user_id", - oauth_app_id="oauth_app_id", - after="after", - before="before", - limit=1, - app="app", - include_credentials=True, - ) + response = client.accounts.list() for item in response: yield item # alternatively, you can paginate page-by-page @@ -160,8 +152,6 @@ def create( client_secret="YOUR_CLIENT_SECRET", ) client.accounts.create( - external_user_id="external_user_id", - oauth_app_id="oauth_app_id", app_slug="app_slug", cfmap_json="cfmap_json", connect_token="connect_token", @@ -215,7 +205,6 @@ def retrieve( ) client.accounts.retrieve( account_id="account_id", - include_credentials=True, ) """ _response = self._raw_client.retrieve( @@ -363,15 +352,7 @@ async def list( async def main() -> None: - response = await client.accounts.list( - external_user_id="external_user_id", - oauth_app_id="oauth_app_id", - after="after", - before="before", - limit=1, - app="app", - include_credentials=True, - ) + response = await client.accounts.list() async for item in response: yield item @@ -450,8 +431,6 @@ async def create( async def main() -> None: await client.accounts.create( - external_user_id="external_user_id", - oauth_app_id="oauth_app_id", app_slug="app_slug", cfmap_json="cfmap_json", connect_token="connect_token", @@ -513,7 +492,6 @@ async def retrieve( async def main() -> None: await client.accounts.retrieve( account_id="account_id", - include_credentials=True, ) diff --git a/src/pipedream/actions/client.py b/src/pipedream/actions/client.py index cebb494..5f86f30 100644 --- a/src/pipedream/actions/client.py +++ b/src/pipedream/actions/client.py @@ -80,13 +80,7 @@ def list( client_id="YOUR_CLIENT_ID", client_secret="YOUR_CLIENT_SECRET", ) - response = client.actions.list( - after="after", - before="before", - limit=1, - q="q", - app="app", - ) + response = client.actions.list() for item in response: yield item # alternatively, you can paginate page-by-page @@ -431,13 +425,7 @@ async def list( async def main() -> None: - response = await client.actions.list( - after="after", - before="before", - limit=1, - q="q", - app="app", - ) + response = await client.actions.list() async for item in response: yield item diff --git a/src/pipedream/actions/raw_client.py b/src/pipedream/actions/raw_client.py index 1637a43..47bb1ed 100644 --- a/src/pipedream/actions/raw_client.py +++ b/src/pipedream/actions/raw_client.py @@ -426,7 +426,7 @@ def run( ), "dynamic_props_id": dynamic_props_id, "stash_id": convert_and_respect_annotation_metadata( - object_=stash_id, annotation=RunActionOptsStashId, direction="write" + object_=stash_id, annotation=typing.Optional[RunActionOptsStashId], direction="write" ), }, headers={ @@ -866,7 +866,7 @@ async def run( ), "dynamic_props_id": dynamic_props_id, "stash_id": convert_and_respect_annotation_metadata( - object_=stash_id, annotation=RunActionOptsStashId, direction="write" + object_=stash_id, annotation=typing.Optional[RunActionOptsStashId], direction="write" ), }, headers={ diff --git a/src/pipedream/apps/__init__.py b/src/pipedream/apps/__init__.py index 3a7655e..4074914 100644 --- a/src/pipedream/apps/__init__.py +++ b/src/pipedream/apps/__init__.py @@ -6,8 +6,8 @@ from importlib import import_module if typing.TYPE_CHECKING: - from .types import AppsListRequestSortDirection, AppsListRequestSortKey -_dynamic_imports: typing.Dict[str, str] = {"AppsListRequestSortDirection": ".types", "AppsListRequestSortKey": ".types"} + from .types import ListAppsRequestSortDirection, ListAppsRequestSortKey +_dynamic_imports: typing.Dict[str, str] = {"ListAppsRequestSortDirection": ".types", "ListAppsRequestSortKey": ".types"} def __getattr__(attr_name: str) -> typing.Any: @@ -31,4 +31,4 @@ def __dir__(): return sorted(lazy_attrs) -__all__ = ["AppsListRequestSortDirection", "AppsListRequestSortKey"] +__all__ = ["ListAppsRequestSortDirection", "ListAppsRequestSortKey"] diff --git a/src/pipedream/apps/client.py b/src/pipedream/apps/client.py index 512d8df..28ab702 100644 --- a/src/pipedream/apps/client.py +++ b/src/pipedream/apps/client.py @@ -8,8 +8,8 @@ from ..types.app import App from ..types.get_app_response import GetAppResponse from .raw_client import AsyncRawAppsClient, RawAppsClient -from .types.apps_list_request_sort_direction import AppsListRequestSortDirection -from .types.apps_list_request_sort_key import AppsListRequestSortKey +from .types.list_apps_request_sort_direction import ListAppsRequestSortDirection +from .types.list_apps_request_sort_key import ListAppsRequestSortKey class AppsClient: @@ -34,8 +34,8 @@ def list( before: typing.Optional[str] = None, limit: typing.Optional[int] = None, q: typing.Optional[str] = None, - sort_key: typing.Optional[AppsListRequestSortKey] = None, - sort_direction: typing.Optional[AppsListRequestSortDirection] = None, + sort_key: typing.Optional[ListAppsRequestSortKey] = None, + sort_direction: typing.Optional[ListAppsRequestSortDirection] = None, category_ids: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None, request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[App]: @@ -56,10 +56,10 @@ def list( q : typing.Optional[str] A search query to filter the apps - sort_key : typing.Optional[AppsListRequestSortKey] + sort_key : typing.Optional[ListAppsRequestSortKey] The key to sort the apps by - sort_direction : typing.Optional[AppsListRequestSortDirection] + sort_direction : typing.Optional[ListAppsRequestSortDirection] The direction to sort the apps category_ids : typing.Optional[typing.Union[str, typing.Sequence[str]]] @@ -83,14 +83,7 @@ def list( client_id="YOUR_CLIENT_ID", client_secret="YOUR_CLIENT_SECRET", ) - response = client.apps.list( - after="after", - before="before", - limit=1, - q="q", - sort_key="name", - sort_direction="asc", - ) + response = client.apps.list() for item in response: yield item # alternatively, you can paginate page-by-page @@ -165,8 +158,8 @@ async def list( before: typing.Optional[str] = None, limit: typing.Optional[int] = None, q: typing.Optional[str] = None, - sort_key: typing.Optional[AppsListRequestSortKey] = None, - sort_direction: typing.Optional[AppsListRequestSortDirection] = None, + sort_key: typing.Optional[ListAppsRequestSortKey] = None, + sort_direction: typing.Optional[ListAppsRequestSortDirection] = None, category_ids: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None, request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[App]: @@ -187,10 +180,10 @@ async def list( q : typing.Optional[str] A search query to filter the apps - sort_key : typing.Optional[AppsListRequestSortKey] + sort_key : typing.Optional[ListAppsRequestSortKey] The key to sort the apps by - sort_direction : typing.Optional[AppsListRequestSortDirection] + sort_direction : typing.Optional[ListAppsRequestSortDirection] The direction to sort the apps category_ids : typing.Optional[typing.Union[str, typing.Sequence[str]]] @@ -219,14 +212,7 @@ async def list( async def main() -> None: - response = await client.apps.list( - after="after", - before="before", - limit=1, - q="q", - sort_key="name", - sort_direction="asc", - ) + response = await client.apps.list() async for item in response: yield item diff --git a/src/pipedream/apps/raw_client.py b/src/pipedream/apps/raw_client.py index 3bf84a7..c127e01 100644 --- a/src/pipedream/apps/raw_client.py +++ b/src/pipedream/apps/raw_client.py @@ -13,8 +13,8 @@ from ..types.app import App from ..types.get_app_response import GetAppResponse from ..types.list_apps_response import ListAppsResponse -from .types.apps_list_request_sort_direction import AppsListRequestSortDirection -from .types.apps_list_request_sort_key import AppsListRequestSortKey +from .types.list_apps_request_sort_direction import ListAppsRequestSortDirection +from .types.list_apps_request_sort_key import ListAppsRequestSortKey class RawAppsClient: @@ -28,8 +28,8 @@ def list( before: typing.Optional[str] = None, limit: typing.Optional[int] = None, q: typing.Optional[str] = None, - sort_key: typing.Optional[AppsListRequestSortKey] = None, - sort_direction: typing.Optional[AppsListRequestSortDirection] = None, + sort_key: typing.Optional[ListAppsRequestSortKey] = None, + sort_direction: typing.Optional[ListAppsRequestSortDirection] = None, category_ids: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None, request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[App]: @@ -50,10 +50,10 @@ def list( q : typing.Optional[str] A search query to filter the apps - sort_key : typing.Optional[AppsListRequestSortKey] + sort_key : typing.Optional[ListAppsRequestSortKey] The key to sort the apps by - sort_direction : typing.Optional[AppsListRequestSortDirection] + sort_direction : typing.Optional[ListAppsRequestSortDirection] The direction to sort the apps category_ids : typing.Optional[typing.Union[str, typing.Sequence[str]]] @@ -165,8 +165,8 @@ async def list( before: typing.Optional[str] = None, limit: typing.Optional[int] = None, q: typing.Optional[str] = None, - sort_key: typing.Optional[AppsListRequestSortKey] = None, - sort_direction: typing.Optional[AppsListRequestSortDirection] = None, + sort_key: typing.Optional[ListAppsRequestSortKey] = None, + sort_direction: typing.Optional[ListAppsRequestSortDirection] = None, category_ids: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None, request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[App]: @@ -187,10 +187,10 @@ async def list( q : typing.Optional[str] A search query to filter the apps - sort_key : typing.Optional[AppsListRequestSortKey] + sort_key : typing.Optional[ListAppsRequestSortKey] The key to sort the apps by - sort_direction : typing.Optional[AppsListRequestSortDirection] + sort_direction : typing.Optional[ListAppsRequestSortDirection] The direction to sort the apps category_ids : typing.Optional[typing.Union[str, typing.Sequence[str]]] diff --git a/src/pipedream/apps/types/__init__.py b/src/pipedream/apps/types/__init__.py index 08c3a02..526f29a 100644 --- a/src/pipedream/apps/types/__init__.py +++ b/src/pipedream/apps/types/__init__.py @@ -6,11 +6,11 @@ from importlib import import_module if typing.TYPE_CHECKING: - from .apps_list_request_sort_direction import AppsListRequestSortDirection - from .apps_list_request_sort_key import AppsListRequestSortKey + from .list_apps_request_sort_direction import ListAppsRequestSortDirection + from .list_apps_request_sort_key import ListAppsRequestSortKey _dynamic_imports: typing.Dict[str, str] = { - "AppsListRequestSortDirection": ".apps_list_request_sort_direction", - "AppsListRequestSortKey": ".apps_list_request_sort_key", + "ListAppsRequestSortDirection": ".list_apps_request_sort_direction", + "ListAppsRequestSortKey": ".list_apps_request_sort_key", } @@ -35,4 +35,4 @@ def __dir__(): return sorted(lazy_attrs) -__all__ = ["AppsListRequestSortDirection", "AppsListRequestSortKey"] +__all__ = ["ListAppsRequestSortDirection", "ListAppsRequestSortKey"] diff --git a/src/pipedream/apps/types/apps_list_request_sort_direction.py b/src/pipedream/apps/types/list_apps_request_sort_direction.py similarity index 61% rename from src/pipedream/apps/types/apps_list_request_sort_direction.py rename to src/pipedream/apps/types/list_apps_request_sort_direction.py index d841cc9..4d86906 100644 --- a/src/pipedream/apps/types/apps_list_request_sort_direction.py +++ b/src/pipedream/apps/types/list_apps_request_sort_direction.py @@ -2,4 +2,4 @@ import typing -AppsListRequestSortDirection = typing.Union[typing.Literal["asc", "desc"], typing.Any] +ListAppsRequestSortDirection = typing.Union[typing.Literal["asc", "desc"], typing.Any] diff --git a/src/pipedream/apps/types/apps_list_request_sort_key.py b/src/pipedream/apps/types/list_apps_request_sort_key.py similarity index 65% rename from src/pipedream/apps/types/apps_list_request_sort_key.py rename to src/pipedream/apps/types/list_apps_request_sort_key.py index ab4065d..6e9b8e2 100644 --- a/src/pipedream/apps/types/apps_list_request_sort_key.py +++ b/src/pipedream/apps/types/list_apps_request_sort_key.py @@ -2,4 +2,4 @@ import typing -AppsListRequestSortKey = typing.Union[typing.Literal["name", "name_slug", "featured_weight"], typing.Any] +ListAppsRequestSortKey = typing.Union[typing.Literal["name", "name_slug", "featured_weight"], typing.Any] diff --git a/src/pipedream/client.py b/src/pipedream/client.py index 6f4accc..cfb7782 100644 --- a/src/pipedream/client.py +++ b/src/pipedream/client.py @@ -6,7 +6,7 @@ import typing import httpx -from .types.project_environment import ProjectEnvironment +from ._.types.project_environment import ProjectEnvironment from .core.api_error import ApiError from .core.client_wrapper import AsyncClientWrapper, SyncClientWrapper from .core.oauth_token_provider import OAuthTokenProvider diff --git a/src/pipedream/components/client.py b/src/pipedream/components/client.py index a68c358..2004396 100644 --- a/src/pipedream/components/client.py +++ b/src/pipedream/components/client.py @@ -83,14 +83,7 @@ def list( client_id="YOUR_CLIENT_ID", client_secret="YOUR_CLIENT_SECRET", ) - response = client.components.list( - after="after", - before="before", - limit=1, - q="q", - app="app", - component_type="trigger", - ) + response = client.components.list() for item in response: yield item # alternatively, you can paginate page-by-page @@ -379,14 +372,7 @@ async def list( async def main() -> None: - response = await client.components.list( - after="after", - before="before", - limit=1, - q="q", - app="app", - component_type="trigger", - ) + response = await client.components.list() async for item in response: yield item diff --git a/src/pipedream/core/client_wrapper.py b/src/pipedream/core/client_wrapper.py index d1cbb05..5792b1c 100644 --- a/src/pipedream/core/client_wrapper.py +++ b/src/pipedream/core/client_wrapper.py @@ -3,7 +3,7 @@ import typing import httpx -from ..types.project_environment import ProjectEnvironment +from .._.types.project_environment import ProjectEnvironment from .http_client import AsyncHttpClient, HttpClient @@ -27,10 +27,10 @@ def __init__( def get_headers(self) -> typing.Dict[str, str]: headers: typing.Dict[str, str] = { - "User-Agent": "pipedream/1.0.11", + "User-Agent": "pipedream/1.0.12", "X-Fern-Language": "Python", "X-Fern-SDK-Name": "pipedream", - "X-Fern-SDK-Version": "1.0.11", + "X-Fern-SDK-Version": "1.0.12", **(self.get_custom_headers() or {}), } if self._project_environment is not None: diff --git a/src/pipedream/deployed_triggers/client.py b/src/pipedream/deployed_triggers/client.py index ea92f4e..d7ae086 100644 --- a/src/pipedream/deployed_triggers/client.py +++ b/src/pipedream/deployed_triggers/client.py @@ -81,11 +81,7 @@ def list( client_secret="YOUR_CLIENT_SECRET", ) response = client.deployed_triggers.list( - after="after", - before="before", - limit=1, external_user_id="external_user_id", - emitter_type="email", ) for item in response: yield item @@ -151,6 +147,7 @@ def update( active: typing.Optional[bool] = OMIT, configured_props: typing.Optional[ConfiguredProps] = OMIT, name: typing.Optional[str] = OMIT, + emit_on_deploy: typing.Optional[bool] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> Emitter: """ @@ -171,6 +168,9 @@ def update( name : typing.Optional[str] The name of the trigger + emit_on_deploy : typing.Optional[bool] + Whether the trigger should emit events during deployment + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -200,6 +200,7 @@ def update( active=active, configured_props=configured_props, name=name, + emit_on_deploy=emit_on_deploy, request_options=request_options, ) return _response.data @@ -245,7 +246,6 @@ def delete( client.deployed_triggers.delete( trigger_id="trigger_id", external_user_id="external_user_id", - ignore_hook_errors=True, ) """ _response = self._raw_client.delete( @@ -298,7 +298,6 @@ def list_events( client.deployed_triggers.list_events( trigger_id="trigger_id", external_user_id="external_user_id", - n=1, ) """ _response = self._raw_client.list_events( @@ -558,11 +557,7 @@ async def list( async def main() -> None: response = await client.deployed_triggers.list( - after="after", - before="before", - limit=1, external_user_id="external_user_id", - emitter_type="email", ) async for item in response: yield item @@ -640,6 +635,7 @@ async def update( active: typing.Optional[bool] = OMIT, configured_props: typing.Optional[ConfiguredProps] = OMIT, name: typing.Optional[str] = OMIT, + emit_on_deploy: typing.Optional[bool] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> Emitter: """ @@ -660,6 +656,9 @@ async def update( name : typing.Optional[str] The name of the trigger + emit_on_deploy : typing.Optional[bool] + Whether the trigger should emit events during deployment + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -697,6 +696,7 @@ async def main() -> None: active=active, configured_props=configured_props, name=name, + emit_on_deploy=emit_on_deploy, request_options=request_options, ) return _response.data @@ -747,7 +747,6 @@ async def main() -> None: await client.deployed_triggers.delete( trigger_id="trigger_id", external_user_id="external_user_id", - ignore_hook_errors=True, ) @@ -808,7 +807,6 @@ async def main() -> None: await client.deployed_triggers.list_events( trigger_id="trigger_id", external_user_id="external_user_id", - n=1, ) diff --git a/src/pipedream/deployed_triggers/raw_client.py b/src/pipedream/deployed_triggers/raw_client.py index 4b2e576..dab281a 100644 --- a/src/pipedream/deployed_triggers/raw_client.py +++ b/src/pipedream/deployed_triggers/raw_client.py @@ -186,6 +186,7 @@ def update( active: typing.Optional[bool] = OMIT, configured_props: typing.Optional[ConfiguredProps] = OMIT, name: typing.Optional[str] = OMIT, + emit_on_deploy: typing.Optional[bool] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> HttpResponse[Emitter]: """ @@ -206,6 +207,9 @@ def update( name : typing.Optional[str] The name of the trigger + emit_on_deploy : typing.Optional[bool] + Whether the trigger should emit events during deployment + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -226,6 +230,7 @@ def update( object_=configured_props, annotation=ConfiguredProps, direction="write" ), "name": name, + "emit_on_deploy": emit_on_deploy, }, headers={ "content-type": "application/json", @@ -795,6 +800,7 @@ async def update( active: typing.Optional[bool] = OMIT, configured_props: typing.Optional[ConfiguredProps] = OMIT, name: typing.Optional[str] = OMIT, + emit_on_deploy: typing.Optional[bool] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> AsyncHttpResponse[Emitter]: """ @@ -815,6 +821,9 @@ async def update( name : typing.Optional[str] The name of the trigger + emit_on_deploy : typing.Optional[bool] + Whether the trigger should emit events during deployment + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -835,6 +844,7 @@ async def update( object_=configured_props, annotation=ConfiguredProps, direction="write" ), "name": name, + "emit_on_deploy": emit_on_deploy, }, headers={ "content-type": "application/json", diff --git a/src/pipedream/proxy/raw_client.py b/src/pipedream/proxy/raw_client.py index 6825954..7c4d061 100644 --- a/src/pipedream/proxy/raw_client.py +++ b/src/pipedream/proxy/raw_client.py @@ -1,5 +1,6 @@ # This file was auto-generated by Fern from our API Definition. +import contextlib import typing from json.decoder import JSONDecodeError @@ -10,7 +11,6 @@ from ..core.pydantic_utilities import parse_obj_as from ..core.request_options import RequestOptions from ..errors.too_many_requests_error import TooManyRequestsError -from ..types.proxy_response import ProxyResponse # this is used as the default value for optional parameters OMIT = typing.cast(typing.Any, ...) @@ -20,6 +20,7 @@ class RawProxyClient: def __init__(self, *, client_wrapper: SyncClientWrapper): self._client_wrapper = client_wrapper + @contextlib.contextmanager def get( self, url_64: str, @@ -27,7 +28,7 @@ def get( external_user_id: str, account_id: str, request_options: typing.Optional[RequestOptions] = None, - ) -> HttpResponse[ProxyResponse]: + ) -> typing.Iterator[HttpResponse[typing.Iterator[bytes]]]: """ Forward an authenticated GET request to an external API using an external user's account credentials @@ -43,14 +44,14 @@ def get( The account ID to use for authentication request_options : typing.Optional[RequestOptions] - Request-specific configuration. + Request-specific configuration. You can pass in configuration such as `chunk_size`, and more to customize the request and response. Returns ------- - HttpResponse[ProxyResponse] + typing.Iterator[HttpResponse[typing.Iterator[bytes]]] proxy request successful """ - _response = self._client_wrapper.httpx_client.request( + with self._client_wrapper.httpx_client.stream( f"v1/connect/{jsonable_encoder(self._client_wrapper._project_id)}/proxy/{jsonable_encoder(url_64)}", method="GET", params={ @@ -58,35 +59,37 @@ def get( "account_id": account_id, }, request_options=request_options, - ) - try: - if _response is None or not _response.text.strip(): - return HttpResponse(response=_response, data=None) - if 200 <= _response.status_code < 300: - _data = typing.cast( - ProxyResponse, - parse_obj_as( - type_=ProxyResponse, # type: ignore - object_=_response.json(), - ), - ) - return HttpResponse(response=_response, data=_data) - if _response.status_code == 429: - raise TooManyRequestsError( - headers=dict(_response.headers), - body=typing.cast( - typing.Optional[typing.Any], - parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore - object_=_response.json(), - ), - ), - ) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - + ) as _response: + + def _stream() -> HttpResponse[typing.Iterator[bytes]]: + try: + if 200 <= _response.status_code < 300: + _chunk_size = request_options.get("chunk_size", None) if request_options is not None else None + return HttpResponse( + response=_response, data=(_chunk for _chunk in _response.iter_bytes(chunk_size=_chunk_size)) + ) + _response.read() + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + typing.Optional[typing.Any], + parse_obj_as( + type_=typing.Optional[typing.Any], # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.text + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + yield _stream() + + @contextlib.contextmanager def post( self, url_64: str, @@ -95,7 +98,7 @@ def post( account_id: str, request: typing.Dict[str, typing.Optional[typing.Any]], request_options: typing.Optional[RequestOptions] = None, - ) -> HttpResponse[ProxyResponse]: + ) -> typing.Iterator[HttpResponse[typing.Iterator[bytes]]]: """ Forward an authenticated POST request to an external API using an external user's account credentials @@ -113,14 +116,14 @@ def post( request : typing.Dict[str, typing.Optional[typing.Any]] request_options : typing.Optional[RequestOptions] - Request-specific configuration. + Request-specific configuration. You can pass in configuration such as `chunk_size`, and more to customize the request and response. Returns ------- - HttpResponse[ProxyResponse] + typing.Iterator[HttpResponse[typing.Iterator[bytes]]] proxy request successful """ - _response = self._client_wrapper.httpx_client.request( + with self._client_wrapper.httpx_client.stream( f"v1/connect/{jsonable_encoder(self._client_wrapper._project_id)}/proxy/{jsonable_encoder(url_64)}", method="POST", params={ @@ -133,35 +136,37 @@ def post( }, request_options=request_options, omit=OMIT, - ) - try: - if _response is None or not _response.text.strip(): - return HttpResponse(response=_response, data=None) - if 200 <= _response.status_code < 300: - _data = typing.cast( - ProxyResponse, - parse_obj_as( - type_=ProxyResponse, # type: ignore - object_=_response.json(), - ), - ) - return HttpResponse(response=_response, data=_data) - if _response.status_code == 429: - raise TooManyRequestsError( - headers=dict(_response.headers), - body=typing.cast( - typing.Optional[typing.Any], - parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore - object_=_response.json(), - ), - ), - ) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - + ) as _response: + + def _stream() -> HttpResponse[typing.Iterator[bytes]]: + try: + if 200 <= _response.status_code < 300: + _chunk_size = request_options.get("chunk_size", None) if request_options is not None else None + return HttpResponse( + response=_response, data=(_chunk for _chunk in _response.iter_bytes(chunk_size=_chunk_size)) + ) + _response.read() + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + typing.Optional[typing.Any], + parse_obj_as( + type_=typing.Optional[typing.Any], # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.text + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + yield _stream() + + @contextlib.contextmanager def put( self, url_64: str, @@ -170,7 +175,7 @@ def put( account_id: str, request: typing.Dict[str, typing.Optional[typing.Any]], request_options: typing.Optional[RequestOptions] = None, - ) -> HttpResponse[ProxyResponse]: + ) -> typing.Iterator[HttpResponse[typing.Iterator[bytes]]]: """ Forward an authenticated PUT request to an external API using an external user's account credentials @@ -188,14 +193,14 @@ def put( request : typing.Dict[str, typing.Optional[typing.Any]] request_options : typing.Optional[RequestOptions] - Request-specific configuration. + Request-specific configuration. You can pass in configuration such as `chunk_size`, and more to customize the request and response. Returns ------- - HttpResponse[ProxyResponse] + typing.Iterator[HttpResponse[typing.Iterator[bytes]]] proxy request successful """ - _response = self._client_wrapper.httpx_client.request( + with self._client_wrapper.httpx_client.stream( f"v1/connect/{jsonable_encoder(self._client_wrapper._project_id)}/proxy/{jsonable_encoder(url_64)}", method="PUT", params={ @@ -208,35 +213,37 @@ def put( }, request_options=request_options, omit=OMIT, - ) - try: - if _response is None or not _response.text.strip(): - return HttpResponse(response=_response, data=None) - if 200 <= _response.status_code < 300: - _data = typing.cast( - ProxyResponse, - parse_obj_as( - type_=ProxyResponse, # type: ignore - object_=_response.json(), - ), - ) - return HttpResponse(response=_response, data=_data) - if _response.status_code == 429: - raise TooManyRequestsError( - headers=dict(_response.headers), - body=typing.cast( - typing.Optional[typing.Any], - parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore - object_=_response.json(), - ), - ), - ) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - + ) as _response: + + def _stream() -> HttpResponse[typing.Iterator[bytes]]: + try: + if 200 <= _response.status_code < 300: + _chunk_size = request_options.get("chunk_size", None) if request_options is not None else None + return HttpResponse( + response=_response, data=(_chunk for _chunk in _response.iter_bytes(chunk_size=_chunk_size)) + ) + _response.read() + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + typing.Optional[typing.Any], + parse_obj_as( + type_=typing.Optional[typing.Any], # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.text + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + yield _stream() + + @contextlib.contextmanager def delete( self, url_64: str, @@ -244,7 +251,7 @@ def delete( external_user_id: str, account_id: str, request_options: typing.Optional[RequestOptions] = None, - ) -> HttpResponse[ProxyResponse]: + ) -> typing.Iterator[HttpResponse[typing.Iterator[bytes]]]: """ Forward an authenticated DELETE request to an external API using an external user's account credentials @@ -260,14 +267,14 @@ def delete( The account ID to use for authentication request_options : typing.Optional[RequestOptions] - Request-specific configuration. + Request-specific configuration. You can pass in configuration such as `chunk_size`, and more to customize the request and response. Returns ------- - HttpResponse[ProxyResponse] + typing.Iterator[HttpResponse[typing.Iterator[bytes]]] proxy request successful """ - _response = self._client_wrapper.httpx_client.request( + with self._client_wrapper.httpx_client.stream( f"v1/connect/{jsonable_encoder(self._client_wrapper._project_id)}/proxy/{jsonable_encoder(url_64)}", method="DELETE", params={ @@ -275,35 +282,37 @@ def delete( "account_id": account_id, }, request_options=request_options, - ) - try: - if _response is None or not _response.text.strip(): - return HttpResponse(response=_response, data=None) - if 200 <= _response.status_code < 300: - _data = typing.cast( - ProxyResponse, - parse_obj_as( - type_=ProxyResponse, # type: ignore - object_=_response.json(), - ), - ) - return HttpResponse(response=_response, data=_data) - if _response.status_code == 429: - raise TooManyRequestsError( - headers=dict(_response.headers), - body=typing.cast( - typing.Optional[typing.Any], - parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore - object_=_response.json(), - ), - ), - ) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - + ) as _response: + + def _stream() -> HttpResponse[typing.Iterator[bytes]]: + try: + if 200 <= _response.status_code < 300: + _chunk_size = request_options.get("chunk_size", None) if request_options is not None else None + return HttpResponse( + response=_response, data=(_chunk for _chunk in _response.iter_bytes(chunk_size=_chunk_size)) + ) + _response.read() + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + typing.Optional[typing.Any], + parse_obj_as( + type_=typing.Optional[typing.Any], # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.text + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + yield _stream() + + @contextlib.contextmanager def patch( self, url_64: str, @@ -312,7 +321,7 @@ def patch( account_id: str, request: typing.Dict[str, typing.Optional[typing.Any]], request_options: typing.Optional[RequestOptions] = None, - ) -> HttpResponse[ProxyResponse]: + ) -> typing.Iterator[HttpResponse[typing.Iterator[bytes]]]: """ Forward an authenticated PATCH request to an external API using an external user's account credentials @@ -330,14 +339,14 @@ def patch( request : typing.Dict[str, typing.Optional[typing.Any]] request_options : typing.Optional[RequestOptions] - Request-specific configuration. + Request-specific configuration. You can pass in configuration such as `chunk_size`, and more to customize the request and response. Returns ------- - HttpResponse[ProxyResponse] + typing.Iterator[HttpResponse[typing.Iterator[bytes]]] proxy request successful """ - _response = self._client_wrapper.httpx_client.request( + with self._client_wrapper.httpx_client.stream( f"v1/connect/{jsonable_encoder(self._client_wrapper._project_id)}/proxy/{jsonable_encoder(url_64)}", method="PATCH", params={ @@ -350,40 +359,42 @@ def patch( }, request_options=request_options, omit=OMIT, - ) - try: - if _response is None or not _response.text.strip(): - return HttpResponse(response=_response, data=None) - if 200 <= _response.status_code < 300: - _data = typing.cast( - ProxyResponse, - parse_obj_as( - type_=ProxyResponse, # type: ignore - object_=_response.json(), - ), - ) - return HttpResponse(response=_response, data=_data) - if _response.status_code == 429: - raise TooManyRequestsError( - headers=dict(_response.headers), - body=typing.cast( - typing.Optional[typing.Any], - parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore - object_=_response.json(), - ), - ), - ) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + ) as _response: + + def _stream() -> HttpResponse[typing.Iterator[bytes]]: + try: + if 200 <= _response.status_code < 300: + _chunk_size = request_options.get("chunk_size", None) if request_options is not None else None + return HttpResponse( + response=_response, data=(_chunk for _chunk in _response.iter_bytes(chunk_size=_chunk_size)) + ) + _response.read() + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + typing.Optional[typing.Any], + parse_obj_as( + type_=typing.Optional[typing.Any], # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.text + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + yield _stream() class AsyncRawProxyClient: def __init__(self, *, client_wrapper: AsyncClientWrapper): self._client_wrapper = client_wrapper + @contextlib.asynccontextmanager async def get( self, url_64: str, @@ -391,7 +402,7 @@ async def get( external_user_id: str, account_id: str, request_options: typing.Optional[RequestOptions] = None, - ) -> AsyncHttpResponse[ProxyResponse]: + ) -> typing.AsyncIterator[AsyncHttpResponse[typing.AsyncIterator[bytes]]]: """ Forward an authenticated GET request to an external API using an external user's account credentials @@ -407,14 +418,14 @@ async def get( The account ID to use for authentication request_options : typing.Optional[RequestOptions] - Request-specific configuration. + Request-specific configuration. You can pass in configuration such as `chunk_size`, and more to customize the request and response. Returns ------- - AsyncHttpResponse[ProxyResponse] + typing.AsyncIterator[AsyncHttpResponse[typing.AsyncIterator[bytes]]] proxy request successful """ - _response = await self._client_wrapper.httpx_client.request( + async with self._client_wrapper.httpx_client.stream( f"v1/connect/{jsonable_encoder(self._client_wrapper._project_id)}/proxy/{jsonable_encoder(url_64)}", method="GET", params={ @@ -422,35 +433,38 @@ async def get( "account_id": account_id, }, request_options=request_options, - ) - try: - if _response is None or not _response.text.strip(): - return AsyncHttpResponse(response=_response, data=None) - if 200 <= _response.status_code < 300: - _data = typing.cast( - ProxyResponse, - parse_obj_as( - type_=ProxyResponse, # type: ignore - object_=_response.json(), - ), - ) - return AsyncHttpResponse(response=_response, data=_data) - if _response.status_code == 429: - raise TooManyRequestsError( - headers=dict(_response.headers), - body=typing.cast( - typing.Optional[typing.Any], - parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore - object_=_response.json(), - ), - ), - ) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - + ) as _response: + + async def _stream() -> AsyncHttpResponse[typing.AsyncIterator[bytes]]: + try: + if 200 <= _response.status_code < 300: + _chunk_size = request_options.get("chunk_size", None) if request_options is not None else None + return AsyncHttpResponse( + response=_response, + data=(_chunk async for _chunk in _response.aiter_bytes(chunk_size=_chunk_size)), + ) + await _response.aread() + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + typing.Optional[typing.Any], + parse_obj_as( + type_=typing.Optional[typing.Any], # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.text + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + yield await _stream() + + @contextlib.asynccontextmanager async def post( self, url_64: str, @@ -459,7 +473,7 @@ async def post( account_id: str, request: typing.Dict[str, typing.Optional[typing.Any]], request_options: typing.Optional[RequestOptions] = None, - ) -> AsyncHttpResponse[ProxyResponse]: + ) -> typing.AsyncIterator[AsyncHttpResponse[typing.AsyncIterator[bytes]]]: """ Forward an authenticated POST request to an external API using an external user's account credentials @@ -477,14 +491,14 @@ async def post( request : typing.Dict[str, typing.Optional[typing.Any]] request_options : typing.Optional[RequestOptions] - Request-specific configuration. + Request-specific configuration. You can pass in configuration such as `chunk_size`, and more to customize the request and response. Returns ------- - AsyncHttpResponse[ProxyResponse] + typing.AsyncIterator[AsyncHttpResponse[typing.AsyncIterator[bytes]]] proxy request successful """ - _response = await self._client_wrapper.httpx_client.request( + async with self._client_wrapper.httpx_client.stream( f"v1/connect/{jsonable_encoder(self._client_wrapper._project_id)}/proxy/{jsonable_encoder(url_64)}", method="POST", params={ @@ -497,35 +511,38 @@ async def post( }, request_options=request_options, omit=OMIT, - ) - try: - if _response is None or not _response.text.strip(): - return AsyncHttpResponse(response=_response, data=None) - if 200 <= _response.status_code < 300: - _data = typing.cast( - ProxyResponse, - parse_obj_as( - type_=ProxyResponse, # type: ignore - object_=_response.json(), - ), - ) - return AsyncHttpResponse(response=_response, data=_data) - if _response.status_code == 429: - raise TooManyRequestsError( - headers=dict(_response.headers), - body=typing.cast( - typing.Optional[typing.Any], - parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore - object_=_response.json(), - ), - ), - ) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - + ) as _response: + + async def _stream() -> AsyncHttpResponse[typing.AsyncIterator[bytes]]: + try: + if 200 <= _response.status_code < 300: + _chunk_size = request_options.get("chunk_size", None) if request_options is not None else None + return AsyncHttpResponse( + response=_response, + data=(_chunk async for _chunk in _response.aiter_bytes(chunk_size=_chunk_size)), + ) + await _response.aread() + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + typing.Optional[typing.Any], + parse_obj_as( + type_=typing.Optional[typing.Any], # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.text + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + yield await _stream() + + @contextlib.asynccontextmanager async def put( self, url_64: str, @@ -534,7 +551,7 @@ async def put( account_id: str, request: typing.Dict[str, typing.Optional[typing.Any]], request_options: typing.Optional[RequestOptions] = None, - ) -> AsyncHttpResponse[ProxyResponse]: + ) -> typing.AsyncIterator[AsyncHttpResponse[typing.AsyncIterator[bytes]]]: """ Forward an authenticated PUT request to an external API using an external user's account credentials @@ -552,14 +569,14 @@ async def put( request : typing.Dict[str, typing.Optional[typing.Any]] request_options : typing.Optional[RequestOptions] - Request-specific configuration. + Request-specific configuration. You can pass in configuration such as `chunk_size`, and more to customize the request and response. Returns ------- - AsyncHttpResponse[ProxyResponse] + typing.AsyncIterator[AsyncHttpResponse[typing.AsyncIterator[bytes]]] proxy request successful """ - _response = await self._client_wrapper.httpx_client.request( + async with self._client_wrapper.httpx_client.stream( f"v1/connect/{jsonable_encoder(self._client_wrapper._project_id)}/proxy/{jsonable_encoder(url_64)}", method="PUT", params={ @@ -572,35 +589,38 @@ async def put( }, request_options=request_options, omit=OMIT, - ) - try: - if _response is None or not _response.text.strip(): - return AsyncHttpResponse(response=_response, data=None) - if 200 <= _response.status_code < 300: - _data = typing.cast( - ProxyResponse, - parse_obj_as( - type_=ProxyResponse, # type: ignore - object_=_response.json(), - ), - ) - return AsyncHttpResponse(response=_response, data=_data) - if _response.status_code == 429: - raise TooManyRequestsError( - headers=dict(_response.headers), - body=typing.cast( - typing.Optional[typing.Any], - parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore - object_=_response.json(), - ), - ), - ) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - + ) as _response: + + async def _stream() -> AsyncHttpResponse[typing.AsyncIterator[bytes]]: + try: + if 200 <= _response.status_code < 300: + _chunk_size = request_options.get("chunk_size", None) if request_options is not None else None + return AsyncHttpResponse( + response=_response, + data=(_chunk async for _chunk in _response.aiter_bytes(chunk_size=_chunk_size)), + ) + await _response.aread() + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + typing.Optional[typing.Any], + parse_obj_as( + type_=typing.Optional[typing.Any], # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.text + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + yield await _stream() + + @contextlib.asynccontextmanager async def delete( self, url_64: str, @@ -608,7 +628,7 @@ async def delete( external_user_id: str, account_id: str, request_options: typing.Optional[RequestOptions] = None, - ) -> AsyncHttpResponse[ProxyResponse]: + ) -> typing.AsyncIterator[AsyncHttpResponse[typing.AsyncIterator[bytes]]]: """ Forward an authenticated DELETE request to an external API using an external user's account credentials @@ -624,14 +644,14 @@ async def delete( The account ID to use for authentication request_options : typing.Optional[RequestOptions] - Request-specific configuration. + Request-specific configuration. You can pass in configuration such as `chunk_size`, and more to customize the request and response. Returns ------- - AsyncHttpResponse[ProxyResponse] + typing.AsyncIterator[AsyncHttpResponse[typing.AsyncIterator[bytes]]] proxy request successful """ - _response = await self._client_wrapper.httpx_client.request( + async with self._client_wrapper.httpx_client.stream( f"v1/connect/{jsonable_encoder(self._client_wrapper._project_id)}/proxy/{jsonable_encoder(url_64)}", method="DELETE", params={ @@ -639,35 +659,38 @@ async def delete( "account_id": account_id, }, request_options=request_options, - ) - try: - if _response is None or not _response.text.strip(): - return AsyncHttpResponse(response=_response, data=None) - if 200 <= _response.status_code < 300: - _data = typing.cast( - ProxyResponse, - parse_obj_as( - type_=ProxyResponse, # type: ignore - object_=_response.json(), - ), - ) - return AsyncHttpResponse(response=_response, data=_data) - if _response.status_code == 429: - raise TooManyRequestsError( - headers=dict(_response.headers), - body=typing.cast( - typing.Optional[typing.Any], - parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore - object_=_response.json(), - ), - ), - ) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - + ) as _response: + + async def _stream() -> AsyncHttpResponse[typing.AsyncIterator[bytes]]: + try: + if 200 <= _response.status_code < 300: + _chunk_size = request_options.get("chunk_size", None) if request_options is not None else None + return AsyncHttpResponse( + response=_response, + data=(_chunk async for _chunk in _response.aiter_bytes(chunk_size=_chunk_size)), + ) + await _response.aread() + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + typing.Optional[typing.Any], + parse_obj_as( + type_=typing.Optional[typing.Any], # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.text + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + yield await _stream() + + @contextlib.asynccontextmanager async def patch( self, url_64: str, @@ -676,7 +699,7 @@ async def patch( account_id: str, request: typing.Dict[str, typing.Optional[typing.Any]], request_options: typing.Optional[RequestOptions] = None, - ) -> AsyncHttpResponse[ProxyResponse]: + ) -> typing.AsyncIterator[AsyncHttpResponse[typing.AsyncIterator[bytes]]]: """ Forward an authenticated PATCH request to an external API using an external user's account credentials @@ -694,14 +717,14 @@ async def patch( request : typing.Dict[str, typing.Optional[typing.Any]] request_options : typing.Optional[RequestOptions] - Request-specific configuration. + Request-specific configuration. You can pass in configuration such as `chunk_size`, and more to customize the request and response. Returns ------- - AsyncHttpResponse[ProxyResponse] + typing.AsyncIterator[AsyncHttpResponse[typing.AsyncIterator[bytes]]] proxy request successful """ - _response = await self._client_wrapper.httpx_client.request( + async with self._client_wrapper.httpx_client.stream( f"v1/connect/{jsonable_encoder(self._client_wrapper._project_id)}/proxy/{jsonable_encoder(url_64)}", method="PATCH", params={ @@ -714,31 +737,33 @@ async def patch( }, request_options=request_options, omit=OMIT, - ) - try: - if _response is None or not _response.text.strip(): - return AsyncHttpResponse(response=_response, data=None) - if 200 <= _response.status_code < 300: - _data = typing.cast( - ProxyResponse, - parse_obj_as( - type_=ProxyResponse, # type: ignore - object_=_response.json(), - ), - ) - return AsyncHttpResponse(response=_response, data=_data) - if _response.status_code == 429: - raise TooManyRequestsError( - headers=dict(_response.headers), - body=typing.cast( - typing.Optional[typing.Any], - parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore - object_=_response.json(), - ), - ), - ) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + ) as _response: + + async def _stream() -> AsyncHttpResponse[typing.AsyncIterator[bytes]]: + try: + if 200 <= _response.status_code < 300: + _chunk_size = request_options.get("chunk_size", None) if request_options is not None else None + return AsyncHttpResponse( + response=_response, + data=(_chunk async for _chunk in _response.aiter_bytes(chunk_size=_chunk_size)), + ) + await _response.aread() + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + typing.Optional[typing.Any], + parse_obj_as( + type_=typing.Optional[typing.Any], # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.text + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + yield await _stream() diff --git a/src/pipedream/tokens/client.py b/src/pipedream/tokens/client.py index f37eccf..cbe1c64 100644 --- a/src/pipedream/tokens/client.py +++ b/src/pipedream/tokens/client.py @@ -132,7 +132,6 @@ def validate( client.tokens.validate( ctok="ctok", app_id="app_id", - oauth_app_id="oauth_app_id", ) """ _response = self._raw_client.validate( @@ -273,7 +272,6 @@ async def main() -> None: await client.tokens.validate( ctok="ctok", app_id="app_id", - oauth_app_id="oauth_app_id", ) diff --git a/src/pipedream/triggers/client.py b/src/pipedream/triggers/client.py index 85a6646..17e518e 100644 --- a/src/pipedream/triggers/client.py +++ b/src/pipedream/triggers/client.py @@ -79,13 +79,7 @@ def list( client_id="YOUR_CLIENT_ID", client_secret="YOUR_CLIENT_SECRET", ) - response = client.triggers.list( - after="after", - before="before", - limit=1, - q="q", - app="app", - ) + response = client.triggers.list() for item in response: yield item # alternatively, you can paginate page-by-page @@ -305,6 +299,7 @@ def deploy( dynamic_props_id: typing.Optional[str] = OMIT, workflow_id: typing.Optional[str] = OMIT, webhook_url: typing.Optional[str] = OMIT, + emit_on_deploy: typing.Optional[bool] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> Emitter: """ @@ -332,6 +327,9 @@ def deploy( webhook_url : typing.Optional[str] Optional webhook URL to receive trigger events + emit_on_deploy : typing.Optional[bool] + Whether the trigger should emit events during the deploy hook execution. Defaults to true if not specified. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -363,6 +361,7 @@ def deploy( dynamic_props_id=dynamic_props_id, workflow_id=workflow_id, webhook_url=webhook_url, + emit_on_deploy=emit_on_deploy, request_options=request_options, ) return _response.data @@ -436,13 +435,7 @@ async def list( async def main() -> None: - response = await client.triggers.list( - after="after", - before="before", - limit=1, - q="q", - app="app", - ) + response = await client.triggers.list() async for item in response: yield item @@ -690,6 +683,7 @@ async def deploy( dynamic_props_id: typing.Optional[str] = OMIT, workflow_id: typing.Optional[str] = OMIT, webhook_url: typing.Optional[str] = OMIT, + emit_on_deploy: typing.Optional[bool] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> Emitter: """ @@ -717,6 +711,9 @@ async def deploy( webhook_url : typing.Optional[str] Optional webhook URL to receive trigger events + emit_on_deploy : typing.Optional[bool] + Whether the trigger should emit events during the deploy hook execution. Defaults to true if not specified. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -756,6 +753,7 @@ async def main() -> None: dynamic_props_id=dynamic_props_id, workflow_id=workflow_id, webhook_url=webhook_url, + emit_on_deploy=emit_on_deploy, request_options=request_options, ) return _response.data diff --git a/src/pipedream/triggers/raw_client.py b/src/pipedream/triggers/raw_client.py index 0d5da95..93ac6c6 100644 --- a/src/pipedream/triggers/raw_client.py +++ b/src/pipedream/triggers/raw_client.py @@ -384,6 +384,7 @@ def deploy( dynamic_props_id: typing.Optional[str] = OMIT, workflow_id: typing.Optional[str] = OMIT, webhook_url: typing.Optional[str] = OMIT, + emit_on_deploy: typing.Optional[bool] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> HttpResponse[Emitter]: """ @@ -411,6 +412,9 @@ def deploy( webhook_url : typing.Optional[str] Optional webhook URL to receive trigger events + emit_on_deploy : typing.Optional[bool] + Whether the trigger should emit events during the deploy hook execution. Defaults to true if not specified. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -432,6 +436,7 @@ def deploy( "dynamic_props_id": dynamic_props_id, "workflow_id": workflow_id, "webhook_url": webhook_url, + "emit_on_deploy": emit_on_deploy, }, headers={ "content-type": "application/json", @@ -829,6 +834,7 @@ async def deploy( dynamic_props_id: typing.Optional[str] = OMIT, workflow_id: typing.Optional[str] = OMIT, webhook_url: typing.Optional[str] = OMIT, + emit_on_deploy: typing.Optional[bool] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> AsyncHttpResponse[Emitter]: """ @@ -856,6 +862,9 @@ async def deploy( webhook_url : typing.Optional[str] Optional webhook URL to receive trigger events + emit_on_deploy : typing.Optional[bool] + Whether the trigger should emit events during the deploy hook execution. Defaults to true if not specified. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -877,6 +886,7 @@ async def deploy( "dynamic_props_id": dynamic_props_id, "workflow_id": workflow_id, "webhook_url": webhook_url, + "emit_on_deploy": emit_on_deploy, }, headers={ "content-type": "application/json", diff --git a/src/pipedream/types/__init__.py b/src/pipedream/types/__init__.py index 1484a49..6063ee7 100644 --- a/src/pipedream/types/__init__.py +++ b/src/pipedream/types/__init__.py @@ -102,6 +102,7 @@ from .prop_option_nested import PropOptionNested from .prop_option_value import PropOptionValue from .proxy_response import ProxyResponse + from .proxy_response_binary import ProxyResponseBinary from .reload_props_opts import ReloadPropsOpts from .reload_props_response import ReloadPropsResponse from .run_action_opts_stash_id import RunActionOptsStashId @@ -214,6 +215,7 @@ "PropOptionNested": ".prop_option_nested", "PropOptionValue": ".prop_option_value", "ProxyResponse": ".proxy_response", + "ProxyResponseBinary": ".proxy_response_binary", "ReloadPropsOpts": ".reload_props_opts", "ReloadPropsResponse": ".reload_props_response", "RunActionOptsStashId": ".run_action_opts_stash_id", @@ -350,6 +352,7 @@ def __dir__(): "PropOptionNested", "PropOptionValue", "ProxyResponse", + "ProxyResponseBinary", "ReloadPropsOpts", "ReloadPropsResponse", "RunActionOptsStashId", diff --git a/src/pipedream/types/deployed_component.py b/src/pipedream/types/deployed_component.py index 39ba6c2..b628be7 100644 --- a/src/pipedream/types/deployed_component.py +++ b/src/pipedream/types/deployed_component.py @@ -69,6 +69,11 @@ class DeployedComponent(UniversalBaseModel): Callback observations for the deployed component """ + emit_on_deploy: typing.Optional[bool] = pydantic.Field(default=None) + """ + Whether the trigger emits events during the deploy hook execution. When false, the $emit function is disabled during deploy hook execution. Defaults to true. + """ + if IS_PYDANTIC_V2: model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 else: diff --git a/src/pipedream/types/emitter.py b/src/pipedream/types/emitter.py index cd4962f..9f1c407 100644 --- a/src/pipedream/types/emitter.py +++ b/src/pipedream/types/emitter.py @@ -28,6 +28,7 @@ class Emitter_DeployedComponent(UniversalBaseModel): name: str name_slug: str callback_observations: typing.Optional[typing.Optional[typing.Any]] = None + emit_on_deploy: typing.Optional[bool] = None if IS_PYDANTIC_V2: model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 diff --git a/src/pipedream/types/proxy_response_binary.py b/src/pipedream/types/proxy_response_binary.py new file mode 100644 index 0000000..e799429 --- /dev/null +++ b/src/pipedream/types/proxy_response_binary.py @@ -0,0 +1,3 @@ +# This file was auto-generated by Fern from our API Definition. + +ProxyResponseBinary = str From 5c550fae2f231391b937fab6772d1a18217d42a6 Mon Sep 17 00:00:00 2001 From: Jay Vercellone Date: Tue, 25 Nov 2025 17:11:24 -0300 Subject: [PATCH 02/13] Support both JSON and other content --- src/pipedream/proxy/raw_client.py | 642 ++++++++++++++++++------------ 1 file changed, 391 insertions(+), 251 deletions(-) diff --git a/src/pipedream/proxy/raw_client.py b/src/pipedream/proxy/raw_client.py index 7c4d061..76e3d4f 100644 --- a/src/pipedream/proxy/raw_client.py +++ b/src/pipedream/proxy/raw_client.py @@ -1,6 +1,5 @@ # This file was auto-generated by Fern from our API Definition. -import contextlib import typing from json.decoder import JSONDecodeError @@ -11,6 +10,7 @@ from ..core.pydantic_utilities import parse_obj_as from ..core.request_options import RequestOptions from ..errors.too_many_requests_error import TooManyRequestsError +from ..types.proxy_response import ProxyResponse # this is used as the default value for optional parameters OMIT = typing.cast(typing.Any, ...) @@ -20,7 +20,6 @@ class RawProxyClient: def __init__(self, *, client_wrapper: SyncClientWrapper): self._client_wrapper = client_wrapper - @contextlib.contextmanager def get( self, url_64: str, @@ -28,7 +27,7 @@ def get( external_user_id: str, account_id: str, request_options: typing.Optional[RequestOptions] = None, - ) -> typing.Iterator[HttpResponse[typing.Iterator[bytes]]]: + ) -> HttpResponse[typing.Union[ProxyResponse, typing.Iterator[bytes]]]: """ Forward an authenticated GET request to an external API using an external user's account credentials @@ -48,7 +47,7 @@ def get( Returns ------- - typing.Iterator[HttpResponse[typing.Iterator[bytes]]] + HttpResponse[typing.Union[ProxyResponse, typing.Iterator[bytes]]] proxy request successful """ with self._client_wrapper.httpx_client.stream( @@ -60,36 +59,50 @@ def get( }, request_options=request_options, ) as _response: - - def _stream() -> HttpResponse[typing.Iterator[bytes]]: - try: - if 200 <= _response.status_code < 300: + try: + if 200 <= _response.status_code < 300: + # Check Content-Type to determine how to handle the response + content_type = _response.headers.get("content-type", "").lower() + is_json = "application/json" in content_type or not content_type + + if is_json: + # Parse as JSON + _response.read() + if not _response.text.strip(): + return HttpResponse(response=_response, data=None) + _data = typing.cast( + ProxyResponse, + parse_obj_as( + type_=ProxyResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + else: + # Stream binary content _chunk_size = request_options.get("chunk_size", None) if request_options is not None else None return HttpResponse( response=_response, data=(_chunk for _chunk in _response.iter_bytes(chunk_size=_chunk_size)) ) - _response.read() - if _response.status_code == 429: - raise TooManyRequestsError( - headers=dict(_response.headers), - body=typing.cast( - typing.Optional[typing.Any], - parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore - object_=_response.json(), - ), + _response.read() + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + typing.Optional[typing.Any], + parse_obj_as( + type_=typing.Optional[typing.Any], # type: ignore + object_=_response.json(), ), - ) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError( - status_code=_response.status_code, headers=dict(_response.headers), body=_response.text + ), ) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.text + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - yield _stream() - - @contextlib.contextmanager def post( self, url_64: str, @@ -98,7 +111,7 @@ def post( account_id: str, request: typing.Dict[str, typing.Optional[typing.Any]], request_options: typing.Optional[RequestOptions] = None, - ) -> typing.Iterator[HttpResponse[typing.Iterator[bytes]]]: + ) -> HttpResponse[typing.Union[ProxyResponse, typing.Iterator[bytes]]]: """ Forward an authenticated POST request to an external API using an external user's account credentials @@ -120,7 +133,7 @@ def post( Returns ------- - typing.Iterator[HttpResponse[typing.Iterator[bytes]]] + HttpResponse[typing.Union[ProxyResponse, typing.Iterator[bytes]]] proxy request successful """ with self._client_wrapper.httpx_client.stream( @@ -137,36 +150,50 @@ def post( request_options=request_options, omit=OMIT, ) as _response: - - def _stream() -> HttpResponse[typing.Iterator[bytes]]: - try: - if 200 <= _response.status_code < 300: + try: + if 200 <= _response.status_code < 300: + # Check Content-Type to determine how to handle the response + content_type = _response.headers.get("content-type", "").lower() + is_json = "application/json" in content_type or not content_type + + if is_json: + # Parse as JSON + _response.read() + if not _response.text.strip(): + return HttpResponse(response=_response, data=None) + _data = typing.cast( + ProxyResponse, + parse_obj_as( + type_=ProxyResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + else: + # Stream binary content _chunk_size = request_options.get("chunk_size", None) if request_options is not None else None return HttpResponse( response=_response, data=(_chunk for _chunk in _response.iter_bytes(chunk_size=_chunk_size)) ) - _response.read() - if _response.status_code == 429: - raise TooManyRequestsError( - headers=dict(_response.headers), - body=typing.cast( - typing.Optional[typing.Any], - parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore - object_=_response.json(), - ), + _response.read() + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + typing.Optional[typing.Any], + parse_obj_as( + type_=typing.Optional[typing.Any], # type: ignore + object_=_response.json(), ), - ) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError( - status_code=_response.status_code, headers=dict(_response.headers), body=_response.text + ), ) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - - yield _stream() + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.text + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - @contextlib.contextmanager def put( self, url_64: str, @@ -175,7 +202,7 @@ def put( account_id: str, request: typing.Dict[str, typing.Optional[typing.Any]], request_options: typing.Optional[RequestOptions] = None, - ) -> typing.Iterator[HttpResponse[typing.Iterator[bytes]]]: + ) -> HttpResponse[typing.Union[ProxyResponse, typing.Iterator[bytes]]]: """ Forward an authenticated PUT request to an external API using an external user's account credentials @@ -197,7 +224,7 @@ def put( Returns ------- - typing.Iterator[HttpResponse[typing.Iterator[bytes]]] + HttpResponse[typing.Union[ProxyResponse, typing.Iterator[bytes]]] proxy request successful """ with self._client_wrapper.httpx_client.stream( @@ -214,36 +241,50 @@ def put( request_options=request_options, omit=OMIT, ) as _response: - - def _stream() -> HttpResponse[typing.Iterator[bytes]]: - try: - if 200 <= _response.status_code < 300: + try: + if 200 <= _response.status_code < 300: + # Check Content-Type to determine how to handle the response + content_type = _response.headers.get("content-type", "").lower() + is_json = "application/json" in content_type or not content_type + + if is_json: + # Parse as JSON + _response.read() + if not _response.text.strip(): + return HttpResponse(response=_response, data=None) + _data = typing.cast( + ProxyResponse, + parse_obj_as( + type_=ProxyResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + else: + # Stream binary content _chunk_size = request_options.get("chunk_size", None) if request_options is not None else None return HttpResponse( response=_response, data=(_chunk for _chunk in _response.iter_bytes(chunk_size=_chunk_size)) ) - _response.read() - if _response.status_code == 429: - raise TooManyRequestsError( - headers=dict(_response.headers), - body=typing.cast( - typing.Optional[typing.Any], - parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore - object_=_response.json(), - ), + _response.read() + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + typing.Optional[typing.Any], + parse_obj_as( + type_=typing.Optional[typing.Any], # type: ignore + object_=_response.json(), ), - ) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError( - status_code=_response.status_code, headers=dict(_response.headers), body=_response.text + ), ) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - - yield _stream() + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.text + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - @contextlib.contextmanager def delete( self, url_64: str, @@ -251,7 +292,7 @@ def delete( external_user_id: str, account_id: str, request_options: typing.Optional[RequestOptions] = None, - ) -> typing.Iterator[HttpResponse[typing.Iterator[bytes]]]: + ) -> HttpResponse[typing.Union[ProxyResponse, typing.Iterator[bytes]]]: """ Forward an authenticated DELETE request to an external API using an external user's account credentials @@ -271,7 +312,7 @@ def delete( Returns ------- - typing.Iterator[HttpResponse[typing.Iterator[bytes]]] + HttpResponse[typing.Union[ProxyResponse, typing.Iterator[bytes]]] proxy request successful """ with self._client_wrapper.httpx_client.stream( @@ -283,36 +324,50 @@ def delete( }, request_options=request_options, ) as _response: - - def _stream() -> HttpResponse[typing.Iterator[bytes]]: - try: - if 200 <= _response.status_code < 300: + try: + if 200 <= _response.status_code < 300: + # Check Content-Type to determine how to handle the response + content_type = _response.headers.get("content-type", "").lower() + is_json = "application/json" in content_type or not content_type + + if is_json: + # Parse as JSON + _response.read() + if not _response.text.strip(): + return HttpResponse(response=_response, data=None) + _data = typing.cast( + ProxyResponse, + parse_obj_as( + type_=ProxyResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + else: + # Stream binary content _chunk_size = request_options.get("chunk_size", None) if request_options is not None else None return HttpResponse( response=_response, data=(_chunk for _chunk in _response.iter_bytes(chunk_size=_chunk_size)) ) - _response.read() - if _response.status_code == 429: - raise TooManyRequestsError( - headers=dict(_response.headers), - body=typing.cast( - typing.Optional[typing.Any], - parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore - object_=_response.json(), - ), + _response.read() + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + typing.Optional[typing.Any], + parse_obj_as( + type_=typing.Optional[typing.Any], # type: ignore + object_=_response.json(), ), - ) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError( - status_code=_response.status_code, headers=dict(_response.headers), body=_response.text + ), ) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.text + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - yield _stream() - - @contextlib.contextmanager def patch( self, url_64: str, @@ -321,7 +376,7 @@ def patch( account_id: str, request: typing.Dict[str, typing.Optional[typing.Any]], request_options: typing.Optional[RequestOptions] = None, - ) -> typing.Iterator[HttpResponse[typing.Iterator[bytes]]]: + ) -> HttpResponse[typing.Union[ProxyResponse, typing.Iterator[bytes]]]: """ Forward an authenticated PATCH request to an external API using an external user's account credentials @@ -343,7 +398,7 @@ def patch( Returns ------- - typing.Iterator[HttpResponse[typing.Iterator[bytes]]] + HttpResponse[typing.Union[ProxyResponse, typing.Iterator[bytes]]] proxy request successful """ with self._client_wrapper.httpx_client.stream( @@ -360,41 +415,55 @@ def patch( request_options=request_options, omit=OMIT, ) as _response: - - def _stream() -> HttpResponse[typing.Iterator[bytes]]: - try: - if 200 <= _response.status_code < 300: + try: + if 200 <= _response.status_code < 300: + # Check Content-Type to determine how to handle the response + content_type = _response.headers.get("content-type", "").lower() + is_json = "application/json" in content_type or not content_type + + if is_json: + # Parse as JSON + _response.read() + if not _response.text.strip(): + return HttpResponse(response=_response, data=None) + _data = typing.cast( + ProxyResponse, + parse_obj_as( + type_=ProxyResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + else: + # Stream binary content _chunk_size = request_options.get("chunk_size", None) if request_options is not None else None return HttpResponse( response=_response, data=(_chunk for _chunk in _response.iter_bytes(chunk_size=_chunk_size)) ) - _response.read() - if _response.status_code == 429: - raise TooManyRequestsError( - headers=dict(_response.headers), - body=typing.cast( - typing.Optional[typing.Any], - parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore - object_=_response.json(), - ), + _response.read() + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + typing.Optional[typing.Any], + parse_obj_as( + type_=typing.Optional[typing.Any], # type: ignore + object_=_response.json(), ), - ) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError( - status_code=_response.status_code, headers=dict(_response.headers), body=_response.text + ), ) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - - yield _stream() + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.text + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) class AsyncRawProxyClient: def __init__(self, *, client_wrapper: AsyncClientWrapper): self._client_wrapper = client_wrapper - @contextlib.asynccontextmanager async def get( self, url_64: str, @@ -402,7 +471,7 @@ async def get( external_user_id: str, account_id: str, request_options: typing.Optional[RequestOptions] = None, - ) -> typing.AsyncIterator[AsyncHttpResponse[typing.AsyncIterator[bytes]]]: + ) -> AsyncHttpResponse[typing.Union[ProxyResponse, typing.AsyncIterator[bytes]]]: """ Forward an authenticated GET request to an external API using an external user's account credentials @@ -422,7 +491,7 @@ async def get( Returns ------- - typing.AsyncIterator[AsyncHttpResponse[typing.AsyncIterator[bytes]]] + AsyncHttpResponse[typing.Union[ProxyResponse, typing.AsyncIterator[bytes]]] proxy request successful """ async with self._client_wrapper.httpx_client.stream( @@ -434,37 +503,51 @@ async def get( }, request_options=request_options, ) as _response: - - async def _stream() -> AsyncHttpResponse[typing.AsyncIterator[bytes]]: - try: - if 200 <= _response.status_code < 300: + try: + if 200 <= _response.status_code < 300: + # Check Content-Type to determine how to handle the response + content_type = _response.headers.get("content-type", "").lower() + is_json = "application/json" in content_type or not content_type + + if is_json: + # Parse as JSON + await _response.aread() + if not _response.text.strip(): + return AsyncHttpResponse(response=_response, data=None) + _data = typing.cast( + ProxyResponse, + parse_obj_as( + type_=ProxyResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + else: + # Stream binary content _chunk_size = request_options.get("chunk_size", None) if request_options is not None else None return AsyncHttpResponse( response=_response, data=(_chunk async for _chunk in _response.aiter_bytes(chunk_size=_chunk_size)), ) - await _response.aread() - if _response.status_code == 429: - raise TooManyRequestsError( - headers=dict(_response.headers), - body=typing.cast( - typing.Optional[typing.Any], - parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore - object_=_response.json(), - ), + await _response.aread() + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + typing.Optional[typing.Any], + parse_obj_as( + type_=typing.Optional[typing.Any], # type: ignore + object_=_response.json(), ), - ) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError( - status_code=_response.status_code, headers=dict(_response.headers), body=_response.text + ), ) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.text + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - yield await _stream() - - @contextlib.asynccontextmanager async def post( self, url_64: str, @@ -473,7 +556,7 @@ async def post( account_id: str, request: typing.Dict[str, typing.Optional[typing.Any]], request_options: typing.Optional[RequestOptions] = None, - ) -> typing.AsyncIterator[AsyncHttpResponse[typing.AsyncIterator[bytes]]]: + ) -> AsyncHttpResponse[typing.Union[ProxyResponse, typing.AsyncIterator[bytes]]]: """ Forward an authenticated POST request to an external API using an external user's account credentials @@ -495,7 +578,7 @@ async def post( Returns ------- - typing.AsyncIterator[AsyncHttpResponse[typing.AsyncIterator[bytes]]] + AsyncHttpResponse[typing.Union[ProxyResponse, typing.AsyncIterator[bytes]]] proxy request successful """ async with self._client_wrapper.httpx_client.stream( @@ -512,37 +595,51 @@ async def post( request_options=request_options, omit=OMIT, ) as _response: - - async def _stream() -> AsyncHttpResponse[typing.AsyncIterator[bytes]]: - try: - if 200 <= _response.status_code < 300: + try: + if 200 <= _response.status_code < 300: + # Check Content-Type to determine how to handle the response + content_type = _response.headers.get("content-type", "").lower() + is_json = "application/json" in content_type or not content_type + + if is_json: + # Parse as JSON + await _response.aread() + if not _response.text.strip(): + return AsyncHttpResponse(response=_response, data=None) + _data = typing.cast( + ProxyResponse, + parse_obj_as( + type_=ProxyResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + else: + # Stream binary content _chunk_size = request_options.get("chunk_size", None) if request_options is not None else None return AsyncHttpResponse( response=_response, data=(_chunk async for _chunk in _response.aiter_bytes(chunk_size=_chunk_size)), ) - await _response.aread() - if _response.status_code == 429: - raise TooManyRequestsError( - headers=dict(_response.headers), - body=typing.cast( - typing.Optional[typing.Any], - parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore - object_=_response.json(), - ), + await _response.aread() + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + typing.Optional[typing.Any], + parse_obj_as( + type_=typing.Optional[typing.Any], # type: ignore + object_=_response.json(), ), - ) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError( - status_code=_response.status_code, headers=dict(_response.headers), body=_response.text + ), ) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - - yield await _stream() + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.text + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - @contextlib.asynccontextmanager async def put( self, url_64: str, @@ -551,7 +648,7 @@ async def put( account_id: str, request: typing.Dict[str, typing.Optional[typing.Any]], request_options: typing.Optional[RequestOptions] = None, - ) -> typing.AsyncIterator[AsyncHttpResponse[typing.AsyncIterator[bytes]]]: + ) -> AsyncHttpResponse[typing.Union[ProxyResponse, typing.AsyncIterator[bytes]]]: """ Forward an authenticated PUT request to an external API using an external user's account credentials @@ -573,7 +670,7 @@ async def put( Returns ------- - typing.AsyncIterator[AsyncHttpResponse[typing.AsyncIterator[bytes]]] + AsyncHttpResponse[typing.Union[ProxyResponse, typing.AsyncIterator[bytes]]] proxy request successful """ async with self._client_wrapper.httpx_client.stream( @@ -590,37 +687,51 @@ async def put( request_options=request_options, omit=OMIT, ) as _response: - - async def _stream() -> AsyncHttpResponse[typing.AsyncIterator[bytes]]: - try: - if 200 <= _response.status_code < 300: + try: + if 200 <= _response.status_code < 300: + # Check Content-Type to determine how to handle the response + content_type = _response.headers.get("content-type", "").lower() + is_json = "application/json" in content_type or not content_type + + if is_json: + # Parse as JSON + await _response.aread() + if not _response.text.strip(): + return AsyncHttpResponse(response=_response, data=None) + _data = typing.cast( + ProxyResponse, + parse_obj_as( + type_=ProxyResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + else: + # Stream binary content _chunk_size = request_options.get("chunk_size", None) if request_options is not None else None return AsyncHttpResponse( response=_response, data=(_chunk async for _chunk in _response.aiter_bytes(chunk_size=_chunk_size)), ) - await _response.aread() - if _response.status_code == 429: - raise TooManyRequestsError( - headers=dict(_response.headers), - body=typing.cast( - typing.Optional[typing.Any], - parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore - object_=_response.json(), - ), + await _response.aread() + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + typing.Optional[typing.Any], + parse_obj_as( + type_=typing.Optional[typing.Any], # type: ignore + object_=_response.json(), ), - ) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError( - status_code=_response.status_code, headers=dict(_response.headers), body=_response.text + ), ) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - - yield await _stream() + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.text + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - @contextlib.asynccontextmanager async def delete( self, url_64: str, @@ -628,7 +739,7 @@ async def delete( external_user_id: str, account_id: str, request_options: typing.Optional[RequestOptions] = None, - ) -> typing.AsyncIterator[AsyncHttpResponse[typing.AsyncIterator[bytes]]]: + ) -> AsyncHttpResponse[typing.Union[ProxyResponse, typing.AsyncIterator[bytes]]]: """ Forward an authenticated DELETE request to an external API using an external user's account credentials @@ -648,7 +759,7 @@ async def delete( Returns ------- - typing.AsyncIterator[AsyncHttpResponse[typing.AsyncIterator[bytes]]] + AsyncHttpResponse[typing.Union[ProxyResponse, typing.AsyncIterator[bytes]]] proxy request successful """ async with self._client_wrapper.httpx_client.stream( @@ -660,37 +771,51 @@ async def delete( }, request_options=request_options, ) as _response: - - async def _stream() -> AsyncHttpResponse[typing.AsyncIterator[bytes]]: - try: - if 200 <= _response.status_code < 300: + try: + if 200 <= _response.status_code < 300: + # Check Content-Type to determine how to handle the response + content_type = _response.headers.get("content-type", "").lower() + is_json = "application/json" in content_type or not content_type + + if is_json: + # Parse as JSON + await _response.aread() + if not _response.text.strip(): + return AsyncHttpResponse(response=_response, data=None) + _data = typing.cast( + ProxyResponse, + parse_obj_as( + type_=ProxyResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + else: + # Stream binary content _chunk_size = request_options.get("chunk_size", None) if request_options is not None else None return AsyncHttpResponse( response=_response, data=(_chunk async for _chunk in _response.aiter_bytes(chunk_size=_chunk_size)), ) - await _response.aread() - if _response.status_code == 429: - raise TooManyRequestsError( - headers=dict(_response.headers), - body=typing.cast( - typing.Optional[typing.Any], - parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore - object_=_response.json(), - ), + await _response.aread() + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + typing.Optional[typing.Any], + parse_obj_as( + type_=typing.Optional[typing.Any], # type: ignore + object_=_response.json(), ), - ) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError( - status_code=_response.status_code, headers=dict(_response.headers), body=_response.text + ), ) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.text + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - yield await _stream() - - @contextlib.asynccontextmanager async def patch( self, url_64: str, @@ -699,7 +824,7 @@ async def patch( account_id: str, request: typing.Dict[str, typing.Optional[typing.Any]], request_options: typing.Optional[RequestOptions] = None, - ) -> typing.AsyncIterator[AsyncHttpResponse[typing.AsyncIterator[bytes]]]: + ) -> AsyncHttpResponse[typing.Union[ProxyResponse, typing.AsyncIterator[bytes]]]: """ Forward an authenticated PATCH request to an external API using an external user's account credentials @@ -721,7 +846,7 @@ async def patch( Returns ------- - typing.AsyncIterator[AsyncHttpResponse[typing.AsyncIterator[bytes]]] + AsyncHttpResponse[typing.Union[ProxyResponse, typing.AsyncIterator[bytes]]] proxy request successful """ async with self._client_wrapper.httpx_client.stream( @@ -738,32 +863,47 @@ async def patch( request_options=request_options, omit=OMIT, ) as _response: - - async def _stream() -> AsyncHttpResponse[typing.AsyncIterator[bytes]]: - try: - if 200 <= _response.status_code < 300: + try: + if 200 <= _response.status_code < 300: + # Check Content-Type to determine how to handle the response + content_type = _response.headers.get("content-type", "").lower() + is_json = "application/json" in content_type or not content_type + + if is_json: + # Parse as JSON + await _response.aread() + if not _response.text.strip(): + return AsyncHttpResponse(response=_response, data=None) + _data = typing.cast( + ProxyResponse, + parse_obj_as( + type_=ProxyResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + else: + # Stream binary content _chunk_size = request_options.get("chunk_size", None) if request_options is not None else None return AsyncHttpResponse( response=_response, data=(_chunk async for _chunk in _response.aiter_bytes(chunk_size=_chunk_size)), ) - await _response.aread() - if _response.status_code == 429: - raise TooManyRequestsError( - headers=dict(_response.headers), - body=typing.cast( - typing.Optional[typing.Any], - parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore - object_=_response.json(), - ), + await _response.aread() + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + typing.Optional[typing.Any], + parse_obj_as( + type_=typing.Optional[typing.Any], # type: ignore + object_=_response.json(), ), - ) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError( - status_code=_response.status_code, headers=dict(_response.headers), body=_response.text + ), ) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) - - yield await _stream() + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.text + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) From b5302be3d9cb698d17beacd6d39583573d00d63c Mon Sep 17 00:00:00 2001 From: Jay Vercellone Date: Tue, 25 Nov 2025 17:11:36 -0300 Subject: [PATCH 03/13] Fix the annoying typo --- src/pipedream/client.py | 2 +- src/pipedream/core/client_wrapper.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pipedream/client.py b/src/pipedream/client.py index cfb7782..6f4accc 100644 --- a/src/pipedream/client.py +++ b/src/pipedream/client.py @@ -6,7 +6,7 @@ import typing import httpx -from ._.types.project_environment import ProjectEnvironment +from .types.project_environment import ProjectEnvironment from .core.api_error import ApiError from .core.client_wrapper import AsyncClientWrapper, SyncClientWrapper from .core.oauth_token_provider import OAuthTokenProvider diff --git a/src/pipedream/core/client_wrapper.py b/src/pipedream/core/client_wrapper.py index 5792b1c..b75073a 100644 --- a/src/pipedream/core/client_wrapper.py +++ b/src/pipedream/core/client_wrapper.py @@ -3,7 +3,7 @@ import typing import httpx -from .._.types.project_environment import ProjectEnvironment +from ..types.project_environment import ProjectEnvironment from .http_client import AsyncHttpClient, HttpClient From 48a1822610c3175e0b4a3472b24bea7aea317230 Mon Sep 17 00:00:00 2001 From: Jay Vercellone Date: Tue, 25 Nov 2025 17:27:46 -0300 Subject: [PATCH 04/13] Undo type renames --- src/pipedream/apps/__init__.py | 6 +++--- src/pipedream/apps/client.py | 20 +++++++++---------- src/pipedream/apps/raw_client.py | 20 +++++++++---------- src/pipedream/apps/types/__init__.py | 10 +++++----- ...py => apps_list_request_sort_direction.py} | 2 +- ...t_key.py => apps_list_request_sort_key.py} | 2 +- src/pipedream/proxy/client.py | 20 +++++++++---------- 7 files changed, 40 insertions(+), 40 deletions(-) rename src/pipedream/apps/types/{list_apps_request_sort_direction.py => apps_list_request_sort_direction.py} (61%) rename src/pipedream/apps/types/{list_apps_request_sort_key.py => apps_list_request_sort_key.py} (65%) diff --git a/src/pipedream/apps/__init__.py b/src/pipedream/apps/__init__.py index 4074914..3a7655e 100644 --- a/src/pipedream/apps/__init__.py +++ b/src/pipedream/apps/__init__.py @@ -6,8 +6,8 @@ from importlib import import_module if typing.TYPE_CHECKING: - from .types import ListAppsRequestSortDirection, ListAppsRequestSortKey -_dynamic_imports: typing.Dict[str, str] = {"ListAppsRequestSortDirection": ".types", "ListAppsRequestSortKey": ".types"} + from .types import AppsListRequestSortDirection, AppsListRequestSortKey +_dynamic_imports: typing.Dict[str, str] = {"AppsListRequestSortDirection": ".types", "AppsListRequestSortKey": ".types"} def __getattr__(attr_name: str) -> typing.Any: @@ -31,4 +31,4 @@ def __dir__(): return sorted(lazy_attrs) -__all__ = ["ListAppsRequestSortDirection", "ListAppsRequestSortKey"] +__all__ = ["AppsListRequestSortDirection", "AppsListRequestSortKey"] diff --git a/src/pipedream/apps/client.py b/src/pipedream/apps/client.py index 28ab702..5d44a36 100644 --- a/src/pipedream/apps/client.py +++ b/src/pipedream/apps/client.py @@ -8,8 +8,8 @@ from ..types.app import App from ..types.get_app_response import GetAppResponse from .raw_client import AsyncRawAppsClient, RawAppsClient -from .types.list_apps_request_sort_direction import ListAppsRequestSortDirection -from .types.list_apps_request_sort_key import ListAppsRequestSortKey +from .types.apps_list_request_sort_direction import AppsListRequestSortDirection +from .types.apps_list_request_sort_key import AppsListRequestSortKey class AppsClient: @@ -34,8 +34,8 @@ def list( before: typing.Optional[str] = None, limit: typing.Optional[int] = None, q: typing.Optional[str] = None, - sort_key: typing.Optional[ListAppsRequestSortKey] = None, - sort_direction: typing.Optional[ListAppsRequestSortDirection] = None, + sort_key: typing.Optional[AppsListRequestSortKey] = None, + sort_direction: typing.Optional[AppsListRequestSortDirection] = None, category_ids: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None, request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[App]: @@ -56,10 +56,10 @@ def list( q : typing.Optional[str] A search query to filter the apps - sort_key : typing.Optional[ListAppsRequestSortKey] + sort_key : typing.Optional[AppsListRequestSortKey] The key to sort the apps by - sort_direction : typing.Optional[ListAppsRequestSortDirection] + sort_direction : typing.Optional[AppsListRequestSortDirection] The direction to sort the apps category_ids : typing.Optional[typing.Union[str, typing.Sequence[str]]] @@ -158,8 +158,8 @@ async def list( before: typing.Optional[str] = None, limit: typing.Optional[int] = None, q: typing.Optional[str] = None, - sort_key: typing.Optional[ListAppsRequestSortKey] = None, - sort_direction: typing.Optional[ListAppsRequestSortDirection] = None, + sort_key: typing.Optional[AppsListRequestSortKey] = None, + sort_direction: typing.Optional[AppsListRequestSortDirection] = None, category_ids: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None, request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[App]: @@ -180,10 +180,10 @@ async def list( q : typing.Optional[str] A search query to filter the apps - sort_key : typing.Optional[ListAppsRequestSortKey] + sort_key : typing.Optional[AppsListRequestSortKey] The key to sort the apps by - sort_direction : typing.Optional[ListAppsRequestSortDirection] + sort_direction : typing.Optional[AppsListRequestSortDirection] The direction to sort the apps category_ids : typing.Optional[typing.Union[str, typing.Sequence[str]]] diff --git a/src/pipedream/apps/raw_client.py b/src/pipedream/apps/raw_client.py index c127e01..3bf84a7 100644 --- a/src/pipedream/apps/raw_client.py +++ b/src/pipedream/apps/raw_client.py @@ -13,8 +13,8 @@ from ..types.app import App from ..types.get_app_response import GetAppResponse from ..types.list_apps_response import ListAppsResponse -from .types.list_apps_request_sort_direction import ListAppsRequestSortDirection -from .types.list_apps_request_sort_key import ListAppsRequestSortKey +from .types.apps_list_request_sort_direction import AppsListRequestSortDirection +from .types.apps_list_request_sort_key import AppsListRequestSortKey class RawAppsClient: @@ -28,8 +28,8 @@ def list( before: typing.Optional[str] = None, limit: typing.Optional[int] = None, q: typing.Optional[str] = None, - sort_key: typing.Optional[ListAppsRequestSortKey] = None, - sort_direction: typing.Optional[ListAppsRequestSortDirection] = None, + sort_key: typing.Optional[AppsListRequestSortKey] = None, + sort_direction: typing.Optional[AppsListRequestSortDirection] = None, category_ids: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None, request_options: typing.Optional[RequestOptions] = None, ) -> SyncPager[App]: @@ -50,10 +50,10 @@ def list( q : typing.Optional[str] A search query to filter the apps - sort_key : typing.Optional[ListAppsRequestSortKey] + sort_key : typing.Optional[AppsListRequestSortKey] The key to sort the apps by - sort_direction : typing.Optional[ListAppsRequestSortDirection] + sort_direction : typing.Optional[AppsListRequestSortDirection] The direction to sort the apps category_ids : typing.Optional[typing.Union[str, typing.Sequence[str]]] @@ -165,8 +165,8 @@ async def list( before: typing.Optional[str] = None, limit: typing.Optional[int] = None, q: typing.Optional[str] = None, - sort_key: typing.Optional[ListAppsRequestSortKey] = None, - sort_direction: typing.Optional[ListAppsRequestSortDirection] = None, + sort_key: typing.Optional[AppsListRequestSortKey] = None, + sort_direction: typing.Optional[AppsListRequestSortDirection] = None, category_ids: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None, request_options: typing.Optional[RequestOptions] = None, ) -> AsyncPager[App]: @@ -187,10 +187,10 @@ async def list( q : typing.Optional[str] A search query to filter the apps - sort_key : typing.Optional[ListAppsRequestSortKey] + sort_key : typing.Optional[AppsListRequestSortKey] The key to sort the apps by - sort_direction : typing.Optional[ListAppsRequestSortDirection] + sort_direction : typing.Optional[AppsListRequestSortDirection] The direction to sort the apps category_ids : typing.Optional[typing.Union[str, typing.Sequence[str]]] diff --git a/src/pipedream/apps/types/__init__.py b/src/pipedream/apps/types/__init__.py index 526f29a..08c3a02 100644 --- a/src/pipedream/apps/types/__init__.py +++ b/src/pipedream/apps/types/__init__.py @@ -6,11 +6,11 @@ from importlib import import_module if typing.TYPE_CHECKING: - from .list_apps_request_sort_direction import ListAppsRequestSortDirection - from .list_apps_request_sort_key import ListAppsRequestSortKey + from .apps_list_request_sort_direction import AppsListRequestSortDirection + from .apps_list_request_sort_key import AppsListRequestSortKey _dynamic_imports: typing.Dict[str, str] = { - "ListAppsRequestSortDirection": ".list_apps_request_sort_direction", - "ListAppsRequestSortKey": ".list_apps_request_sort_key", + "AppsListRequestSortDirection": ".apps_list_request_sort_direction", + "AppsListRequestSortKey": ".apps_list_request_sort_key", } @@ -35,4 +35,4 @@ def __dir__(): return sorted(lazy_attrs) -__all__ = ["ListAppsRequestSortDirection", "ListAppsRequestSortKey"] +__all__ = ["AppsListRequestSortDirection", "AppsListRequestSortKey"] diff --git a/src/pipedream/apps/types/list_apps_request_sort_direction.py b/src/pipedream/apps/types/apps_list_request_sort_direction.py similarity index 61% rename from src/pipedream/apps/types/list_apps_request_sort_direction.py rename to src/pipedream/apps/types/apps_list_request_sort_direction.py index 4d86906..d841cc9 100644 --- a/src/pipedream/apps/types/list_apps_request_sort_direction.py +++ b/src/pipedream/apps/types/apps_list_request_sort_direction.py @@ -2,4 +2,4 @@ import typing -ListAppsRequestSortDirection = typing.Union[typing.Literal["asc", "desc"], typing.Any] +AppsListRequestSortDirection = typing.Union[typing.Literal["asc", "desc"], typing.Any] diff --git a/src/pipedream/apps/types/list_apps_request_sort_key.py b/src/pipedream/apps/types/apps_list_request_sort_key.py similarity index 65% rename from src/pipedream/apps/types/list_apps_request_sort_key.py rename to src/pipedream/apps/types/apps_list_request_sort_key.py index 6e9b8e2..ab4065d 100644 --- a/src/pipedream/apps/types/list_apps_request_sort_key.py +++ b/src/pipedream/apps/types/apps_list_request_sort_key.py @@ -2,4 +2,4 @@ import typing -ListAppsRequestSortKey = typing.Union[typing.Literal["name", "name_slug", "featured_weight"], typing.Any] +AppsListRequestSortKey = typing.Union[typing.Literal["name", "name_slug", "featured_weight"], typing.Any] diff --git a/src/pipedream/proxy/client.py b/src/pipedream/proxy/client.py index bdcb3f2..a817df2 100644 --- a/src/pipedream/proxy/client.py +++ b/src/pipedream/proxy/client.py @@ -86,7 +86,7 @@ def get( } request_options = RequestOptions( additional_headers=downstream_headers, - additional_query_parameters=params, + additional_query_parameters=params or {}, ) _response = self._raw_client.get( url_64, @@ -160,7 +160,7 @@ def post( } request_options = RequestOptions( additional_headers=downstream_headers, - additional_query_parameters=params, + additional_query_parameters=params or {}, ) _response = self._raw_client.post( url_64, @@ -235,7 +235,7 @@ def put( } request_options = RequestOptions( additional_headers=downstream_headers, - additional_query_parameters=params, + additional_query_parameters=params or {}, ) _response = self._raw_client.put( url_64, @@ -302,7 +302,7 @@ def delete( } request_options = RequestOptions( additional_headers=downstream_headers, - additional_query_parameters=params, + additional_query_parameters=params or {}, ) _response = self._raw_client.delete( url_64, @@ -376,7 +376,7 @@ def patch( } request_options = RequestOptions( additional_headers=downstream_headers, - additional_query_parameters=params, + additional_query_parameters=params or {}, ) _response = self._raw_client.patch( url_64, @@ -470,7 +470,7 @@ async def main() -> None: } request_options = RequestOptions( additional_headers=downstream_headers, - additional_query_parameters=params, + additional_query_parameters=params or {}, ) _response = await self._raw_client.get( url_64, @@ -551,7 +551,7 @@ async def main() -> None: } request_options = RequestOptions( additional_headers=downstream_headers, - additional_query_parameters=params, + additional_query_parameters=params or {}, ) _response = await self._raw_client.post( url_64, @@ -634,7 +634,7 @@ async def main() -> None: } request_options = RequestOptions( additional_headers=downstream_headers, - additional_query_parameters=params, + additional_query_parameters=params or {}, ) _response = await self._raw_client.put( url_64, @@ -709,7 +709,7 @@ async def main() -> None: } request_options = RequestOptions( additional_headers=downstream_headers, - additional_query_parameters=params, + additional_query_parameters=params or {}, ) _response = await self._raw_client.delete( url_64, @@ -790,7 +790,7 @@ async def main() -> None: } request_options = RequestOptions( additional_headers=downstream_headers, - additional_query_parameters=params, + additional_query_parameters=params or {}, ) _response = await self._raw_client.patch( url_64, From 59ab2419aaf4cb76956410084aec8a219c4ea92e Mon Sep 17 00:00:00 2001 From: Jay Vercellone Date: Wed, 26 Nov 2025 14:17:48 -0300 Subject: [PATCH 05/13] Update the types --- src/pipedream/proxy/client.py | 60 +++++++++++++++++------------------ 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/src/pipedream/proxy/client.py b/src/pipedream/proxy/client.py index a817df2..c27f667 100644 --- a/src/pipedream/proxy/client.py +++ b/src/pipedream/proxy/client.py @@ -37,7 +37,7 @@ def get( account_id: str, headers: typing.Optional[typing.Dict[str, typing.Any]] = None, params: typing.Optional[typing.Dict[str, typing.Any]] = None, - ) -> ProxyResponse: + ) -> typing.Union[ProxyResponse, typing.Iterator[bytes]]: """ Parameters ---------- @@ -58,8 +58,8 @@ def get( Returns ------- - ProxyResponse - proxy request successful + typing.Union[ProxyResponse, typing.Iterator[bytes]] + ProxyResponse for JSON content, Iterator[bytes] for binary content Examples -------- @@ -108,7 +108,7 @@ def post( typing.Optional[typing.Any], ]] = None, params: typing.Optional[typing.Dict[str, typing.Any]] = None, - ) -> ProxyResponse: + ) -> typing.Union[ProxyResponse, typing.Iterator[bytes]]: """ Parameters ---------- @@ -132,8 +132,8 @@ def post( Returns ------- - ProxyResponse - proxy request successful + typing.Union[ProxyResponse, typing.Iterator[bytes]] + ProxyResponse for JSON content, Iterator[bytes] for binary content Examples -------- @@ -183,7 +183,7 @@ def put( typing.Optional[typing.Any], ]] = None, params: typing.Optional[typing.Dict[str, typing.Any]] = None, - ) -> ProxyResponse: + ) -> typing.Union[ProxyResponse, typing.Iterator[bytes]]: """ Parameters ---------- @@ -207,8 +207,8 @@ def put( Returns ------- - ProxyResponse - proxy request successful + typing.Union[ProxyResponse, typing.Iterator[bytes]] + ProxyResponse for JSON content, Iterator[bytes] for binary content Examples -------- @@ -254,7 +254,7 @@ def delete( account_id: str, headers: typing.Optional[typing.Dict[str, typing.Any]] = None, params: typing.Optional[typing.Dict[str, typing.Any]] = None, - ) -> ProxyResponse: + ) -> typing.Union[ProxyResponse, typing.Iterator[bytes]]: """ Parameters ---------- @@ -275,8 +275,8 @@ def delete( Returns ------- - ProxyResponse - proxy request successful + typing.Union[ProxyResponse, typing.Iterator[bytes]] + ProxyResponse for JSON content, Iterator[bytes] for binary content Examples -------- @@ -324,7 +324,7 @@ def patch( typing.Optional[typing.Any], ]] = None, params: typing.Optional[typing.Dict[str, typing.Any]] = None, - ) -> ProxyResponse: + ) -> typing.Union[ProxyResponse, typing.Iterator[bytes]]: """ Parameters ---------- @@ -348,8 +348,8 @@ def patch( Returns ------- - ProxyResponse - proxy request successful + typing.Union[ProxyResponse, typing.Iterator[bytes]] + ProxyResponse for JSON content, Iterator[bytes] for binary content Examples -------- @@ -413,7 +413,7 @@ async def get( account_id: str, headers: typing.Optional[typing.Dict[str, typing.Any]] = None, params: typing.Optional[typing.Dict[str, typing.Any]] = None, - ) -> ProxyResponse: + ) -> typing.Union[ProxyResponse, typing.Iterator[bytes]]: """ Parameters ---------- @@ -434,8 +434,8 @@ async def get( Returns ------- - ProxyResponse - proxy request successful + typing.Union[ProxyResponse, typing.Iterator[bytes]] + ProxyResponse for JSON content, Iterator[bytes] for binary content Examples -------- @@ -491,7 +491,7 @@ async def post( typing.Optional[typing.Any], ]] = None, params: typing.Optional[typing.Dict[str, typing.Any]] = None, - ) -> ProxyResponse: + ) -> typing.Union[ProxyResponse, typing.Iterator[bytes]]: """ Parameters ---------- @@ -515,8 +515,8 @@ async def post( Returns ------- - ProxyResponse - proxy request successful + typing.Union[ProxyResponse, typing.Iterator[bytes]] + ProxyResponse for JSON content, Iterator[bytes] for binary content Examples -------- @@ -574,7 +574,7 @@ async def put( typing.Optional[typing.Any], ]] = None, params: typing.Optional[typing.Dict[str, typing.Any]] = None, - ) -> ProxyResponse: + ) -> typing.Union[ProxyResponse, typing.Iterator[bytes]]: """ Parameters ---------- @@ -598,8 +598,8 @@ async def put( Returns ------- - ProxyResponse - proxy request successful + typing.Union[ProxyResponse, typing.Iterator[bytes]] + ProxyResponse for JSON content, Iterator[bytes] for binary content Examples -------- @@ -653,7 +653,7 @@ async def delete( account_id: str, headers: typing.Optional[typing.Dict[str, typing.Any]] = None, params: typing.Optional[typing.Dict[str, typing.Any]] = None, - ) -> ProxyResponse: + ) -> typing.Union[ProxyResponse, typing.Iterator[bytes]]: """ Parameters ---------- @@ -674,8 +674,8 @@ async def delete( Returns ------- - ProxyResponse - proxy request successful + typing.Union[ProxyResponse, typing.Iterator[bytes]] + ProxyResponse for JSON content, Iterator[bytes] for binary content Examples -------- @@ -730,7 +730,7 @@ async def patch( typing.Optional[typing.Any], ]] = None, params: typing.Optional[typing.Dict[str, typing.Any]] = None, - ) -> ProxyResponse: + ) -> typing.Union[ProxyResponse, typing.Iterator[bytes]]: """ Parameters ---------- @@ -754,8 +754,8 @@ async def patch( Returns ------- - ProxyResponse - proxy request successful + typing.Union[ProxyResponse, typing.Iterator[bytes]] + ProxyResponse for JSON content, Iterator[bytes] for binary content Examples -------- From a087e876cc6401082ae7fa8946db754273c235e8 Mon Sep 17 00:00:00 2001 From: Jay Vercellone Date: Wed, 26 Nov 2025 16:46:36 -0300 Subject: [PATCH 06/13] Fix stream consumption --- src/pipedream/proxy/client.py | 38 +- src/pipedream/proxy/raw_client.py | 924 ++++++++++++++++-------------- 2 files changed, 519 insertions(+), 443 deletions(-) diff --git a/src/pipedream/proxy/client.py b/src/pipedream/proxy/client.py index c27f667..555ea3a 100644 --- a/src/pipedream/proxy/client.py +++ b/src/pipedream/proxy/client.py @@ -413,7 +413,7 @@ async def get( account_id: str, headers: typing.Optional[typing.Dict[str, typing.Any]] = None, params: typing.Optional[typing.Dict[str, typing.Any]] = None, - ) -> typing.Union[ProxyResponse, typing.Iterator[bytes]]: + ) -> typing.Union[ProxyResponse, typing.AsyncIterator[bytes]]: """ Parameters ---------- @@ -434,8 +434,8 @@ async def get( Returns ------- - typing.Union[ProxyResponse, typing.Iterator[bytes]] - ProxyResponse for JSON content, Iterator[bytes] for binary content + typing.Union[ProxyResponse, typing.AsyncIterator[bytes]] + ProxyResponse for JSON content, AsyncIterator[bytes] for binary content Examples -------- @@ -456,7 +456,7 @@ async def main() -> None: url="https://example.com/api/endpoint", external_user_id="external_user_id", account_id="account_id", - headers={"Extra-Downstream-Header": "some value"} + headers={"Extra-Downstream-Header": "some value"}, params={"limit": 10}, ) @@ -476,7 +476,8 @@ async def main() -> None: url_64, external_user_id=external_user_id, account_id=account_id, - request_options=request_options) + request_options=request_options, + ) return _response.data async def post( @@ -491,7 +492,7 @@ async def post( typing.Optional[typing.Any], ]] = None, params: typing.Optional[typing.Dict[str, typing.Any]] = None, - ) -> typing.Union[ProxyResponse, typing.Iterator[bytes]]: + ) -> typing.Union[ProxyResponse, typing.AsyncIterator[bytes]]: """ Parameters ---------- @@ -515,8 +516,8 @@ async def post( Returns ------- - typing.Union[ProxyResponse, typing.Iterator[bytes]] - ProxyResponse for JSON content, Iterator[bytes] for binary content + typing.Union[ProxyResponse, typing.AsyncIterator[bytes]] + ProxyResponse for JSON content, AsyncIterator[bytes] for binary content Examples -------- @@ -574,7 +575,7 @@ async def put( typing.Optional[typing.Any], ]] = None, params: typing.Optional[typing.Dict[str, typing.Any]] = None, - ) -> typing.Union[ProxyResponse, typing.Iterator[bytes]]: + ) -> typing.Union[ProxyResponse, typing.AsyncIterator[bytes]]: """ Parameters ---------- @@ -598,8 +599,8 @@ async def put( Returns ------- - typing.Union[ProxyResponse, typing.Iterator[bytes]] - ProxyResponse for JSON content, Iterator[bytes] for binary content + typing.Union[ProxyResponse, typing.AsyncIterator[bytes]] + ProxyResponse for JSON content, AsyncIterator[bytes] for binary content Examples -------- @@ -653,7 +654,7 @@ async def delete( account_id: str, headers: typing.Optional[typing.Dict[str, typing.Any]] = None, params: typing.Optional[typing.Dict[str, typing.Any]] = None, - ) -> typing.Union[ProxyResponse, typing.Iterator[bytes]]: + ) -> typing.Union[ProxyResponse, typing.AsyncIterator[bytes]]: """ Parameters ---------- @@ -674,8 +675,8 @@ async def delete( Returns ------- - typing.Union[ProxyResponse, typing.Iterator[bytes]] - ProxyResponse for JSON content, Iterator[bytes] for binary content + typing.Union[ProxyResponse, typing.AsyncIterator[bytes]] + ProxyResponse for JSON content, AsyncIterator[bytes] for binary content Examples -------- @@ -715,7 +716,8 @@ async def main() -> None: url_64, external_user_id=external_user_id, account_id=account_id, - request_options=request_options) + request_options=request_options, + ) return _response.data async def patch( @@ -730,7 +732,7 @@ async def patch( typing.Optional[typing.Any], ]] = None, params: typing.Optional[typing.Dict[str, typing.Any]] = None, - ) -> typing.Union[ProxyResponse, typing.Iterator[bytes]]: + ) -> typing.Union[ProxyResponse, typing.AsyncIterator[bytes]]: """ Parameters ---------- @@ -754,8 +756,8 @@ async def patch( Returns ------- - typing.Union[ProxyResponse, typing.Iterator[bytes]] - ProxyResponse for JSON content, Iterator[bytes] for binary content + typing.Union[ProxyResponse, typing.AsyncIterator[bytes]] + ProxyResponse for JSON content, AsyncIterator[bytes] for binary content Examples -------- diff --git a/src/pipedream/proxy/raw_client.py b/src/pipedream/proxy/raw_client.py index 76e3d4f..a0d593e 100644 --- a/src/pipedream/proxy/raw_client.py +++ b/src/pipedream/proxy/raw_client.py @@ -1,5 +1,6 @@ # This file was auto-generated by Fern from our API Definition. +import contextlib import typing from json.decoder import JSONDecodeError @@ -16,6 +17,24 @@ OMIT = typing.cast(typing.Any, ...) +def _create_stream_iterator(stream_context, response, chunk_size=None): + """Create an iterator that ensures proper cleanup after streaming.""" + try: + for chunk in response.iter_bytes(chunk_size=chunk_size): + yield chunk + finally: + stream_context.__exit__(None, None, None) + + +async def _create_async_stream_iterator(stream_context, response, chunk_size=None): + """Create an async iterator that ensures proper cleanup after streaming.""" + try: + async for chunk in response.aiter_bytes(chunk_size=chunk_size): + yield chunk + finally: + await stream_context.__aexit__(None, None, None) + + class RawProxyClient: def __init__(self, *, client_wrapper: SyncClientWrapper): self._client_wrapper = client_wrapper @@ -50,7 +69,7 @@ def get( HttpResponse[typing.Union[ProxyResponse, typing.Iterator[bytes]]] proxy request successful """ - with self._client_wrapper.httpx_client.stream( + _response = self._client_wrapper.httpx_client.stream( f"v1/connect/{jsonable_encoder(self._client_wrapper._project_id)}/proxy/{jsonable_encoder(url_64)}", method="GET", params={ @@ -58,50 +77,56 @@ def get( "account_id": account_id, }, request_options=request_options, - ) as _response: - try: - if 200 <= _response.status_code < 300: - # Check Content-Type to determine how to handle the response - content_type = _response.headers.get("content-type", "").lower() - is_json = "application/json" in content_type or not content_type - - if is_json: - # Parse as JSON - _response.read() - if not _response.text.strip(): - return HttpResponse(response=_response, data=None) - _data = typing.cast( - ProxyResponse, - parse_obj_as( - type_=ProxyResponse, # type: ignore - object_=_response.json(), - ), - ) - return HttpResponse(response=_response, data=_data) - else: - # Stream binary content - _chunk_size = request_options.get("chunk_size", None) if request_options is not None else None - return HttpResponse( - response=_response, data=(_chunk for _chunk in _response.iter_bytes(chunk_size=_chunk_size)) - ) - _response.read() - if _response.status_code == 429: - raise TooManyRequestsError( - headers=dict(_response.headers), - body=typing.cast( - typing.Optional[typing.Any], - parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore - object_=_response.json(), - ), + ) + _stream_context = _response.__enter__() + + try: + if 200 <= _stream_context.status_code < 300: + # Check Content-Type to determine how to handle the response + content_type = _stream_context.headers.get("content-type", "").lower() + is_json = "application/json" in content_type or not content_type + + if is_json: + # Parse as JSON and close the stream + _stream_context.read() + _response.__exit__(None, None, None) + if not _stream_context.text.strip(): + return HttpResponse(response=_stream_context, data=None) + _data = typing.cast( + ProxyResponse, + parse_obj_as( + type_=ProxyResponse, # type: ignore + object_=_stream_context.json(), ), ) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError( - status_code=_response.status_code, headers=dict(_response.headers), body=_response.text + return HttpResponse(response=_stream_context, data=_data) + else: + # Stream binary content - keep stream open for lazy iteration + _chunk_size = request_options.get("chunk_size", None) if request_options is not None else None + return HttpResponse(response=_stream_context, data=_create_stream_iterator(_response, _stream_context, _chunk_size)) + _stream_context.read() + _response.__exit__(None, None, None) + if _stream_context.status_code == 429: + raise TooManyRequestsError( + headers=dict(_stream_context.headers), + body=typing.cast( + typing.Optional[typing.Any], + parse_obj_as( + type_=typing.Optional[typing.Any], # type: ignore + object_=_stream_context.json(), + ), + ), ) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + _response_json = _stream_context.json() + except JSONDecodeError: + _response.__exit__(None, None, None) + raise ApiError( + status_code=_stream_context.status_code, headers=dict(_stream_context.headers), body=_stream_context.text + ) + except Exception: + _response.__exit__(None, None, None) + raise + raise ApiError(status_code=_stream_context.status_code, headers=dict(_stream_context.headers), body=_response_json) def post( self, @@ -136,7 +161,7 @@ def post( HttpResponse[typing.Union[ProxyResponse, typing.Iterator[bytes]]] proxy request successful """ - with self._client_wrapper.httpx_client.stream( + _response = self._client_wrapper.httpx_client.stream( f"v1/connect/{jsonable_encoder(self._client_wrapper._project_id)}/proxy/{jsonable_encoder(url_64)}", method="POST", params={ @@ -149,50 +174,56 @@ def post( }, request_options=request_options, omit=OMIT, - ) as _response: - try: - if 200 <= _response.status_code < 300: - # Check Content-Type to determine how to handle the response - content_type = _response.headers.get("content-type", "").lower() - is_json = "application/json" in content_type or not content_type - - if is_json: - # Parse as JSON - _response.read() - if not _response.text.strip(): - return HttpResponse(response=_response, data=None) - _data = typing.cast( - ProxyResponse, - parse_obj_as( - type_=ProxyResponse, # type: ignore - object_=_response.json(), - ), - ) - return HttpResponse(response=_response, data=_data) - else: - # Stream binary content - _chunk_size = request_options.get("chunk_size", None) if request_options is not None else None - return HttpResponse( - response=_response, data=(_chunk for _chunk in _response.iter_bytes(chunk_size=_chunk_size)) - ) - _response.read() - if _response.status_code == 429: - raise TooManyRequestsError( - headers=dict(_response.headers), - body=typing.cast( - typing.Optional[typing.Any], - parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore - object_=_response.json(), - ), + ) + _stream_context = _response.__enter__() + + try: + if 200 <= _stream_context.status_code < 300: + # Check Content-Type to determine how to handle the response + content_type = _stream_context.headers.get("content-type", "").lower() + is_json = "application/json" in content_type or not content_type + + if is_json: + # Parse as JSON and close the stream + _stream_context.read() + _response.__exit__(None, None, None) + if not _stream_context.text.strip(): + return HttpResponse(response=_stream_context, data=None) + _data = typing.cast( + ProxyResponse, + parse_obj_as( + type_=ProxyResponse, # type: ignore + object_=_stream_context.json(), ), ) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError( - status_code=_response.status_code, headers=dict(_response.headers), body=_response.text + return HttpResponse(response=_stream_context, data=_data) + else: + # Stream binary content - keep stream open for lazy iteration + _chunk_size = request_options.get("chunk_size", None) if request_options is not None else None + return HttpResponse(response=_stream_context, data=_create_stream_iterator(_response, _stream_context, _chunk_size)) + _stream_context.read() + _response.__exit__(None, None, None) + if _stream_context.status_code == 429: + raise TooManyRequestsError( + headers=dict(_stream_context.headers), + body=typing.cast( + typing.Optional[typing.Any], + parse_obj_as( + type_=typing.Optional[typing.Any], # type: ignore + object_=_stream_context.json(), + ), + ), ) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + _response_json = _stream_context.json() + except JSONDecodeError: + _response.__exit__(None, None, None) + raise ApiError( + status_code=_stream_context.status_code, headers=dict(_stream_context.headers), body=_stream_context.text + ) + except Exception: + _response.__exit__(None, None, None) + raise + raise ApiError(status_code=_stream_context.status_code, headers=dict(_stream_context.headers), body=_response_json) def put( self, @@ -227,7 +258,7 @@ def put( HttpResponse[typing.Union[ProxyResponse, typing.Iterator[bytes]]] proxy request successful """ - with self._client_wrapper.httpx_client.stream( + _response = self._client_wrapper.httpx_client.stream( f"v1/connect/{jsonable_encoder(self._client_wrapper._project_id)}/proxy/{jsonable_encoder(url_64)}", method="PUT", params={ @@ -240,50 +271,56 @@ def put( }, request_options=request_options, omit=OMIT, - ) as _response: - try: - if 200 <= _response.status_code < 300: - # Check Content-Type to determine how to handle the response - content_type = _response.headers.get("content-type", "").lower() - is_json = "application/json" in content_type or not content_type - - if is_json: - # Parse as JSON - _response.read() - if not _response.text.strip(): - return HttpResponse(response=_response, data=None) - _data = typing.cast( - ProxyResponse, - parse_obj_as( - type_=ProxyResponse, # type: ignore - object_=_response.json(), - ), - ) - return HttpResponse(response=_response, data=_data) - else: - # Stream binary content - _chunk_size = request_options.get("chunk_size", None) if request_options is not None else None - return HttpResponse( - response=_response, data=(_chunk for _chunk in _response.iter_bytes(chunk_size=_chunk_size)) - ) - _response.read() - if _response.status_code == 429: - raise TooManyRequestsError( - headers=dict(_response.headers), - body=typing.cast( - typing.Optional[typing.Any], - parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore - object_=_response.json(), - ), + ) + _stream_context = _response.__enter__() + + try: + if 200 <= _stream_context.status_code < 300: + # Check Content-Type to determine how to handle the response + content_type = _stream_context.headers.get("content-type", "").lower() + is_json = "application/json" in content_type or not content_type + + if is_json: + # Parse as JSON and close the stream + _stream_context.read() + _response.__exit__(None, None, None) + if not _stream_context.text.strip(): + return HttpResponse(response=_stream_context, data=None) + _data = typing.cast( + ProxyResponse, + parse_obj_as( + type_=ProxyResponse, # type: ignore + object_=_stream_context.json(), ), ) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError( - status_code=_response.status_code, headers=dict(_response.headers), body=_response.text + return HttpResponse(response=_stream_context, data=_data) + else: + # Stream binary content - keep stream open for lazy iteration + _chunk_size = request_options.get("chunk_size", None) if request_options is not None else None + return HttpResponse(response=_stream_context, data=_create_stream_iterator(_response, _stream_context, _chunk_size)) + _stream_context.read() + _response.__exit__(None, None, None) + if _stream_context.status_code == 429: + raise TooManyRequestsError( + headers=dict(_stream_context.headers), + body=typing.cast( + typing.Optional[typing.Any], + parse_obj_as( + type_=typing.Optional[typing.Any], # type: ignore + object_=_stream_context.json(), + ), + ), ) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + _response_json = _stream_context.json() + except JSONDecodeError: + _response.__exit__(None, None, None) + raise ApiError( + status_code=_stream_context.status_code, headers=dict(_stream_context.headers), body=_stream_context.text + ) + except Exception: + _response.__exit__(None, None, None) + raise + raise ApiError(status_code=_stream_context.status_code, headers=dict(_stream_context.headers), body=_response_json) def delete( self, @@ -315,7 +352,7 @@ def delete( HttpResponse[typing.Union[ProxyResponse, typing.Iterator[bytes]]] proxy request successful """ - with self._client_wrapper.httpx_client.stream( + _response = self._client_wrapper.httpx_client.stream( f"v1/connect/{jsonable_encoder(self._client_wrapper._project_id)}/proxy/{jsonable_encoder(url_64)}", method="DELETE", params={ @@ -323,50 +360,56 @@ def delete( "account_id": account_id, }, request_options=request_options, - ) as _response: - try: - if 200 <= _response.status_code < 300: - # Check Content-Type to determine how to handle the response - content_type = _response.headers.get("content-type", "").lower() - is_json = "application/json" in content_type or not content_type - - if is_json: - # Parse as JSON - _response.read() - if not _response.text.strip(): - return HttpResponse(response=_response, data=None) - _data = typing.cast( - ProxyResponse, - parse_obj_as( - type_=ProxyResponse, # type: ignore - object_=_response.json(), - ), - ) - return HttpResponse(response=_response, data=_data) - else: - # Stream binary content - _chunk_size = request_options.get("chunk_size", None) if request_options is not None else None - return HttpResponse( - response=_response, data=(_chunk for _chunk in _response.iter_bytes(chunk_size=_chunk_size)) - ) - _response.read() - if _response.status_code == 429: - raise TooManyRequestsError( - headers=dict(_response.headers), - body=typing.cast( - typing.Optional[typing.Any], - parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore - object_=_response.json(), - ), + ) + _stream_context = _response.__enter__() + + try: + if 200 <= _stream_context.status_code < 300: + # Check Content-Type to determine how to handle the response + content_type = _stream_context.headers.get("content-type", "").lower() + is_json = "application/json" in content_type or not content_type + + if is_json: + # Parse as JSON and close the stream + _stream_context.read() + _response.__exit__(None, None, None) + if not _stream_context.text.strip(): + return HttpResponse(response=_stream_context, data=None) + _data = typing.cast( + ProxyResponse, + parse_obj_as( + type_=ProxyResponse, # type: ignore + object_=_stream_context.json(), ), ) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError( - status_code=_response.status_code, headers=dict(_response.headers), body=_response.text + return HttpResponse(response=_stream_context, data=_data) + else: + # Stream binary content - keep stream open for lazy iteration + _chunk_size = request_options.get("chunk_size", None) if request_options is not None else None + return HttpResponse(response=_stream_context, data=_create_stream_iterator(_response, _stream_context, _chunk_size)) + _stream_context.read() + _response.__exit__(None, None, None) + if _stream_context.status_code == 429: + raise TooManyRequestsError( + headers=dict(_stream_context.headers), + body=typing.cast( + typing.Optional[typing.Any], + parse_obj_as( + type_=typing.Optional[typing.Any], # type: ignore + object_=_stream_context.json(), + ), + ), ) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + _response_json = _stream_context.json() + except JSONDecodeError: + _response.__exit__(None, None, None) + raise ApiError( + status_code=_stream_context.status_code, headers=dict(_stream_context.headers), body=_stream_context.text + ) + except Exception: + _response.__exit__(None, None, None) + raise + raise ApiError(status_code=_stream_context.status_code, headers=dict(_stream_context.headers), body=_response_json) def patch( self, @@ -401,7 +444,7 @@ def patch( HttpResponse[typing.Union[ProxyResponse, typing.Iterator[bytes]]] proxy request successful """ - with self._client_wrapper.httpx_client.stream( + _response = self._client_wrapper.httpx_client.stream( f"v1/connect/{jsonable_encoder(self._client_wrapper._project_id)}/proxy/{jsonable_encoder(url_64)}", method="PATCH", params={ @@ -414,50 +457,56 @@ def patch( }, request_options=request_options, omit=OMIT, - ) as _response: - try: - if 200 <= _response.status_code < 300: - # Check Content-Type to determine how to handle the response - content_type = _response.headers.get("content-type", "").lower() - is_json = "application/json" in content_type or not content_type - - if is_json: - # Parse as JSON - _response.read() - if not _response.text.strip(): - return HttpResponse(response=_response, data=None) - _data = typing.cast( - ProxyResponse, - parse_obj_as( - type_=ProxyResponse, # type: ignore - object_=_response.json(), - ), - ) - return HttpResponse(response=_response, data=_data) - else: - # Stream binary content - _chunk_size = request_options.get("chunk_size", None) if request_options is not None else None - return HttpResponse( - response=_response, data=(_chunk for _chunk in _response.iter_bytes(chunk_size=_chunk_size)) - ) - _response.read() - if _response.status_code == 429: - raise TooManyRequestsError( - headers=dict(_response.headers), - body=typing.cast( - typing.Optional[typing.Any], - parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore - object_=_response.json(), - ), + ) + _stream_context = _response.__enter__() + + try: + if 200 <= _stream_context.status_code < 300: + # Check Content-Type to determine how to handle the response + content_type = _stream_context.headers.get("content-type", "").lower() + is_json = "application/json" in content_type or not content_type + + if is_json: + # Parse as JSON and close the stream + _stream_context.read() + _response.__exit__(None, None, None) + if not _stream_context.text.strip(): + return HttpResponse(response=_stream_context, data=None) + _data = typing.cast( + ProxyResponse, + parse_obj_as( + type_=ProxyResponse, # type: ignore + object_=_stream_context.json(), ), ) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError( - status_code=_response.status_code, headers=dict(_response.headers), body=_response.text + return HttpResponse(response=_stream_context, data=_data) + else: + # Stream binary content - keep stream open for lazy iteration + _chunk_size = request_options.get("chunk_size", None) if request_options is not None else None + return HttpResponse(response=_stream_context, data=_create_stream_iterator(_response, _stream_context, _chunk_size)) + _stream_context.read() + _response.__exit__(None, None, None) + if _stream_context.status_code == 429: + raise TooManyRequestsError( + headers=dict(_stream_context.headers), + body=typing.cast( + typing.Optional[typing.Any], + parse_obj_as( + type_=typing.Optional[typing.Any], # type: ignore + object_=_stream_context.json(), + ), + ), ) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + _response_json = _stream_context.json() + except JSONDecodeError: + _response.__exit__(None, None, None) + raise ApiError( + status_code=_stream_context.status_code, headers=dict(_stream_context.headers), body=_stream_context.text + ) + except Exception: + _response.__exit__(None, None, None) + raise + raise ApiError(status_code=_stream_context.status_code, headers=dict(_stream_context.headers), body=_response_json) class AsyncRawProxyClient: @@ -494,7 +543,7 @@ async def get( AsyncHttpResponse[typing.Union[ProxyResponse, typing.AsyncIterator[bytes]]] proxy request successful """ - async with self._client_wrapper.httpx_client.stream( + _response = self._client_wrapper.httpx_client.stream( f"v1/connect/{jsonable_encoder(self._client_wrapper._project_id)}/proxy/{jsonable_encoder(url_64)}", method="GET", params={ @@ -502,51 +551,56 @@ async def get( "account_id": account_id, }, request_options=request_options, - ) as _response: - try: - if 200 <= _response.status_code < 300: - # Check Content-Type to determine how to handle the response - content_type = _response.headers.get("content-type", "").lower() - is_json = "application/json" in content_type or not content_type - - if is_json: - # Parse as JSON - await _response.aread() - if not _response.text.strip(): - return AsyncHttpResponse(response=_response, data=None) - _data = typing.cast( - ProxyResponse, - parse_obj_as( - type_=ProxyResponse, # type: ignore - object_=_response.json(), - ), - ) - return AsyncHttpResponse(response=_response, data=_data) - else: - # Stream binary content - _chunk_size = request_options.get("chunk_size", None) if request_options is not None else None - return AsyncHttpResponse( - response=_response, - data=(_chunk async for _chunk in _response.aiter_bytes(chunk_size=_chunk_size)), - ) - await _response.aread() - if _response.status_code == 429: - raise TooManyRequestsError( - headers=dict(_response.headers), - body=typing.cast( - typing.Optional[typing.Any], - parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore - object_=_response.json(), - ), + ) + _stream_context = await _response.__aenter__() + + try: + if 200 <= _stream_context.status_code < 300: + # Check Content-Type to determine how to handle the response + content_type = _stream_context.headers.get("content-type", "").lower() + is_json = "application/json" in content_type or not content_type + + if is_json: + # Parse as JSON and close the stream + await _stream_context.aread() + await _response.__aexit__(None, None, None) + if not _stream_context.text.strip(): + return AsyncHttpResponse(response=_stream_context, data=None) + _data = typing.cast( + ProxyResponse, + parse_obj_as( + type_=ProxyResponse, # type: ignore + object_=_stream_context.json(), ), ) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError( - status_code=_response.status_code, headers=dict(_response.headers), body=_response.text + return AsyncHttpResponse(response=_stream_context, data=_data) + else: + # Stream binary content - keep stream open for lazy iteration + _chunk_size = request_options.get("chunk_size", None) if request_options is not None else None + return AsyncHttpResponse(response=_stream_context, data=_create_async_stream_iterator(_response, _stream_context, _chunk_size)) + await _stream_context.aread() + await _response.__aexit__(None, None, None) + if _stream_context.status_code == 429: + raise TooManyRequestsError( + headers=dict(_stream_context.headers), + body=typing.cast( + typing.Optional[typing.Any], + parse_obj_as( + type_=typing.Optional[typing.Any], # type: ignore + object_=_stream_context.json(), + ), + ), ) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + _response_json = _stream_context.json() + except JSONDecodeError: + await _response.__aexit__(None, None, None) + raise ApiError( + status_code=_stream_context.status_code, headers=dict(_stream_context.headers), body=_stream_context.text + ) + except Exception: + await _response.__aexit__(None, None, None) + raise + raise ApiError(status_code=_stream_context.status_code, headers=dict(_stream_context.headers), body=_response_json) async def post( self, @@ -581,7 +635,7 @@ async def post( AsyncHttpResponse[typing.Union[ProxyResponse, typing.AsyncIterator[bytes]]] proxy request successful """ - async with self._client_wrapper.httpx_client.stream( + _response = self._client_wrapper.httpx_client.stream( f"v1/connect/{jsonable_encoder(self._client_wrapper._project_id)}/proxy/{jsonable_encoder(url_64)}", method="POST", params={ @@ -594,51 +648,56 @@ async def post( }, request_options=request_options, omit=OMIT, - ) as _response: - try: - if 200 <= _response.status_code < 300: - # Check Content-Type to determine how to handle the response - content_type = _response.headers.get("content-type", "").lower() - is_json = "application/json" in content_type or not content_type - - if is_json: - # Parse as JSON - await _response.aread() - if not _response.text.strip(): - return AsyncHttpResponse(response=_response, data=None) - _data = typing.cast( - ProxyResponse, - parse_obj_as( - type_=ProxyResponse, # type: ignore - object_=_response.json(), - ), - ) - return AsyncHttpResponse(response=_response, data=_data) - else: - # Stream binary content - _chunk_size = request_options.get("chunk_size", None) if request_options is not None else None - return AsyncHttpResponse( - response=_response, - data=(_chunk async for _chunk in _response.aiter_bytes(chunk_size=_chunk_size)), - ) - await _response.aread() - if _response.status_code == 429: - raise TooManyRequestsError( - headers=dict(_response.headers), - body=typing.cast( - typing.Optional[typing.Any], - parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore - object_=_response.json(), - ), + ) + _stream_context = await _response.__aenter__() + + try: + if 200 <= _stream_context.status_code < 300: + # Check Content-Type to determine how to handle the response + content_type = _stream_context.headers.get("content-type", "").lower() + is_json = "application/json" in content_type or not content_type + + if is_json: + # Parse as JSON and close the stream + await _stream_context.aread() + await _response.__aexit__(None, None, None) + if not _stream_context.text.strip(): + return AsyncHttpResponse(response=_stream_context, data=None) + _data = typing.cast( + ProxyResponse, + parse_obj_as( + type_=ProxyResponse, # type: ignore + object_=_stream_context.json(), ), ) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError( - status_code=_response.status_code, headers=dict(_response.headers), body=_response.text + return AsyncHttpResponse(response=_stream_context, data=_data) + else: + # Stream binary content - keep stream open for lazy iteration + _chunk_size = request_options.get("chunk_size", None) if request_options is not None else None + return AsyncHttpResponse(response=_stream_context, data=_create_async_stream_iterator(_response, _stream_context, _chunk_size)) + await _stream_context.aread() + await _response.__aexit__(None, None, None) + if _stream_context.status_code == 429: + raise TooManyRequestsError( + headers=dict(_stream_context.headers), + body=typing.cast( + typing.Optional[typing.Any], + parse_obj_as( + type_=typing.Optional[typing.Any], # type: ignore + object_=_stream_context.json(), + ), + ), ) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + _response_json = _stream_context.json() + except JSONDecodeError: + await _response.__aexit__(None, None, None) + raise ApiError( + status_code=_stream_context.status_code, headers=dict(_stream_context.headers), body=_stream_context.text + ) + except Exception: + await _response.__aexit__(None, None, None) + raise + raise ApiError(status_code=_stream_context.status_code, headers=dict(_stream_context.headers), body=_response_json) async def put( self, @@ -673,7 +732,7 @@ async def put( AsyncHttpResponse[typing.Union[ProxyResponse, typing.AsyncIterator[bytes]]] proxy request successful """ - async with self._client_wrapper.httpx_client.stream( + _response = self._client_wrapper.httpx_client.stream( f"v1/connect/{jsonable_encoder(self._client_wrapper._project_id)}/proxy/{jsonable_encoder(url_64)}", method="PUT", params={ @@ -686,51 +745,56 @@ async def put( }, request_options=request_options, omit=OMIT, - ) as _response: - try: - if 200 <= _response.status_code < 300: - # Check Content-Type to determine how to handle the response - content_type = _response.headers.get("content-type", "").lower() - is_json = "application/json" in content_type or not content_type - - if is_json: - # Parse as JSON - await _response.aread() - if not _response.text.strip(): - return AsyncHttpResponse(response=_response, data=None) - _data = typing.cast( - ProxyResponse, - parse_obj_as( - type_=ProxyResponse, # type: ignore - object_=_response.json(), - ), - ) - return AsyncHttpResponse(response=_response, data=_data) - else: - # Stream binary content - _chunk_size = request_options.get("chunk_size", None) if request_options is not None else None - return AsyncHttpResponse( - response=_response, - data=(_chunk async for _chunk in _response.aiter_bytes(chunk_size=_chunk_size)), - ) - await _response.aread() - if _response.status_code == 429: - raise TooManyRequestsError( - headers=dict(_response.headers), - body=typing.cast( - typing.Optional[typing.Any], - parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore - object_=_response.json(), - ), + ) + _stream_context = await _response.__aenter__() + + try: + if 200 <= _stream_context.status_code < 300: + # Check Content-Type to determine how to handle the response + content_type = _stream_context.headers.get("content-type", "").lower() + is_json = "application/json" in content_type or not content_type + + if is_json: + # Parse as JSON and close the stream + await _stream_context.aread() + await _response.__aexit__(None, None, None) + if not _stream_context.text.strip(): + return AsyncHttpResponse(response=_stream_context, data=None) + _data = typing.cast( + ProxyResponse, + parse_obj_as( + type_=ProxyResponse, # type: ignore + object_=_stream_context.json(), ), ) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError( - status_code=_response.status_code, headers=dict(_response.headers), body=_response.text + return AsyncHttpResponse(response=_stream_context, data=_data) + else: + # Stream binary content - keep stream open for lazy iteration + _chunk_size = request_options.get("chunk_size", None) if request_options is not None else None + return AsyncHttpResponse(response=_stream_context, data=_create_async_stream_iterator(_response, _stream_context, _chunk_size)) + await _stream_context.aread() + await _response.__aexit__(None, None, None) + if _stream_context.status_code == 429: + raise TooManyRequestsError( + headers=dict(_stream_context.headers), + body=typing.cast( + typing.Optional[typing.Any], + parse_obj_as( + type_=typing.Optional[typing.Any], # type: ignore + object_=_stream_context.json(), + ), + ), ) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + _response_json = _stream_context.json() + except JSONDecodeError: + await _response.__aexit__(None, None, None) + raise ApiError( + status_code=_stream_context.status_code, headers=dict(_stream_context.headers), body=_stream_context.text + ) + except Exception: + await _response.__aexit__(None, None, None) + raise + raise ApiError(status_code=_stream_context.status_code, headers=dict(_stream_context.headers), body=_response_json) async def delete( self, @@ -762,7 +826,7 @@ async def delete( AsyncHttpResponse[typing.Union[ProxyResponse, typing.AsyncIterator[bytes]]] proxy request successful """ - async with self._client_wrapper.httpx_client.stream( + _response = self._client_wrapper.httpx_client.stream( f"v1/connect/{jsonable_encoder(self._client_wrapper._project_id)}/proxy/{jsonable_encoder(url_64)}", method="DELETE", params={ @@ -770,51 +834,56 @@ async def delete( "account_id": account_id, }, request_options=request_options, - ) as _response: - try: - if 200 <= _response.status_code < 300: - # Check Content-Type to determine how to handle the response - content_type = _response.headers.get("content-type", "").lower() - is_json = "application/json" in content_type or not content_type - - if is_json: - # Parse as JSON - await _response.aread() - if not _response.text.strip(): - return AsyncHttpResponse(response=_response, data=None) - _data = typing.cast( - ProxyResponse, - parse_obj_as( - type_=ProxyResponse, # type: ignore - object_=_response.json(), - ), - ) - return AsyncHttpResponse(response=_response, data=_data) - else: - # Stream binary content - _chunk_size = request_options.get("chunk_size", None) if request_options is not None else None - return AsyncHttpResponse( - response=_response, - data=(_chunk async for _chunk in _response.aiter_bytes(chunk_size=_chunk_size)), - ) - await _response.aread() - if _response.status_code == 429: - raise TooManyRequestsError( - headers=dict(_response.headers), - body=typing.cast( - typing.Optional[typing.Any], - parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore - object_=_response.json(), - ), + ) + _stream_context = await _response.__aenter__() + + try: + if 200 <= _stream_context.status_code < 300: + # Check Content-Type to determine how to handle the response + content_type = _stream_context.headers.get("content-type", "").lower() + is_json = "application/json" in content_type or not content_type + + if is_json: + # Parse as JSON and close the stream + await _stream_context.aread() + await _response.__aexit__(None, None, None) + if not _stream_context.text.strip(): + return AsyncHttpResponse(response=_stream_context, data=None) + _data = typing.cast( + ProxyResponse, + parse_obj_as( + type_=ProxyResponse, # type: ignore + object_=_stream_context.json(), ), ) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError( - status_code=_response.status_code, headers=dict(_response.headers), body=_response.text + return AsyncHttpResponse(response=_stream_context, data=_data) + else: + # Stream binary content - keep stream open for lazy iteration + _chunk_size = request_options.get("chunk_size", None) if request_options is not None else None + return AsyncHttpResponse(response=_stream_context, data=_create_async_stream_iterator(_response, _stream_context, _chunk_size)) + await _stream_context.aread() + await _response.__aexit__(None, None, None) + if _stream_context.status_code == 429: + raise TooManyRequestsError( + headers=dict(_stream_context.headers), + body=typing.cast( + typing.Optional[typing.Any], + parse_obj_as( + type_=typing.Optional[typing.Any], # type: ignore + object_=_stream_context.json(), + ), + ), ) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + _response_json = _stream_context.json() + except JSONDecodeError: + await _response.__aexit__(None, None, None) + raise ApiError( + status_code=_stream_context.status_code, headers=dict(_stream_context.headers), body=_stream_context.text + ) + except Exception: + await _response.__aexit__(None, None, None) + raise + raise ApiError(status_code=_stream_context.status_code, headers=dict(_stream_context.headers), body=_response_json) async def patch( self, @@ -849,7 +918,7 @@ async def patch( AsyncHttpResponse[typing.Union[ProxyResponse, typing.AsyncIterator[bytes]]] proxy request successful """ - async with self._client_wrapper.httpx_client.stream( + _response = self._client_wrapper.httpx_client.stream( f"v1/connect/{jsonable_encoder(self._client_wrapper._project_id)}/proxy/{jsonable_encoder(url_64)}", method="PATCH", params={ @@ -862,48 +931,53 @@ async def patch( }, request_options=request_options, omit=OMIT, - ) as _response: - try: - if 200 <= _response.status_code < 300: - # Check Content-Type to determine how to handle the response - content_type = _response.headers.get("content-type", "").lower() - is_json = "application/json" in content_type or not content_type - - if is_json: - # Parse as JSON - await _response.aread() - if not _response.text.strip(): - return AsyncHttpResponse(response=_response, data=None) - _data = typing.cast( - ProxyResponse, - parse_obj_as( - type_=ProxyResponse, # type: ignore - object_=_response.json(), - ), - ) - return AsyncHttpResponse(response=_response, data=_data) - else: - # Stream binary content - _chunk_size = request_options.get("chunk_size", None) if request_options is not None else None - return AsyncHttpResponse( - response=_response, - data=(_chunk async for _chunk in _response.aiter_bytes(chunk_size=_chunk_size)), - ) - await _response.aread() - if _response.status_code == 429: - raise TooManyRequestsError( - headers=dict(_response.headers), - body=typing.cast( - typing.Optional[typing.Any], - parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore - object_=_response.json(), - ), + ) + _stream_context = await _response.__aenter__() + + try: + if 200 <= _stream_context.status_code < 300: + # Check Content-Type to determine how to handle the response + content_type = _stream_context.headers.get("content-type", "").lower() + is_json = "application/json" in content_type or not content_type + + if is_json: + # Parse as JSON and close the stream + await _stream_context.aread() + await _response.__aexit__(None, None, None) + if not _stream_context.text.strip(): + return AsyncHttpResponse(response=_stream_context, data=None) + _data = typing.cast( + ProxyResponse, + parse_obj_as( + type_=ProxyResponse, # type: ignore + object_=_stream_context.json(), ), ) - _response_json = _response.json() - except JSONDecodeError: - raise ApiError( - status_code=_response.status_code, headers=dict(_response.headers), body=_response.text + return AsyncHttpResponse(response=_stream_context, data=_data) + else: + # Stream binary content - keep stream open for lazy iteration + _chunk_size = request_options.get("chunk_size", None) if request_options is not None else None + return AsyncHttpResponse(response=_stream_context, data=_create_async_stream_iterator(_response, _stream_context, _chunk_size)) + await _stream_context.aread() + await _response.__aexit__(None, None, None) + if _stream_context.status_code == 429: + raise TooManyRequestsError( + headers=dict(_stream_context.headers), + body=typing.cast( + typing.Optional[typing.Any], + parse_obj_as( + type_=typing.Optional[typing.Any], # type: ignore + object_=_stream_context.json(), + ), + ), ) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + _response_json = _stream_context.json() + except JSONDecodeError: + await _response.__aexit__(None, None, None) + raise ApiError( + status_code=_stream_context.status_code, headers=dict(_stream_context.headers), body=_stream_context.text + ) + except Exception: + await _response.__aexit__(None, None, None) + raise + raise ApiError(status_code=_stream_context.status_code, headers=dict(_stream_context.headers), body=_response_json) From 9aa32dde9c2b9693928cab88805280a4ad4ea5fc Mon Sep 17 00:00:00 2001 From: Jay Vercellone Date: Wed, 26 Nov 2025 18:21:48 -0300 Subject: [PATCH 07/13] Remove unused import --- src/pipedream/proxy/raw_client.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pipedream/proxy/raw_client.py b/src/pipedream/proxy/raw_client.py index a0d593e..a6117eb 100644 --- a/src/pipedream/proxy/raw_client.py +++ b/src/pipedream/proxy/raw_client.py @@ -1,6 +1,5 @@ # This file was auto-generated by Fern from our API Definition. -import contextlib import typing from json.decoder import JSONDecodeError From 8352537c590ad7f8808f8963437e2d59a43a85de Mon Sep 17 00:00:00 2001 From: Jay Vercellone Date: Tue, 2 Dec 2025 18:15:47 -0300 Subject: [PATCH 08/13] Handle non-OK, non-JSON responses --- src/pipedream/proxy/raw_client.py | 100 +++++++++++++++++++++--------- 1 file changed, 70 insertions(+), 30 deletions(-) diff --git a/src/pipedream/proxy/raw_client.py b/src/pipedream/proxy/raw_client.py index a6117eb..8416972 100644 --- a/src/pipedream/proxy/raw_client.py +++ b/src/pipedream/proxy/raw_client.py @@ -105,6 +105,7 @@ def get( return HttpResponse(response=_stream_context, data=_create_stream_iterator(_response, _stream_context, _chunk_size)) _stream_context.read() _response.__exit__(None, None, None) + _error_content_type = _stream_context.headers.get("content-type", "").lower() if _stream_context.status_code == 429: raise TooManyRequestsError( headers=dict(_stream_context.headers), @@ -112,11 +113,14 @@ def get( typing.Optional[typing.Any], parse_obj_as( type_=typing.Optional[typing.Any], # type: ignore - object_=_stream_context.json(), + object_=_stream_context.json() if "application/json" in _error_content_type else _stream_context.text, ), ), ) - _response_json = _stream_context.json() + if "application/json" in _error_content_type: + _response_body = _stream_context.json() + else: + _response_body = _stream_context.text except JSONDecodeError: _response.__exit__(None, None, None) raise ApiError( @@ -125,7 +129,7 @@ def get( except Exception: _response.__exit__(None, None, None) raise - raise ApiError(status_code=_stream_context.status_code, headers=dict(_stream_context.headers), body=_response_json) + raise ApiError(status_code=_stream_context.status_code, headers=dict(_stream_context.headers), body=_response_body) def post( self, @@ -202,6 +206,7 @@ def post( return HttpResponse(response=_stream_context, data=_create_stream_iterator(_response, _stream_context, _chunk_size)) _stream_context.read() _response.__exit__(None, None, None) + _error_content_type = _stream_context.headers.get("content-type", "").lower() if _stream_context.status_code == 429: raise TooManyRequestsError( headers=dict(_stream_context.headers), @@ -209,11 +214,14 @@ def post( typing.Optional[typing.Any], parse_obj_as( type_=typing.Optional[typing.Any], # type: ignore - object_=_stream_context.json(), + object_=_stream_context.json() if "application/json" in _error_content_type else _stream_context.text, ), ), ) - _response_json = _stream_context.json() + if "application/json" in _error_content_type: + _response_body = _stream_context.json() + else: + _response_body = _stream_context.text except JSONDecodeError: _response.__exit__(None, None, None) raise ApiError( @@ -222,7 +230,7 @@ def post( except Exception: _response.__exit__(None, None, None) raise - raise ApiError(status_code=_stream_context.status_code, headers=dict(_stream_context.headers), body=_response_json) + raise ApiError(status_code=_stream_context.status_code, headers=dict(_stream_context.headers), body=_response_body) def put( self, @@ -299,6 +307,7 @@ def put( return HttpResponse(response=_stream_context, data=_create_stream_iterator(_response, _stream_context, _chunk_size)) _stream_context.read() _response.__exit__(None, None, None) + _error_content_type = _stream_context.headers.get("content-type", "").lower() if _stream_context.status_code == 429: raise TooManyRequestsError( headers=dict(_stream_context.headers), @@ -306,11 +315,14 @@ def put( typing.Optional[typing.Any], parse_obj_as( type_=typing.Optional[typing.Any], # type: ignore - object_=_stream_context.json(), + object_=_stream_context.json() if "application/json" in _error_content_type else _stream_context.text, ), ), ) - _response_json = _stream_context.json() + if "application/json" in _error_content_type: + _response_body = _stream_context.json() + else: + _response_body = _stream_context.text except JSONDecodeError: _response.__exit__(None, None, None) raise ApiError( @@ -319,7 +331,7 @@ def put( except Exception: _response.__exit__(None, None, None) raise - raise ApiError(status_code=_stream_context.status_code, headers=dict(_stream_context.headers), body=_response_json) + raise ApiError(status_code=_stream_context.status_code, headers=dict(_stream_context.headers), body=_response_body) def delete( self, @@ -388,6 +400,7 @@ def delete( return HttpResponse(response=_stream_context, data=_create_stream_iterator(_response, _stream_context, _chunk_size)) _stream_context.read() _response.__exit__(None, None, None) + _error_content_type = _stream_context.headers.get("content-type", "").lower() if _stream_context.status_code == 429: raise TooManyRequestsError( headers=dict(_stream_context.headers), @@ -395,11 +408,14 @@ def delete( typing.Optional[typing.Any], parse_obj_as( type_=typing.Optional[typing.Any], # type: ignore - object_=_stream_context.json(), + object_=_stream_context.json() if "application/json" in _error_content_type else _stream_context.text, ), ), ) - _response_json = _stream_context.json() + if "application/json" in _error_content_type: + _response_body = _stream_context.json() + else: + _response_body = _stream_context.text except JSONDecodeError: _response.__exit__(None, None, None) raise ApiError( @@ -408,7 +424,7 @@ def delete( except Exception: _response.__exit__(None, None, None) raise - raise ApiError(status_code=_stream_context.status_code, headers=dict(_stream_context.headers), body=_response_json) + raise ApiError(status_code=_stream_context.status_code, headers=dict(_stream_context.headers), body=_response_body) def patch( self, @@ -485,6 +501,7 @@ def patch( return HttpResponse(response=_stream_context, data=_create_stream_iterator(_response, _stream_context, _chunk_size)) _stream_context.read() _response.__exit__(None, None, None) + _error_content_type = _stream_context.headers.get("content-type", "").lower() if _stream_context.status_code == 429: raise TooManyRequestsError( headers=dict(_stream_context.headers), @@ -492,11 +509,14 @@ def patch( typing.Optional[typing.Any], parse_obj_as( type_=typing.Optional[typing.Any], # type: ignore - object_=_stream_context.json(), + object_=_stream_context.json() if "application/json" in _error_content_type else _stream_context.text, ), ), ) - _response_json = _stream_context.json() + if "application/json" in _error_content_type: + _response_body = _stream_context.json() + else: + _response_body = _stream_context.text except JSONDecodeError: _response.__exit__(None, None, None) raise ApiError( @@ -505,7 +525,7 @@ def patch( except Exception: _response.__exit__(None, None, None) raise - raise ApiError(status_code=_stream_context.status_code, headers=dict(_stream_context.headers), body=_response_json) + raise ApiError(status_code=_stream_context.status_code, headers=dict(_stream_context.headers), body=_response_body) class AsyncRawProxyClient: @@ -579,6 +599,7 @@ async def get( return AsyncHttpResponse(response=_stream_context, data=_create_async_stream_iterator(_response, _stream_context, _chunk_size)) await _stream_context.aread() await _response.__aexit__(None, None, None) + _error_content_type = _stream_context.headers.get("content-type", "").lower() if _stream_context.status_code == 429: raise TooManyRequestsError( headers=dict(_stream_context.headers), @@ -586,11 +607,14 @@ async def get( typing.Optional[typing.Any], parse_obj_as( type_=typing.Optional[typing.Any], # type: ignore - object_=_stream_context.json(), + object_=_stream_context.json() if "application/json" in _error_content_type else _stream_context.text, ), ), ) - _response_json = _stream_context.json() + if "application/json" in _error_content_type: + _response_body = _stream_context.json() + else: + _response_body = _stream_context.text except JSONDecodeError: await _response.__aexit__(None, None, None) raise ApiError( @@ -599,7 +623,7 @@ async def get( except Exception: await _response.__aexit__(None, None, None) raise - raise ApiError(status_code=_stream_context.status_code, headers=dict(_stream_context.headers), body=_response_json) + raise ApiError(status_code=_stream_context.status_code, headers=dict(_stream_context.headers), body=_response_body) async def post( self, @@ -676,6 +700,7 @@ async def post( return AsyncHttpResponse(response=_stream_context, data=_create_async_stream_iterator(_response, _stream_context, _chunk_size)) await _stream_context.aread() await _response.__aexit__(None, None, None) + _error_content_type = _stream_context.headers.get("content-type", "").lower() if _stream_context.status_code == 429: raise TooManyRequestsError( headers=dict(_stream_context.headers), @@ -683,11 +708,14 @@ async def post( typing.Optional[typing.Any], parse_obj_as( type_=typing.Optional[typing.Any], # type: ignore - object_=_stream_context.json(), + object_=_stream_context.json() if "application/json" in _error_content_type else _stream_context.text, ), ), ) - _response_json = _stream_context.json() + if "application/json" in _error_content_type: + _response_body = _stream_context.json() + else: + _response_body = _stream_context.text except JSONDecodeError: await _response.__aexit__(None, None, None) raise ApiError( @@ -696,7 +724,7 @@ async def post( except Exception: await _response.__aexit__(None, None, None) raise - raise ApiError(status_code=_stream_context.status_code, headers=dict(_stream_context.headers), body=_response_json) + raise ApiError(status_code=_stream_context.status_code, headers=dict(_stream_context.headers), body=_response_body) async def put( self, @@ -773,6 +801,7 @@ async def put( return AsyncHttpResponse(response=_stream_context, data=_create_async_stream_iterator(_response, _stream_context, _chunk_size)) await _stream_context.aread() await _response.__aexit__(None, None, None) + _error_content_type = _stream_context.headers.get("content-type", "").lower() if _stream_context.status_code == 429: raise TooManyRequestsError( headers=dict(_stream_context.headers), @@ -780,11 +809,14 @@ async def put( typing.Optional[typing.Any], parse_obj_as( type_=typing.Optional[typing.Any], # type: ignore - object_=_stream_context.json(), + object_=_stream_context.json() if "application/json" in _error_content_type else _stream_context.text, ), ), ) - _response_json = _stream_context.json() + if "application/json" in _error_content_type: + _response_body = _stream_context.json() + else: + _response_body = _stream_context.text except JSONDecodeError: await _response.__aexit__(None, None, None) raise ApiError( @@ -793,7 +825,7 @@ async def put( except Exception: await _response.__aexit__(None, None, None) raise - raise ApiError(status_code=_stream_context.status_code, headers=dict(_stream_context.headers), body=_response_json) + raise ApiError(status_code=_stream_context.status_code, headers=dict(_stream_context.headers), body=_response_body) async def delete( self, @@ -862,6 +894,7 @@ async def delete( return AsyncHttpResponse(response=_stream_context, data=_create_async_stream_iterator(_response, _stream_context, _chunk_size)) await _stream_context.aread() await _response.__aexit__(None, None, None) + _error_content_type = _stream_context.headers.get("content-type", "").lower() if _stream_context.status_code == 429: raise TooManyRequestsError( headers=dict(_stream_context.headers), @@ -869,11 +902,14 @@ async def delete( typing.Optional[typing.Any], parse_obj_as( type_=typing.Optional[typing.Any], # type: ignore - object_=_stream_context.json(), + object_=_stream_context.json() if "application/json" in _error_content_type else _stream_context.text, ), ), ) - _response_json = _stream_context.json() + if "application/json" in _error_content_type: + _response_body = _stream_context.json() + else: + _response_body = _stream_context.text except JSONDecodeError: await _response.__aexit__(None, None, None) raise ApiError( @@ -882,7 +918,7 @@ async def delete( except Exception: await _response.__aexit__(None, None, None) raise - raise ApiError(status_code=_stream_context.status_code, headers=dict(_stream_context.headers), body=_response_json) + raise ApiError(status_code=_stream_context.status_code, headers=dict(_stream_context.headers), body=_response_body) async def patch( self, @@ -959,6 +995,7 @@ async def patch( return AsyncHttpResponse(response=_stream_context, data=_create_async_stream_iterator(_response, _stream_context, _chunk_size)) await _stream_context.aread() await _response.__aexit__(None, None, None) + _error_content_type = _stream_context.headers.get("content-type", "").lower() if _stream_context.status_code == 429: raise TooManyRequestsError( headers=dict(_stream_context.headers), @@ -966,11 +1003,14 @@ async def patch( typing.Optional[typing.Any], parse_obj_as( type_=typing.Optional[typing.Any], # type: ignore - object_=_stream_context.json(), + object_=_stream_context.json() if "application/json" in _error_content_type else _stream_context.text, ), ), ) - _response_json = _stream_context.json() + if "application/json" in _error_content_type: + _response_body = _stream_context.json() + else: + _response_body = _stream_context.text except JSONDecodeError: await _response.__aexit__(None, None, None) raise ApiError( @@ -979,4 +1019,4 @@ async def patch( except Exception: await _response.__aexit__(None, None, None) raise - raise ApiError(status_code=_stream_context.status_code, headers=dict(_stream_context.headers), body=_response_json) + raise ApiError(status_code=_stream_context.status_code, headers=dict(_stream_context.headers), body=_response_body) From 71d9389fd61e59b585c176c541a96755e5e680b3 Mon Sep 17 00:00:00 2001 From: Jay Vercellone Date: Tue, 2 Dec 2025 19:00:40 -0300 Subject: [PATCH 09/13] Context management cleanup --- src/pipedream/proxy/client.py | 171 ++++- src/pipedream/proxy/raw_client.py | 1079 ++++++++++++++--------------- 2 files changed, 652 insertions(+), 598 deletions(-) diff --git a/src/pipedream/proxy/client.py b/src/pipedream/proxy/client.py index 555ea3a..4e5e169 100644 --- a/src/pipedream/proxy/client.py +++ b/src/pipedream/proxy/client.py @@ -2,6 +2,7 @@ import base64 import typing +from collections.abc import AsyncIterator, Iterator from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper from ..core.request_options import RequestOptions @@ -88,13 +89,26 @@ def get( additional_headers=downstream_headers, additional_query_parameters=params or {}, ) - _response = self._raw_client.get( + ctx = self._raw_client.get( url_64, external_user_id=external_user_id, account_id=account_id, request_options=request_options, ) - return _response.data + _response = ctx.__enter__() + data = _response.data + + if not isinstance(data, Iterator): + ctx.__exit__(None, None, None) + return data + + def _stream() -> typing.Iterator[bytes]: + try: + for chunk in data: + yield chunk + finally: + ctx.__exit__(None, None, None) + return _stream() def post( self, @@ -162,14 +176,27 @@ def post( additional_headers=downstream_headers, additional_query_parameters=params or {}, ) - _response = self._raw_client.post( + ctx = self._raw_client.post( url_64, external_user_id=external_user_id, account_id=account_id, request=body or {}, request_options=request_options, ) - return _response.data + _response = ctx.__enter__() + data = _response.data + + if not isinstance(data, Iterator): + ctx.__exit__(None, None, None) + return data + + def _stream() -> typing.Iterator[bytes]: + try: + for chunk in data: + yield chunk + finally: + ctx.__exit__(None, None, None) + return _stream() def put( self, @@ -237,14 +264,27 @@ def put( additional_headers=downstream_headers, additional_query_parameters=params or {}, ) - _response = self._raw_client.put( + ctx = self._raw_client.put( url_64, external_user_id=external_user_id, account_id=account_id, request=body or {}, request_options=request_options, ) - return _response.data + _response = ctx.__enter__() + data = _response.data + + if not isinstance(data, Iterator): + ctx.__exit__(None, None, None) + return data + + def _stream() -> typing.Iterator[bytes]: + try: + for chunk in data: + yield chunk + finally: + ctx.__exit__(None, None, None) + return _stream() def delete( self, @@ -304,13 +344,26 @@ def delete( additional_headers=downstream_headers, additional_query_parameters=params or {}, ) - _response = self._raw_client.delete( + ctx = self._raw_client.delete( url_64, external_user_id=external_user_id, account_id=account_id, request_options=request_options, ) - return _response.data + _response = ctx.__enter__() + data = _response.data + + if not isinstance(data, Iterator): + ctx.__exit__(None, None, None) + return data + + def _stream() -> typing.Iterator[bytes]: + try: + for chunk in data: + yield chunk + finally: + ctx.__exit__(None, None, None) + return _stream() def patch( self, @@ -378,14 +431,27 @@ def patch( additional_headers=downstream_headers, additional_query_parameters=params or {}, ) - _response = self._raw_client.patch( + ctx = self._raw_client.patch( url_64, external_user_id=external_user_id, account_id=account_id, request=body or {}, request_options=request_options, ) - return _response.data + _response = ctx.__enter__() + data = _response.data + + if not isinstance(data, Iterator): + ctx.__exit__(None, None, None) + return data + + def _stream() -> typing.Iterator[bytes]: + try: + for chunk in data: + yield chunk + finally: + ctx.__exit__(None, None, None) + return _stream() class AsyncProxyClient: @@ -472,13 +538,26 @@ async def main() -> None: additional_headers=downstream_headers, additional_query_parameters=params or {}, ) - _response = await self._raw_client.get( + ctx = self._raw_client.get( url_64, external_user_id=external_user_id, account_id=account_id, request_options=request_options, ) - return _response.data + _response = await ctx.__aenter__() + data = _response.data + + if not isinstance(data, AsyncIterator): + await ctx.__aexit__(None, None, None) + return data + + async def _stream() -> typing.AsyncIterator[bytes]: + try: + async for chunk in data: + yield chunk + finally: + await ctx.__aexit__(None, None, None) + return _stream() async def post( self, @@ -554,14 +633,27 @@ async def main() -> None: additional_headers=downstream_headers, additional_query_parameters=params or {}, ) - _response = await self._raw_client.post( + ctx = self._raw_client.post( url_64, external_user_id=external_user_id, account_id=account_id, request=body or {}, request_options=request_options, ) - return _response.data + _response = await ctx.__aenter__() + data = _response.data + + if not isinstance(data, AsyncIterator): + await ctx.__aexit__(None, None, None) + return data + + async def _stream() -> typing.AsyncIterator[bytes]: + try: + async for chunk in data: + yield chunk + finally: + await ctx.__aexit__(None, None, None) + return _stream() async def put( self, @@ -637,14 +729,27 @@ async def main() -> None: additional_headers=downstream_headers, additional_query_parameters=params or {}, ) - _response = await self._raw_client.put( + ctx = self._raw_client.put( url_64, external_user_id=external_user_id, account_id=account_id, request=body or {}, request_options=request_options, ) - return _response.data + _response = await ctx.__aenter__() + data = _response.data + + if not isinstance(data, AsyncIterator): + await ctx.__aexit__(None, None, None) + return data + + async def _stream() -> typing.AsyncIterator[bytes]: + try: + async for chunk in data: + yield chunk + finally: + await ctx.__aexit__(None, None, None) + return _stream() async def delete( self, @@ -712,13 +817,26 @@ async def main() -> None: additional_headers=downstream_headers, additional_query_parameters=params or {}, ) - _response = await self._raw_client.delete( + ctx = self._raw_client.delete( url_64, external_user_id=external_user_id, account_id=account_id, request_options=request_options, ) - return _response.data + _response = await ctx.__aenter__() + data = _response.data + + if not isinstance(data, AsyncIterator): + await ctx.__aexit__(None, None, None) + return data + + async def _stream() -> typing.AsyncIterator[bytes]: + try: + async for chunk in data: + yield chunk + finally: + await ctx.__aexit__(None, None, None) + return _stream() async def patch( self, @@ -794,11 +912,24 @@ async def main() -> None: additional_headers=downstream_headers, additional_query_parameters=params or {}, ) - _response = await self._raw_client.patch( + ctx = self._raw_client.patch( url_64, external_user_id=external_user_id, account_id=account_id, request=body or {}, request_options=request_options, ) - return _response.data + _response = await ctx.__aenter__() + data = _response.data + + if not isinstance(data, AsyncIterator): + await ctx.__aexit__(None, None, None) + return data + + async def _stream() -> typing.AsyncIterator[bytes]: + try: + async for chunk in data: + yield chunk + finally: + await ctx.__aexit__(None, None, None) + return _stream() diff --git a/src/pipedream/proxy/raw_client.py b/src/pipedream/proxy/raw_client.py index 8416972..03feb35 100644 --- a/src/pipedream/proxy/raw_client.py +++ b/src/pipedream/proxy/raw_client.py @@ -1,5 +1,6 @@ # This file was auto-generated by Fern from our API Definition. +import contextlib import typing from json.decoder import JSONDecodeError @@ -16,28 +17,11 @@ OMIT = typing.cast(typing.Any, ...) -def _create_stream_iterator(stream_context, response, chunk_size=None): - """Create an iterator that ensures proper cleanup after streaming.""" - try: - for chunk in response.iter_bytes(chunk_size=chunk_size): - yield chunk - finally: - stream_context.__exit__(None, None, None) - - -async def _create_async_stream_iterator(stream_context, response, chunk_size=None): - """Create an async iterator that ensures proper cleanup after streaming.""" - try: - async for chunk in response.aiter_bytes(chunk_size=chunk_size): - yield chunk - finally: - await stream_context.__aexit__(None, None, None) - - class RawProxyClient: def __init__(self, *, client_wrapper: SyncClientWrapper): self._client_wrapper = client_wrapper + @contextlib.contextmanager def get( self, url_64: str, @@ -45,7 +29,7 @@ def get( external_user_id: str, account_id: str, request_options: typing.Optional[RequestOptions] = None, - ) -> HttpResponse[typing.Union[ProxyResponse, typing.Iterator[bytes]]]: + ) -> typing.Iterator[HttpResponse[typing.Union[ProxyResponse, typing.Iterator[bytes]]]]: """ Forward an authenticated GET request to an external API using an external user's account credentials @@ -65,10 +49,10 @@ def get( Returns ------- - HttpResponse[typing.Union[ProxyResponse, typing.Iterator[bytes]]] + typing.Iterator[HttpResponse[typing.Union[ProxyResponse, typing.Iterator[bytes]]]] proxy request successful """ - _response = self._client_wrapper.httpx_client.stream( + with self._client_wrapper.httpx_client.stream( f"v1/connect/{jsonable_encoder(self._client_wrapper._project_id)}/proxy/{jsonable_encoder(url_64)}", method="GET", params={ @@ -76,61 +60,55 @@ def get( "account_id": account_id, }, request_options=request_options, - ) - _stream_context = _response.__enter__() - - try: - if 200 <= _stream_context.status_code < 300: - # Check Content-Type to determine how to handle the response - content_type = _stream_context.headers.get("content-type", "").lower() - is_json = "application/json" in content_type or not content_type - - if is_json: - # Parse as JSON and close the stream - _stream_context.read() - _response.__exit__(None, None, None) - if not _stream_context.text.strip(): - return HttpResponse(response=_stream_context, data=None) - _data = typing.cast( - ProxyResponse, - parse_obj_as( - type_=ProxyResponse, # type: ignore - object_=_stream_context.json(), - ), + ) as _response: + + def _handle_response() -> HttpResponse[typing.Union[ProxyResponse, typing.Iterator[bytes]]]: + try: + if 200 <= _response.status_code < 300: + content_type = _response.headers.get("content-type", "").lower() + is_json = "application/json" in content_type or not content_type + + if is_json: + _response.read() + if not _response.text.strip(): + return HttpResponse(response=_response, data=None) + _data = typing.cast( + ProxyResponse, + parse_obj_as( + type_=ProxyResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + else: + _chunk_size = request_options.get("chunk_size", None) if request_options is not None else None + return HttpResponse(response=_response, data=_response.iter_bytes(chunk_size=_chunk_size)) + _response.read() + _error_content_type = _response.headers.get("content-type", "").lower() + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + typing.Optional[typing.Any], + parse_obj_as( + type_=typing.Optional[typing.Any], # type: ignore + object_=_response.json() if "application/json" in _error_content_type else _response.text, + ), + ), + ) + if "application/json" in _error_content_type: + _response_body = _response.json() + else: + _response_body = _response.text + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.text ) - return HttpResponse(response=_stream_context, data=_data) - else: - # Stream binary content - keep stream open for lazy iteration - _chunk_size = request_options.get("chunk_size", None) if request_options is not None else None - return HttpResponse(response=_stream_context, data=_create_stream_iterator(_response, _stream_context, _chunk_size)) - _stream_context.read() - _response.__exit__(None, None, None) - _error_content_type = _stream_context.headers.get("content-type", "").lower() - if _stream_context.status_code == 429: - raise TooManyRequestsError( - headers=dict(_stream_context.headers), - body=typing.cast( - typing.Optional[typing.Any], - parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore - object_=_stream_context.json() if "application/json" in _error_content_type else _stream_context.text, - ), - ), - ) - if "application/json" in _error_content_type: - _response_body = _stream_context.json() - else: - _response_body = _stream_context.text - except JSONDecodeError: - _response.__exit__(None, None, None) - raise ApiError( - status_code=_stream_context.status_code, headers=dict(_stream_context.headers), body=_stream_context.text - ) - except Exception: - _response.__exit__(None, None, None) - raise - raise ApiError(status_code=_stream_context.status_code, headers=dict(_stream_context.headers), body=_response_body) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_body) + yield _handle_response() + + @contextlib.contextmanager def post( self, url_64: str, @@ -139,7 +117,7 @@ def post( account_id: str, request: typing.Dict[str, typing.Optional[typing.Any]], request_options: typing.Optional[RequestOptions] = None, - ) -> HttpResponse[typing.Union[ProxyResponse, typing.Iterator[bytes]]]: + ) -> typing.Iterator[HttpResponse[typing.Union[ProxyResponse, typing.Iterator[bytes]]]]: """ Forward an authenticated POST request to an external API using an external user's account credentials @@ -161,10 +139,10 @@ def post( Returns ------- - HttpResponse[typing.Union[ProxyResponse, typing.Iterator[bytes]]] + typing.Iterator[HttpResponse[typing.Union[ProxyResponse, typing.Iterator[bytes]]]] proxy request successful """ - _response = self._client_wrapper.httpx_client.stream( + with self._client_wrapper.httpx_client.stream( f"v1/connect/{jsonable_encoder(self._client_wrapper._project_id)}/proxy/{jsonable_encoder(url_64)}", method="POST", params={ @@ -177,61 +155,55 @@ def post( }, request_options=request_options, omit=OMIT, - ) - _stream_context = _response.__enter__() - - try: - if 200 <= _stream_context.status_code < 300: - # Check Content-Type to determine how to handle the response - content_type = _stream_context.headers.get("content-type", "").lower() - is_json = "application/json" in content_type or not content_type - - if is_json: - # Parse as JSON and close the stream - _stream_context.read() - _response.__exit__(None, None, None) - if not _stream_context.text.strip(): - return HttpResponse(response=_stream_context, data=None) - _data = typing.cast( - ProxyResponse, - parse_obj_as( - type_=ProxyResponse, # type: ignore - object_=_stream_context.json(), - ), + ) as _response: + + def _handle_response() -> HttpResponse[typing.Union[ProxyResponse, typing.Iterator[bytes]]]: + try: + if 200 <= _response.status_code < 300: + content_type = _response.headers.get("content-type", "").lower() + is_json = "application/json" in content_type or not content_type + + if is_json: + _response.read() + if not _response.text.strip(): + return HttpResponse(response=_response, data=None) + _data = typing.cast( + ProxyResponse, + parse_obj_as( + type_=ProxyResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + else: + _chunk_size = request_options.get("chunk_size", None) if request_options is not None else None + return HttpResponse(response=_response, data=_response.iter_bytes(chunk_size=_chunk_size)) + _response.read() + _error_content_type = _response.headers.get("content-type", "").lower() + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + typing.Optional[typing.Any], + parse_obj_as( + type_=typing.Optional[typing.Any], # type: ignore + object_=_response.json() if "application/json" in _error_content_type else _response.text, + ), + ), + ) + if "application/json" in _error_content_type: + _response_body = _response.json() + else: + _response_body = _response.text + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.text ) - return HttpResponse(response=_stream_context, data=_data) - else: - # Stream binary content - keep stream open for lazy iteration - _chunk_size = request_options.get("chunk_size", None) if request_options is not None else None - return HttpResponse(response=_stream_context, data=_create_stream_iterator(_response, _stream_context, _chunk_size)) - _stream_context.read() - _response.__exit__(None, None, None) - _error_content_type = _stream_context.headers.get("content-type", "").lower() - if _stream_context.status_code == 429: - raise TooManyRequestsError( - headers=dict(_stream_context.headers), - body=typing.cast( - typing.Optional[typing.Any], - parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore - object_=_stream_context.json() if "application/json" in _error_content_type else _stream_context.text, - ), - ), - ) - if "application/json" in _error_content_type: - _response_body = _stream_context.json() - else: - _response_body = _stream_context.text - except JSONDecodeError: - _response.__exit__(None, None, None) - raise ApiError( - status_code=_stream_context.status_code, headers=dict(_stream_context.headers), body=_stream_context.text - ) - except Exception: - _response.__exit__(None, None, None) - raise - raise ApiError(status_code=_stream_context.status_code, headers=dict(_stream_context.headers), body=_response_body) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_body) + + yield _handle_response() + @contextlib.contextmanager def put( self, url_64: str, @@ -240,7 +212,7 @@ def put( account_id: str, request: typing.Dict[str, typing.Optional[typing.Any]], request_options: typing.Optional[RequestOptions] = None, - ) -> HttpResponse[typing.Union[ProxyResponse, typing.Iterator[bytes]]]: + ) -> typing.Iterator[HttpResponse[typing.Union[ProxyResponse, typing.Iterator[bytes]]]]: """ Forward an authenticated PUT request to an external API using an external user's account credentials @@ -262,10 +234,10 @@ def put( Returns ------- - HttpResponse[typing.Union[ProxyResponse, typing.Iterator[bytes]]] + typing.Iterator[HttpResponse[typing.Union[ProxyResponse, typing.Iterator[bytes]]]] proxy request successful """ - _response = self._client_wrapper.httpx_client.stream( + with self._client_wrapper.httpx_client.stream( f"v1/connect/{jsonable_encoder(self._client_wrapper._project_id)}/proxy/{jsonable_encoder(url_64)}", method="PUT", params={ @@ -278,61 +250,55 @@ def put( }, request_options=request_options, omit=OMIT, - ) - _stream_context = _response.__enter__() - - try: - if 200 <= _stream_context.status_code < 300: - # Check Content-Type to determine how to handle the response - content_type = _stream_context.headers.get("content-type", "").lower() - is_json = "application/json" in content_type or not content_type - - if is_json: - # Parse as JSON and close the stream - _stream_context.read() - _response.__exit__(None, None, None) - if not _stream_context.text.strip(): - return HttpResponse(response=_stream_context, data=None) - _data = typing.cast( - ProxyResponse, - parse_obj_as( - type_=ProxyResponse, # type: ignore - object_=_stream_context.json(), - ), + ) as _response: + + def _handle_response() -> HttpResponse[typing.Union[ProxyResponse, typing.Iterator[bytes]]]: + try: + if 200 <= _response.status_code < 300: + content_type = _response.headers.get("content-type", "").lower() + is_json = "application/json" in content_type or not content_type + + if is_json: + _response.read() + if not _response.text.strip(): + return HttpResponse(response=_response, data=None) + _data = typing.cast( + ProxyResponse, + parse_obj_as( + type_=ProxyResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + else: + _chunk_size = request_options.get("chunk_size", None) if request_options is not None else None + return HttpResponse(response=_response, data=_response.iter_bytes(chunk_size=_chunk_size)) + _response.read() + _error_content_type = _response.headers.get("content-type", "").lower() + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + typing.Optional[typing.Any], + parse_obj_as( + type_=typing.Optional[typing.Any], # type: ignore + object_=_response.json() if "application/json" in _error_content_type else _response.text, + ), + ), + ) + if "application/json" in _error_content_type: + _response_body = _response.json() + else: + _response_body = _response.text + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.text ) - return HttpResponse(response=_stream_context, data=_data) - else: - # Stream binary content - keep stream open for lazy iteration - _chunk_size = request_options.get("chunk_size", None) if request_options is not None else None - return HttpResponse(response=_stream_context, data=_create_stream_iterator(_response, _stream_context, _chunk_size)) - _stream_context.read() - _response.__exit__(None, None, None) - _error_content_type = _stream_context.headers.get("content-type", "").lower() - if _stream_context.status_code == 429: - raise TooManyRequestsError( - headers=dict(_stream_context.headers), - body=typing.cast( - typing.Optional[typing.Any], - parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore - object_=_stream_context.json() if "application/json" in _error_content_type else _stream_context.text, - ), - ), - ) - if "application/json" in _error_content_type: - _response_body = _stream_context.json() - else: - _response_body = _stream_context.text - except JSONDecodeError: - _response.__exit__(None, None, None) - raise ApiError( - status_code=_stream_context.status_code, headers=dict(_stream_context.headers), body=_stream_context.text - ) - except Exception: - _response.__exit__(None, None, None) - raise - raise ApiError(status_code=_stream_context.status_code, headers=dict(_stream_context.headers), body=_response_body) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_body) + + yield _handle_response() + @contextlib.contextmanager def delete( self, url_64: str, @@ -340,7 +306,7 @@ def delete( external_user_id: str, account_id: str, request_options: typing.Optional[RequestOptions] = None, - ) -> HttpResponse[typing.Union[ProxyResponse, typing.Iterator[bytes]]]: + ) -> typing.Iterator[HttpResponse[typing.Union[ProxyResponse, typing.Iterator[bytes]]]]: """ Forward an authenticated DELETE request to an external API using an external user's account credentials @@ -360,10 +326,10 @@ def delete( Returns ------- - HttpResponse[typing.Union[ProxyResponse, typing.Iterator[bytes]]] + typing.Iterator[HttpResponse[typing.Union[ProxyResponse, typing.Iterator[bytes]]]] proxy request successful """ - _response = self._client_wrapper.httpx_client.stream( + with self._client_wrapper.httpx_client.stream( f"v1/connect/{jsonable_encoder(self._client_wrapper._project_id)}/proxy/{jsonable_encoder(url_64)}", method="DELETE", params={ @@ -371,61 +337,55 @@ def delete( "account_id": account_id, }, request_options=request_options, - ) - _stream_context = _response.__enter__() - - try: - if 200 <= _stream_context.status_code < 300: - # Check Content-Type to determine how to handle the response - content_type = _stream_context.headers.get("content-type", "").lower() - is_json = "application/json" in content_type or not content_type - - if is_json: - # Parse as JSON and close the stream - _stream_context.read() - _response.__exit__(None, None, None) - if not _stream_context.text.strip(): - return HttpResponse(response=_stream_context, data=None) - _data = typing.cast( - ProxyResponse, - parse_obj_as( - type_=ProxyResponse, # type: ignore - object_=_stream_context.json(), - ), + ) as _response: + + def _handle_response() -> HttpResponse[typing.Union[ProxyResponse, typing.Iterator[bytes]]]: + try: + if 200 <= _response.status_code < 300: + content_type = _response.headers.get("content-type", "").lower() + is_json = "application/json" in content_type or not content_type + + if is_json: + _response.read() + if not _response.text.strip(): + return HttpResponse(response=_response, data=None) + _data = typing.cast( + ProxyResponse, + parse_obj_as( + type_=ProxyResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + else: + _chunk_size = request_options.get("chunk_size", None) if request_options is not None else None + return HttpResponse(response=_response, data=_response.iter_bytes(chunk_size=_chunk_size)) + _response.read() + _error_content_type = _response.headers.get("content-type", "").lower() + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + typing.Optional[typing.Any], + parse_obj_as( + type_=typing.Optional[typing.Any], # type: ignore + object_=_response.json() if "application/json" in _error_content_type else _response.text, + ), + ), + ) + if "application/json" in _error_content_type: + _response_body = _response.json() + else: + _response_body = _response.text + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.text ) - return HttpResponse(response=_stream_context, data=_data) - else: - # Stream binary content - keep stream open for lazy iteration - _chunk_size = request_options.get("chunk_size", None) if request_options is not None else None - return HttpResponse(response=_stream_context, data=_create_stream_iterator(_response, _stream_context, _chunk_size)) - _stream_context.read() - _response.__exit__(None, None, None) - _error_content_type = _stream_context.headers.get("content-type", "").lower() - if _stream_context.status_code == 429: - raise TooManyRequestsError( - headers=dict(_stream_context.headers), - body=typing.cast( - typing.Optional[typing.Any], - parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore - object_=_stream_context.json() if "application/json" in _error_content_type else _stream_context.text, - ), - ), - ) - if "application/json" in _error_content_type: - _response_body = _stream_context.json() - else: - _response_body = _stream_context.text - except JSONDecodeError: - _response.__exit__(None, None, None) - raise ApiError( - status_code=_stream_context.status_code, headers=dict(_stream_context.headers), body=_stream_context.text - ) - except Exception: - _response.__exit__(None, None, None) - raise - raise ApiError(status_code=_stream_context.status_code, headers=dict(_stream_context.headers), body=_response_body) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_body) + yield _handle_response() + + @contextlib.contextmanager def patch( self, url_64: str, @@ -434,7 +394,7 @@ def patch( account_id: str, request: typing.Dict[str, typing.Optional[typing.Any]], request_options: typing.Optional[RequestOptions] = None, - ) -> HttpResponse[typing.Union[ProxyResponse, typing.Iterator[bytes]]]: + ) -> typing.Iterator[HttpResponse[typing.Union[ProxyResponse, typing.Iterator[bytes]]]]: """ Forward an authenticated PATCH request to an external API using an external user's account credentials @@ -456,10 +416,10 @@ def patch( Returns ------- - HttpResponse[typing.Union[ProxyResponse, typing.Iterator[bytes]]] + typing.Iterator[HttpResponse[typing.Union[ProxyResponse, typing.Iterator[bytes]]]] proxy request successful """ - _response = self._client_wrapper.httpx_client.stream( + with self._client_wrapper.httpx_client.stream( f"v1/connect/{jsonable_encoder(self._client_wrapper._project_id)}/proxy/{jsonable_encoder(url_64)}", method="PATCH", params={ @@ -472,66 +432,60 @@ def patch( }, request_options=request_options, omit=OMIT, - ) - _stream_context = _response.__enter__() - - try: - if 200 <= _stream_context.status_code < 300: - # Check Content-Type to determine how to handle the response - content_type = _stream_context.headers.get("content-type", "").lower() - is_json = "application/json" in content_type or not content_type - - if is_json: - # Parse as JSON and close the stream - _stream_context.read() - _response.__exit__(None, None, None) - if not _stream_context.text.strip(): - return HttpResponse(response=_stream_context, data=None) - _data = typing.cast( - ProxyResponse, - parse_obj_as( - type_=ProxyResponse, # type: ignore - object_=_stream_context.json(), - ), + ) as _response: + + def _handle_response() -> HttpResponse[typing.Union[ProxyResponse, typing.Iterator[bytes]]]: + try: + if 200 <= _response.status_code < 300: + content_type = _response.headers.get("content-type", "").lower() + is_json = "application/json" in content_type or not content_type + + if is_json: + _response.read() + if not _response.text.strip(): + return HttpResponse(response=_response, data=None) + _data = typing.cast( + ProxyResponse, + parse_obj_as( + type_=ProxyResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + else: + _chunk_size = request_options.get("chunk_size", None) if request_options is not None else None + return HttpResponse(response=_response, data=_response.iter_bytes(chunk_size=_chunk_size)) + _response.read() + _error_content_type = _response.headers.get("content-type", "").lower() + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + typing.Optional[typing.Any], + parse_obj_as( + type_=typing.Optional[typing.Any], # type: ignore + object_=_response.json() if "application/json" in _error_content_type else _response.text, + ), + ), + ) + if "application/json" in _error_content_type: + _response_body = _response.json() + else: + _response_body = _response.text + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.text ) - return HttpResponse(response=_stream_context, data=_data) - else: - # Stream binary content - keep stream open for lazy iteration - _chunk_size = request_options.get("chunk_size", None) if request_options is not None else None - return HttpResponse(response=_stream_context, data=_create_stream_iterator(_response, _stream_context, _chunk_size)) - _stream_context.read() - _response.__exit__(None, None, None) - _error_content_type = _stream_context.headers.get("content-type", "").lower() - if _stream_context.status_code == 429: - raise TooManyRequestsError( - headers=dict(_stream_context.headers), - body=typing.cast( - typing.Optional[typing.Any], - parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore - object_=_stream_context.json() if "application/json" in _error_content_type else _stream_context.text, - ), - ), - ) - if "application/json" in _error_content_type: - _response_body = _stream_context.json() - else: - _response_body = _stream_context.text - except JSONDecodeError: - _response.__exit__(None, None, None) - raise ApiError( - status_code=_stream_context.status_code, headers=dict(_stream_context.headers), body=_stream_context.text - ) - except Exception: - _response.__exit__(None, None, None) - raise - raise ApiError(status_code=_stream_context.status_code, headers=dict(_stream_context.headers), body=_response_body) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_body) + + yield _handle_response() class AsyncRawProxyClient: def __init__(self, *, client_wrapper: AsyncClientWrapper): self._client_wrapper = client_wrapper + @contextlib.asynccontextmanager async def get( self, url_64: str, @@ -539,7 +493,7 @@ async def get( external_user_id: str, account_id: str, request_options: typing.Optional[RequestOptions] = None, - ) -> AsyncHttpResponse[typing.Union[ProxyResponse, typing.AsyncIterator[bytes]]]: + ) -> typing.AsyncIterator[AsyncHttpResponse[typing.Union[ProxyResponse, typing.AsyncIterator[bytes]]]]: """ Forward an authenticated GET request to an external API using an external user's account credentials @@ -559,10 +513,10 @@ async def get( Returns ------- - AsyncHttpResponse[typing.Union[ProxyResponse, typing.AsyncIterator[bytes]]] + typing.AsyncIterator[AsyncHttpResponse[typing.Union[ProxyResponse, typing.AsyncIterator[bytes]]]] proxy request successful """ - _response = self._client_wrapper.httpx_client.stream( + async with self._client_wrapper.httpx_client.stream( f"v1/connect/{jsonable_encoder(self._client_wrapper._project_id)}/proxy/{jsonable_encoder(url_64)}", method="GET", params={ @@ -570,61 +524,55 @@ async def get( "account_id": account_id, }, request_options=request_options, - ) - _stream_context = await _response.__aenter__() - - try: - if 200 <= _stream_context.status_code < 300: - # Check Content-Type to determine how to handle the response - content_type = _stream_context.headers.get("content-type", "").lower() - is_json = "application/json" in content_type or not content_type - - if is_json: - # Parse as JSON and close the stream - await _stream_context.aread() - await _response.__aexit__(None, None, None) - if not _stream_context.text.strip(): - return AsyncHttpResponse(response=_stream_context, data=None) - _data = typing.cast( - ProxyResponse, - parse_obj_as( - type_=ProxyResponse, # type: ignore - object_=_stream_context.json(), - ), + ) as _response: + + async def _handle_response() -> AsyncHttpResponse[typing.Union[ProxyResponse, typing.AsyncIterator[bytes]]]: + try: + if 200 <= _response.status_code < 300: + content_type = _response.headers.get("content-type", "").lower() + is_json = "application/json" in content_type or not content_type + + if is_json: + await _response.aread() + if not _response.text.strip(): + return AsyncHttpResponse(response=_response, data=None) + _data = typing.cast( + ProxyResponse, + parse_obj_as( + type_=ProxyResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + else: + _chunk_size = request_options.get("chunk_size", None) if request_options is not None else None + return AsyncHttpResponse(response=_response, data=_response.aiter_bytes(chunk_size=_chunk_size)) + await _response.aread() + _error_content_type = _response.headers.get("content-type", "").lower() + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + typing.Optional[typing.Any], + parse_obj_as( + type_=typing.Optional[typing.Any], # type: ignore + object_=_response.json() if "application/json" in _error_content_type else _response.text, + ), + ), + ) + if "application/json" in _error_content_type: + _response_body = _response.json() + else: + _response_body = _response.text + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.text ) - return AsyncHttpResponse(response=_stream_context, data=_data) - else: - # Stream binary content - keep stream open for lazy iteration - _chunk_size = request_options.get("chunk_size", None) if request_options is not None else None - return AsyncHttpResponse(response=_stream_context, data=_create_async_stream_iterator(_response, _stream_context, _chunk_size)) - await _stream_context.aread() - await _response.__aexit__(None, None, None) - _error_content_type = _stream_context.headers.get("content-type", "").lower() - if _stream_context.status_code == 429: - raise TooManyRequestsError( - headers=dict(_stream_context.headers), - body=typing.cast( - typing.Optional[typing.Any], - parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore - object_=_stream_context.json() if "application/json" in _error_content_type else _stream_context.text, - ), - ), - ) - if "application/json" in _error_content_type: - _response_body = _stream_context.json() - else: - _response_body = _stream_context.text - except JSONDecodeError: - await _response.__aexit__(None, None, None) - raise ApiError( - status_code=_stream_context.status_code, headers=dict(_stream_context.headers), body=_stream_context.text - ) - except Exception: - await _response.__aexit__(None, None, None) - raise - raise ApiError(status_code=_stream_context.status_code, headers=dict(_stream_context.headers), body=_response_body) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_body) + yield await _handle_response() + + @contextlib.asynccontextmanager async def post( self, url_64: str, @@ -633,7 +581,7 @@ async def post( account_id: str, request: typing.Dict[str, typing.Optional[typing.Any]], request_options: typing.Optional[RequestOptions] = None, - ) -> AsyncHttpResponse[typing.Union[ProxyResponse, typing.AsyncIterator[bytes]]]: + ) -> typing.AsyncIterator[AsyncHttpResponse[typing.Union[ProxyResponse, typing.AsyncIterator[bytes]]]]: """ Forward an authenticated POST request to an external API using an external user's account credentials @@ -655,10 +603,10 @@ async def post( Returns ------- - AsyncHttpResponse[typing.Union[ProxyResponse, typing.AsyncIterator[bytes]]] + typing.AsyncIterator[AsyncHttpResponse[typing.Union[ProxyResponse, typing.AsyncIterator[bytes]]]] proxy request successful """ - _response = self._client_wrapper.httpx_client.stream( + async with self._client_wrapper.httpx_client.stream( f"v1/connect/{jsonable_encoder(self._client_wrapper._project_id)}/proxy/{jsonable_encoder(url_64)}", method="POST", params={ @@ -671,61 +619,55 @@ async def post( }, request_options=request_options, omit=OMIT, - ) - _stream_context = await _response.__aenter__() - - try: - if 200 <= _stream_context.status_code < 300: - # Check Content-Type to determine how to handle the response - content_type = _stream_context.headers.get("content-type", "").lower() - is_json = "application/json" in content_type or not content_type - - if is_json: - # Parse as JSON and close the stream - await _stream_context.aread() - await _response.__aexit__(None, None, None) - if not _stream_context.text.strip(): - return AsyncHttpResponse(response=_stream_context, data=None) - _data = typing.cast( - ProxyResponse, - parse_obj_as( - type_=ProxyResponse, # type: ignore - object_=_stream_context.json(), - ), + ) as _response: + + async def _handle_response() -> AsyncHttpResponse[typing.Union[ProxyResponse, typing.AsyncIterator[bytes]]]: + try: + if 200 <= _response.status_code < 300: + content_type = _response.headers.get("content-type", "").lower() + is_json = "application/json" in content_type or not content_type + + if is_json: + await _response.aread() + if not _response.text.strip(): + return AsyncHttpResponse(response=_response, data=None) + _data = typing.cast( + ProxyResponse, + parse_obj_as( + type_=ProxyResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + else: + _chunk_size = request_options.get("chunk_size", None) if request_options is not None else None + return AsyncHttpResponse(response=_response, data=_response.aiter_bytes(chunk_size=_chunk_size)) + await _response.aread() + _error_content_type = _response.headers.get("content-type", "").lower() + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + typing.Optional[typing.Any], + parse_obj_as( + type_=typing.Optional[typing.Any], # type: ignore + object_=_response.json() if "application/json" in _error_content_type else _response.text, + ), + ), + ) + if "application/json" in _error_content_type: + _response_body = _response.json() + else: + _response_body = _response.text + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.text ) - return AsyncHttpResponse(response=_stream_context, data=_data) - else: - # Stream binary content - keep stream open for lazy iteration - _chunk_size = request_options.get("chunk_size", None) if request_options is not None else None - return AsyncHttpResponse(response=_stream_context, data=_create_async_stream_iterator(_response, _stream_context, _chunk_size)) - await _stream_context.aread() - await _response.__aexit__(None, None, None) - _error_content_type = _stream_context.headers.get("content-type", "").lower() - if _stream_context.status_code == 429: - raise TooManyRequestsError( - headers=dict(_stream_context.headers), - body=typing.cast( - typing.Optional[typing.Any], - parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore - object_=_stream_context.json() if "application/json" in _error_content_type else _stream_context.text, - ), - ), - ) - if "application/json" in _error_content_type: - _response_body = _stream_context.json() - else: - _response_body = _stream_context.text - except JSONDecodeError: - await _response.__aexit__(None, None, None) - raise ApiError( - status_code=_stream_context.status_code, headers=dict(_stream_context.headers), body=_stream_context.text - ) - except Exception: - await _response.__aexit__(None, None, None) - raise - raise ApiError(status_code=_stream_context.status_code, headers=dict(_stream_context.headers), body=_response_body) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_body) + + yield await _handle_response() + @contextlib.asynccontextmanager async def put( self, url_64: str, @@ -734,7 +676,7 @@ async def put( account_id: str, request: typing.Dict[str, typing.Optional[typing.Any]], request_options: typing.Optional[RequestOptions] = None, - ) -> AsyncHttpResponse[typing.Union[ProxyResponse, typing.AsyncIterator[bytes]]]: + ) -> typing.AsyncIterator[AsyncHttpResponse[typing.Union[ProxyResponse, typing.AsyncIterator[bytes]]]]: """ Forward an authenticated PUT request to an external API using an external user's account credentials @@ -756,10 +698,10 @@ async def put( Returns ------- - AsyncHttpResponse[typing.Union[ProxyResponse, typing.AsyncIterator[bytes]]] + typing.AsyncIterator[AsyncHttpResponse[typing.Union[ProxyResponse, typing.AsyncIterator[bytes]]]] proxy request successful """ - _response = self._client_wrapper.httpx_client.stream( + async with self._client_wrapper.httpx_client.stream( f"v1/connect/{jsonable_encoder(self._client_wrapper._project_id)}/proxy/{jsonable_encoder(url_64)}", method="PUT", params={ @@ -772,61 +714,55 @@ async def put( }, request_options=request_options, omit=OMIT, - ) - _stream_context = await _response.__aenter__() - - try: - if 200 <= _stream_context.status_code < 300: - # Check Content-Type to determine how to handle the response - content_type = _stream_context.headers.get("content-type", "").lower() - is_json = "application/json" in content_type or not content_type - - if is_json: - # Parse as JSON and close the stream - await _stream_context.aread() - await _response.__aexit__(None, None, None) - if not _stream_context.text.strip(): - return AsyncHttpResponse(response=_stream_context, data=None) - _data = typing.cast( - ProxyResponse, - parse_obj_as( - type_=ProxyResponse, # type: ignore - object_=_stream_context.json(), - ), + ) as _response: + + async def _handle_response() -> AsyncHttpResponse[typing.Union[ProxyResponse, typing.AsyncIterator[bytes]]]: + try: + if 200 <= _response.status_code < 300: + content_type = _response.headers.get("content-type", "").lower() + is_json = "application/json" in content_type or not content_type + + if is_json: + await _response.aread() + if not _response.text.strip(): + return AsyncHttpResponse(response=_response, data=None) + _data = typing.cast( + ProxyResponse, + parse_obj_as( + type_=ProxyResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + else: + _chunk_size = request_options.get("chunk_size", None) if request_options is not None else None + return AsyncHttpResponse(response=_response, data=_response.aiter_bytes(chunk_size=_chunk_size)) + await _response.aread() + _error_content_type = _response.headers.get("content-type", "").lower() + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + typing.Optional[typing.Any], + parse_obj_as( + type_=typing.Optional[typing.Any], # type: ignore + object_=_response.json() if "application/json" in _error_content_type else _response.text, + ), + ), + ) + if "application/json" in _error_content_type: + _response_body = _response.json() + else: + _response_body = _response.text + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.text ) - return AsyncHttpResponse(response=_stream_context, data=_data) - else: - # Stream binary content - keep stream open for lazy iteration - _chunk_size = request_options.get("chunk_size", None) if request_options is not None else None - return AsyncHttpResponse(response=_stream_context, data=_create_async_stream_iterator(_response, _stream_context, _chunk_size)) - await _stream_context.aread() - await _response.__aexit__(None, None, None) - _error_content_type = _stream_context.headers.get("content-type", "").lower() - if _stream_context.status_code == 429: - raise TooManyRequestsError( - headers=dict(_stream_context.headers), - body=typing.cast( - typing.Optional[typing.Any], - parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore - object_=_stream_context.json() if "application/json" in _error_content_type else _stream_context.text, - ), - ), - ) - if "application/json" in _error_content_type: - _response_body = _stream_context.json() - else: - _response_body = _stream_context.text - except JSONDecodeError: - await _response.__aexit__(None, None, None) - raise ApiError( - status_code=_stream_context.status_code, headers=dict(_stream_context.headers), body=_stream_context.text - ) - except Exception: - await _response.__aexit__(None, None, None) - raise - raise ApiError(status_code=_stream_context.status_code, headers=dict(_stream_context.headers), body=_response_body) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_body) + yield await _handle_response() + + @contextlib.asynccontextmanager async def delete( self, url_64: str, @@ -834,7 +770,7 @@ async def delete( external_user_id: str, account_id: str, request_options: typing.Optional[RequestOptions] = None, - ) -> AsyncHttpResponse[typing.Union[ProxyResponse, typing.AsyncIterator[bytes]]]: + ) -> typing.AsyncIterator[AsyncHttpResponse[typing.Union[ProxyResponse, typing.AsyncIterator[bytes]]]]: """ Forward an authenticated DELETE request to an external API using an external user's account credentials @@ -854,10 +790,10 @@ async def delete( Returns ------- - AsyncHttpResponse[typing.Union[ProxyResponse, typing.AsyncIterator[bytes]]] + typing.AsyncIterator[AsyncHttpResponse[typing.Union[ProxyResponse, typing.AsyncIterator[bytes]]]] proxy request successful """ - _response = self._client_wrapper.httpx_client.stream( + async with self._client_wrapper.httpx_client.stream( f"v1/connect/{jsonable_encoder(self._client_wrapper._project_id)}/proxy/{jsonable_encoder(url_64)}", method="DELETE", params={ @@ -865,61 +801,55 @@ async def delete( "account_id": account_id, }, request_options=request_options, - ) - _stream_context = await _response.__aenter__() - - try: - if 200 <= _stream_context.status_code < 300: - # Check Content-Type to determine how to handle the response - content_type = _stream_context.headers.get("content-type", "").lower() - is_json = "application/json" in content_type or not content_type - - if is_json: - # Parse as JSON and close the stream - await _stream_context.aread() - await _response.__aexit__(None, None, None) - if not _stream_context.text.strip(): - return AsyncHttpResponse(response=_stream_context, data=None) - _data = typing.cast( - ProxyResponse, - parse_obj_as( - type_=ProxyResponse, # type: ignore - object_=_stream_context.json(), - ), + ) as _response: + + async def _handle_response() -> AsyncHttpResponse[typing.Union[ProxyResponse, typing.AsyncIterator[bytes]]]: + try: + if 200 <= _response.status_code < 300: + content_type = _response.headers.get("content-type", "").lower() + is_json = "application/json" in content_type or not content_type + + if is_json: + await _response.aread() + if not _response.text.strip(): + return AsyncHttpResponse(response=_response, data=None) + _data = typing.cast( + ProxyResponse, + parse_obj_as( + type_=ProxyResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + else: + _chunk_size = request_options.get("chunk_size", None) if request_options is not None else None + return AsyncHttpResponse(response=_response, data=_response.aiter_bytes(chunk_size=_chunk_size)) + await _response.aread() + _error_content_type = _response.headers.get("content-type", "").lower() + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + typing.Optional[typing.Any], + parse_obj_as( + type_=typing.Optional[typing.Any], # type: ignore + object_=_response.json() if "application/json" in _error_content_type else _response.text, + ), + ), + ) + if "application/json" in _error_content_type: + _response_body = _response.json() + else: + _response_body = _response.text + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.text ) - return AsyncHttpResponse(response=_stream_context, data=_data) - else: - # Stream binary content - keep stream open for lazy iteration - _chunk_size = request_options.get("chunk_size", None) if request_options is not None else None - return AsyncHttpResponse(response=_stream_context, data=_create_async_stream_iterator(_response, _stream_context, _chunk_size)) - await _stream_context.aread() - await _response.__aexit__(None, None, None) - _error_content_type = _stream_context.headers.get("content-type", "").lower() - if _stream_context.status_code == 429: - raise TooManyRequestsError( - headers=dict(_stream_context.headers), - body=typing.cast( - typing.Optional[typing.Any], - parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore - object_=_stream_context.json() if "application/json" in _error_content_type else _stream_context.text, - ), - ), - ) - if "application/json" in _error_content_type: - _response_body = _stream_context.json() - else: - _response_body = _stream_context.text - except JSONDecodeError: - await _response.__aexit__(None, None, None) - raise ApiError( - status_code=_stream_context.status_code, headers=dict(_stream_context.headers), body=_stream_context.text - ) - except Exception: - await _response.__aexit__(None, None, None) - raise - raise ApiError(status_code=_stream_context.status_code, headers=dict(_stream_context.headers), body=_response_body) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_body) + + yield await _handle_response() + @contextlib.asynccontextmanager async def patch( self, url_64: str, @@ -928,7 +858,7 @@ async def patch( account_id: str, request: typing.Dict[str, typing.Optional[typing.Any]], request_options: typing.Optional[RequestOptions] = None, - ) -> AsyncHttpResponse[typing.Union[ProxyResponse, typing.AsyncIterator[bytes]]]: + ) -> typing.AsyncIterator[AsyncHttpResponse[typing.Union[ProxyResponse, typing.AsyncIterator[bytes]]]]: """ Forward an authenticated PATCH request to an external API using an external user's account credentials @@ -950,10 +880,10 @@ async def patch( Returns ------- - AsyncHttpResponse[typing.Union[ProxyResponse, typing.AsyncIterator[bytes]]] + typing.AsyncIterator[AsyncHttpResponse[typing.Union[ProxyResponse, typing.AsyncIterator[bytes]]]] proxy request successful """ - _response = self._client_wrapper.httpx_client.stream( + async with self._client_wrapper.httpx_client.stream( f"v1/connect/{jsonable_encoder(self._client_wrapper._project_id)}/proxy/{jsonable_encoder(url_64)}", method="PATCH", params={ @@ -966,57 +896,50 @@ async def patch( }, request_options=request_options, omit=OMIT, - ) - _stream_context = await _response.__aenter__() - - try: - if 200 <= _stream_context.status_code < 300: - # Check Content-Type to determine how to handle the response - content_type = _stream_context.headers.get("content-type", "").lower() - is_json = "application/json" in content_type or not content_type - - if is_json: - # Parse as JSON and close the stream - await _stream_context.aread() - await _response.__aexit__(None, None, None) - if not _stream_context.text.strip(): - return AsyncHttpResponse(response=_stream_context, data=None) - _data = typing.cast( - ProxyResponse, - parse_obj_as( - type_=ProxyResponse, # type: ignore - object_=_stream_context.json(), - ), + ) as _response: + + async def _handle_response() -> AsyncHttpResponse[typing.Union[ProxyResponse, typing.AsyncIterator[bytes]]]: + try: + if 200 <= _response.status_code < 300: + content_type = _response.headers.get("content-type", "").lower() + is_json = "application/json" in content_type or not content_type + + if is_json: + await _response.aread() + if not _response.text.strip(): + return AsyncHttpResponse(response=_response, data=None) + _data = typing.cast( + ProxyResponse, + parse_obj_as( + type_=ProxyResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + else: + _chunk_size = request_options.get("chunk_size", None) if request_options is not None else None + return AsyncHttpResponse(response=_response, data=_response.aiter_bytes(chunk_size=_chunk_size)) + await _response.aread() + _error_content_type = _response.headers.get("content-type", "").lower() + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + typing.Optional[typing.Any], + parse_obj_as( + type_=typing.Optional[typing.Any], # type: ignore + object_=_response.json() if "application/json" in _error_content_type else _response.text, + ), + ), + ) + if "application/json" in _error_content_type: + _response_body = _response.json() + else: + _response_body = _response.text + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.text ) - return AsyncHttpResponse(response=_stream_context, data=_data) - else: - # Stream binary content - keep stream open for lazy iteration - _chunk_size = request_options.get("chunk_size", None) if request_options is not None else None - return AsyncHttpResponse(response=_stream_context, data=_create_async_stream_iterator(_response, _stream_context, _chunk_size)) - await _stream_context.aread() - await _response.__aexit__(None, None, None) - _error_content_type = _stream_context.headers.get("content-type", "").lower() - if _stream_context.status_code == 429: - raise TooManyRequestsError( - headers=dict(_stream_context.headers), - body=typing.cast( - typing.Optional[typing.Any], - parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore - object_=_stream_context.json() if "application/json" in _error_content_type else _stream_context.text, - ), - ), - ) - if "application/json" in _error_content_type: - _response_body = _stream_context.json() - else: - _response_body = _stream_context.text - except JSONDecodeError: - await _response.__aexit__(None, None, None) - raise ApiError( - status_code=_stream_context.status_code, headers=dict(_stream_context.headers), body=_stream_context.text - ) - except Exception: - await _response.__aexit__(None, None, None) - raise - raise ApiError(status_code=_stream_context.status_code, headers=dict(_stream_context.headers), body=_response_body) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_body) + + yield await _handle_response() From ffa17e63a4e5b1a3e3c756b7b26cd666f85f1c50 Mon Sep 17 00:00:00 2001 From: Jay Vercellone Date: Tue, 2 Dec 2025 19:06:44 -0300 Subject: [PATCH 10/13] Lock proxy low-level client --- .fernignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.fernignore b/.fernignore index e76b5b7..d4a6c0b 100644 --- a/.fernignore +++ b/.fernignore @@ -18,6 +18,7 @@ src/pipedream/pipedream.py # Custom Proxy files src/pipedream/proxy/client.py +src/pipedream/proxy/raw_client.py # Custom Workflow files src/pipedream/workflows/__init__.py From e877151726dc82eb847983c13603b67af40ebc7d Mon Sep 17 00:00:00 2001 From: Jay Vercellone Date: Tue, 2 Dec 2025 19:08:48 -0300 Subject: [PATCH 11/13] Lint files --- src/pipedream/client.py | 2 +- src/pipedream/proxy/client.py | 10 + src/pipedream/proxy/raw_client.py | 622 +++++++++++++++++++----------- 3 files changed, 398 insertions(+), 236 deletions(-) diff --git a/src/pipedream/client.py b/src/pipedream/client.py index 6f4accc..514ef44 100644 --- a/src/pipedream/client.py +++ b/src/pipedream/client.py @@ -6,11 +6,11 @@ import typing import httpx -from .types.project_environment import ProjectEnvironment from .core.api_error import ApiError from .core.client_wrapper import AsyncClientWrapper, SyncClientWrapper from .core.oauth_token_provider import OAuthTokenProvider from .environment import PipedreamEnvironment +from .types.project_environment import ProjectEnvironment if typing.TYPE_CHECKING: from .accounts.client import AccountsClient, AsyncAccountsClient diff --git a/src/pipedream/proxy/client.py b/src/pipedream/proxy/client.py index 4e5e169..7e293d3 100644 --- a/src/pipedream/proxy/client.py +++ b/src/pipedream/proxy/client.py @@ -108,6 +108,7 @@ def _stream() -> typing.Iterator[bytes]: yield chunk finally: ctx.__exit__(None, None, None) + return _stream() def post( @@ -196,6 +197,7 @@ def _stream() -> typing.Iterator[bytes]: yield chunk finally: ctx.__exit__(None, None, None) + return _stream() def put( @@ -284,6 +286,7 @@ def _stream() -> typing.Iterator[bytes]: yield chunk finally: ctx.__exit__(None, None, None) + return _stream() def delete( @@ -363,6 +366,7 @@ def _stream() -> typing.Iterator[bytes]: yield chunk finally: ctx.__exit__(None, None, None) + return _stream() def patch( @@ -451,6 +455,7 @@ def _stream() -> typing.Iterator[bytes]: yield chunk finally: ctx.__exit__(None, None, None) + return _stream() @@ -557,6 +562,7 @@ async def _stream() -> typing.AsyncIterator[bytes]: yield chunk finally: await ctx.__aexit__(None, None, None) + return _stream() async def post( @@ -653,6 +659,7 @@ async def _stream() -> typing.AsyncIterator[bytes]: yield chunk finally: await ctx.__aexit__(None, None, None) + return _stream() async def put( @@ -749,6 +756,7 @@ async def _stream() -> typing.AsyncIterator[bytes]: yield chunk finally: await ctx.__aexit__(None, None, None) + return _stream() async def delete( @@ -836,6 +844,7 @@ async def _stream() -> typing.AsyncIterator[bytes]: yield chunk finally: await ctx.__aexit__(None, None, None) + return _stream() async def patch( @@ -932,4 +941,5 @@ async def _stream() -> typing.AsyncIterator[bytes]: yield chunk finally: await ctx.__aexit__(None, None, None) + return _stream() diff --git a/src/pipedream/proxy/raw_client.py b/src/pipedream/proxy/raw_client.py index 03feb35..45771a1 100644 --- a/src/pipedream/proxy/raw_client.py +++ b/src/pipedream/proxy/raw_client.py @@ -18,6 +18,7 @@ class RawProxyClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): self._client_wrapper = client_wrapper @@ -29,7 +30,8 @@ def get( external_user_id: str, account_id: str, request_options: typing.Optional[RequestOptions] = None, - ) -> typing.Iterator[HttpResponse[typing.Union[ProxyResponse, typing.Iterator[bytes]]]]: + ) -> typing.Iterator[HttpResponse[typing.Union[ProxyResponse, + typing.Iterator[bytes]]]]: """ Forward an authenticated GET request to an external API using an external user's account credentials @@ -53,25 +55,28 @@ def get( proxy request successful """ with self._client_wrapper.httpx_client.stream( - f"v1/connect/{jsonable_encoder(self._client_wrapper._project_id)}/proxy/{jsonable_encoder(url_64)}", - method="GET", - params={ - "external_user_id": external_user_id, - "account_id": account_id, - }, - request_options=request_options, + f"v1/connect/{jsonable_encoder(self._client_wrapper._project_id)}/proxy/{jsonable_encoder(url_64)}", + method="GET", + params={ + "external_user_id": external_user_id, + "account_id": account_id, + }, + request_options=request_options, ) as _response: - def _handle_response() -> HttpResponse[typing.Union[ProxyResponse, typing.Iterator[bytes]]]: + def _handle_response() -> HttpResponse[typing.Union[ + ProxyResponse, typing.Iterator[bytes]]]: try: if 200 <= _response.status_code < 300: - content_type = _response.headers.get("content-type", "").lower() + content_type = _response.headers.get( + "content-type", "").lower() is_json = "application/json" in content_type or not content_type if is_json: _response.read() if not _response.text.strip(): - return HttpResponse(response=_response, data=None) + return HttpResponse(response=_response, + data=None) _data = typing.cast( ProxyResponse, parse_obj_as( @@ -81,18 +86,26 @@ def _handle_response() -> HttpResponse[typing.Union[ProxyResponse, typing.Iterat ) return HttpResponse(response=_response, data=_data) else: - _chunk_size = request_options.get("chunk_size", None) if request_options is not None else None - return HttpResponse(response=_response, data=_response.iter_bytes(chunk_size=_chunk_size)) + _chunk_size = request_options.get( + "chunk_size", + None) if request_options is not None else None + return HttpResponse(response=_response, + data=_response.iter_bytes( + chunk_size=_chunk_size)) _response.read() - _error_content_type = _response.headers.get("content-type", "").lower() + _error_content_type = _response.headers.get( + "content-type", "").lower() if _response.status_code == 429: raise TooManyRequestsError( headers=dict(_response.headers), body=typing.cast( typing.Optional[typing.Any], parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore - object_=_response.json() if "application/json" in _error_content_type else _response.text, + type_=typing.Optional[ + typing.Any], # type: ignore + object_=_response.json() + if "application/json" + in _error_content_type else _response.text, ), ), ) @@ -101,10 +114,12 @@ def _handle_response() -> HttpResponse[typing.Union[ProxyResponse, typing.Iterat else: _response_body = _response.text except JSONDecodeError: - raise ApiError( - status_code=_response.status_code, headers=dict(_response.headers), body=_response.text - ) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_body) + raise ApiError(status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text) + raise ApiError(status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_body) yield _handle_response() @@ -117,7 +132,8 @@ def post( account_id: str, request: typing.Dict[str, typing.Optional[typing.Any]], request_options: typing.Optional[RequestOptions] = None, - ) -> typing.Iterator[HttpResponse[typing.Union[ProxyResponse, typing.Iterator[bytes]]]]: + ) -> typing.Iterator[HttpResponse[typing.Union[ProxyResponse, + typing.Iterator[bytes]]]]: """ Forward an authenticated POST request to an external API using an external user's account credentials @@ -143,30 +159,33 @@ def post( proxy request successful """ with self._client_wrapper.httpx_client.stream( - f"v1/connect/{jsonable_encoder(self._client_wrapper._project_id)}/proxy/{jsonable_encoder(url_64)}", - method="POST", - params={ - "external_user_id": external_user_id, - "account_id": account_id, - }, - json=request, - headers={ - "content-type": "application/json", - }, - request_options=request_options, - omit=OMIT, + f"v1/connect/{jsonable_encoder(self._client_wrapper._project_id)}/proxy/{jsonable_encoder(url_64)}", + method="POST", + params={ + "external_user_id": external_user_id, + "account_id": account_id, + }, + json=request, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, ) as _response: - def _handle_response() -> HttpResponse[typing.Union[ProxyResponse, typing.Iterator[bytes]]]: + def _handle_response() -> HttpResponse[typing.Union[ + ProxyResponse, typing.Iterator[bytes]]]: try: if 200 <= _response.status_code < 300: - content_type = _response.headers.get("content-type", "").lower() + content_type = _response.headers.get( + "content-type", "").lower() is_json = "application/json" in content_type or not content_type if is_json: _response.read() if not _response.text.strip(): - return HttpResponse(response=_response, data=None) + return HttpResponse(response=_response, + data=None) _data = typing.cast( ProxyResponse, parse_obj_as( @@ -176,18 +195,26 @@ def _handle_response() -> HttpResponse[typing.Union[ProxyResponse, typing.Iterat ) return HttpResponse(response=_response, data=_data) else: - _chunk_size = request_options.get("chunk_size", None) if request_options is not None else None - return HttpResponse(response=_response, data=_response.iter_bytes(chunk_size=_chunk_size)) + _chunk_size = request_options.get( + "chunk_size", + None) if request_options is not None else None + return HttpResponse(response=_response, + data=_response.iter_bytes( + chunk_size=_chunk_size)) _response.read() - _error_content_type = _response.headers.get("content-type", "").lower() + _error_content_type = _response.headers.get( + "content-type", "").lower() if _response.status_code == 429: raise TooManyRequestsError( headers=dict(_response.headers), body=typing.cast( typing.Optional[typing.Any], parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore - object_=_response.json() if "application/json" in _error_content_type else _response.text, + type_=typing.Optional[ + typing.Any], # type: ignore + object_=_response.json() + if "application/json" + in _error_content_type else _response.text, ), ), ) @@ -196,10 +223,12 @@ def _handle_response() -> HttpResponse[typing.Union[ProxyResponse, typing.Iterat else: _response_body = _response.text except JSONDecodeError: - raise ApiError( - status_code=_response.status_code, headers=dict(_response.headers), body=_response.text - ) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_body) + raise ApiError(status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text) + raise ApiError(status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_body) yield _handle_response() @@ -212,7 +241,8 @@ def put( account_id: str, request: typing.Dict[str, typing.Optional[typing.Any]], request_options: typing.Optional[RequestOptions] = None, - ) -> typing.Iterator[HttpResponse[typing.Union[ProxyResponse, typing.Iterator[bytes]]]]: + ) -> typing.Iterator[HttpResponse[typing.Union[ProxyResponse, + typing.Iterator[bytes]]]]: """ Forward an authenticated PUT request to an external API using an external user's account credentials @@ -238,30 +268,33 @@ def put( proxy request successful """ with self._client_wrapper.httpx_client.stream( - f"v1/connect/{jsonable_encoder(self._client_wrapper._project_id)}/proxy/{jsonable_encoder(url_64)}", - method="PUT", - params={ - "external_user_id": external_user_id, - "account_id": account_id, - }, - json=request, - headers={ - "content-type": "application/json", - }, - request_options=request_options, - omit=OMIT, + f"v1/connect/{jsonable_encoder(self._client_wrapper._project_id)}/proxy/{jsonable_encoder(url_64)}", + method="PUT", + params={ + "external_user_id": external_user_id, + "account_id": account_id, + }, + json=request, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, ) as _response: - def _handle_response() -> HttpResponse[typing.Union[ProxyResponse, typing.Iterator[bytes]]]: + def _handle_response() -> HttpResponse[typing.Union[ + ProxyResponse, typing.Iterator[bytes]]]: try: if 200 <= _response.status_code < 300: - content_type = _response.headers.get("content-type", "").lower() + content_type = _response.headers.get( + "content-type", "").lower() is_json = "application/json" in content_type or not content_type if is_json: _response.read() if not _response.text.strip(): - return HttpResponse(response=_response, data=None) + return HttpResponse(response=_response, + data=None) _data = typing.cast( ProxyResponse, parse_obj_as( @@ -271,18 +304,26 @@ def _handle_response() -> HttpResponse[typing.Union[ProxyResponse, typing.Iterat ) return HttpResponse(response=_response, data=_data) else: - _chunk_size = request_options.get("chunk_size", None) if request_options is not None else None - return HttpResponse(response=_response, data=_response.iter_bytes(chunk_size=_chunk_size)) + _chunk_size = request_options.get( + "chunk_size", + None) if request_options is not None else None + return HttpResponse(response=_response, + data=_response.iter_bytes( + chunk_size=_chunk_size)) _response.read() - _error_content_type = _response.headers.get("content-type", "").lower() + _error_content_type = _response.headers.get( + "content-type", "").lower() if _response.status_code == 429: raise TooManyRequestsError( headers=dict(_response.headers), body=typing.cast( typing.Optional[typing.Any], parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore - object_=_response.json() if "application/json" in _error_content_type else _response.text, + type_=typing.Optional[ + typing.Any], # type: ignore + object_=_response.json() + if "application/json" + in _error_content_type else _response.text, ), ), ) @@ -291,10 +332,12 @@ def _handle_response() -> HttpResponse[typing.Union[ProxyResponse, typing.Iterat else: _response_body = _response.text except JSONDecodeError: - raise ApiError( - status_code=_response.status_code, headers=dict(_response.headers), body=_response.text - ) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_body) + raise ApiError(status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text) + raise ApiError(status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_body) yield _handle_response() @@ -306,7 +349,8 @@ def delete( external_user_id: str, account_id: str, request_options: typing.Optional[RequestOptions] = None, - ) -> typing.Iterator[HttpResponse[typing.Union[ProxyResponse, typing.Iterator[bytes]]]]: + ) -> typing.Iterator[HttpResponse[typing.Union[ProxyResponse, + typing.Iterator[bytes]]]]: """ Forward an authenticated DELETE request to an external API using an external user's account credentials @@ -330,25 +374,28 @@ def delete( proxy request successful """ with self._client_wrapper.httpx_client.stream( - f"v1/connect/{jsonable_encoder(self._client_wrapper._project_id)}/proxy/{jsonable_encoder(url_64)}", - method="DELETE", - params={ - "external_user_id": external_user_id, - "account_id": account_id, - }, - request_options=request_options, + f"v1/connect/{jsonable_encoder(self._client_wrapper._project_id)}/proxy/{jsonable_encoder(url_64)}", + method="DELETE", + params={ + "external_user_id": external_user_id, + "account_id": account_id, + }, + request_options=request_options, ) as _response: - def _handle_response() -> HttpResponse[typing.Union[ProxyResponse, typing.Iterator[bytes]]]: + def _handle_response() -> HttpResponse[typing.Union[ + ProxyResponse, typing.Iterator[bytes]]]: try: if 200 <= _response.status_code < 300: - content_type = _response.headers.get("content-type", "").lower() + content_type = _response.headers.get( + "content-type", "").lower() is_json = "application/json" in content_type or not content_type if is_json: _response.read() if not _response.text.strip(): - return HttpResponse(response=_response, data=None) + return HttpResponse(response=_response, + data=None) _data = typing.cast( ProxyResponse, parse_obj_as( @@ -358,18 +405,26 @@ def _handle_response() -> HttpResponse[typing.Union[ProxyResponse, typing.Iterat ) return HttpResponse(response=_response, data=_data) else: - _chunk_size = request_options.get("chunk_size", None) if request_options is not None else None - return HttpResponse(response=_response, data=_response.iter_bytes(chunk_size=_chunk_size)) + _chunk_size = request_options.get( + "chunk_size", + None) if request_options is not None else None + return HttpResponse(response=_response, + data=_response.iter_bytes( + chunk_size=_chunk_size)) _response.read() - _error_content_type = _response.headers.get("content-type", "").lower() + _error_content_type = _response.headers.get( + "content-type", "").lower() if _response.status_code == 429: raise TooManyRequestsError( headers=dict(_response.headers), body=typing.cast( typing.Optional[typing.Any], parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore - object_=_response.json() if "application/json" in _error_content_type else _response.text, + type_=typing.Optional[ + typing.Any], # type: ignore + object_=_response.json() + if "application/json" + in _error_content_type else _response.text, ), ), ) @@ -378,10 +433,12 @@ def _handle_response() -> HttpResponse[typing.Union[ProxyResponse, typing.Iterat else: _response_body = _response.text except JSONDecodeError: - raise ApiError( - status_code=_response.status_code, headers=dict(_response.headers), body=_response.text - ) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_body) + raise ApiError(status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text) + raise ApiError(status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_body) yield _handle_response() @@ -394,7 +451,8 @@ def patch( account_id: str, request: typing.Dict[str, typing.Optional[typing.Any]], request_options: typing.Optional[RequestOptions] = None, - ) -> typing.Iterator[HttpResponse[typing.Union[ProxyResponse, typing.Iterator[bytes]]]]: + ) -> typing.Iterator[HttpResponse[typing.Union[ProxyResponse, + typing.Iterator[bytes]]]]: """ Forward an authenticated PATCH request to an external API using an external user's account credentials @@ -420,30 +478,33 @@ def patch( proxy request successful """ with self._client_wrapper.httpx_client.stream( - f"v1/connect/{jsonable_encoder(self._client_wrapper._project_id)}/proxy/{jsonable_encoder(url_64)}", - method="PATCH", - params={ - "external_user_id": external_user_id, - "account_id": account_id, - }, - json=request, - headers={ - "content-type": "application/json", - }, - request_options=request_options, - omit=OMIT, + f"v1/connect/{jsonable_encoder(self._client_wrapper._project_id)}/proxy/{jsonable_encoder(url_64)}", + method="PATCH", + params={ + "external_user_id": external_user_id, + "account_id": account_id, + }, + json=request, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, ) as _response: - def _handle_response() -> HttpResponse[typing.Union[ProxyResponse, typing.Iterator[bytes]]]: + def _handle_response() -> HttpResponse[typing.Union[ + ProxyResponse, typing.Iterator[bytes]]]: try: if 200 <= _response.status_code < 300: - content_type = _response.headers.get("content-type", "").lower() + content_type = _response.headers.get( + "content-type", "").lower() is_json = "application/json" in content_type or not content_type if is_json: _response.read() if not _response.text.strip(): - return HttpResponse(response=_response, data=None) + return HttpResponse(response=_response, + data=None) _data = typing.cast( ProxyResponse, parse_obj_as( @@ -453,18 +514,26 @@ def _handle_response() -> HttpResponse[typing.Union[ProxyResponse, typing.Iterat ) return HttpResponse(response=_response, data=_data) else: - _chunk_size = request_options.get("chunk_size", None) if request_options is not None else None - return HttpResponse(response=_response, data=_response.iter_bytes(chunk_size=_chunk_size)) + _chunk_size = request_options.get( + "chunk_size", + None) if request_options is not None else None + return HttpResponse(response=_response, + data=_response.iter_bytes( + chunk_size=_chunk_size)) _response.read() - _error_content_type = _response.headers.get("content-type", "").lower() + _error_content_type = _response.headers.get( + "content-type", "").lower() if _response.status_code == 429: raise TooManyRequestsError( headers=dict(_response.headers), body=typing.cast( typing.Optional[typing.Any], parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore - object_=_response.json() if "application/json" in _error_content_type else _response.text, + type_=typing.Optional[ + typing.Any], # type: ignore + object_=_response.json() + if "application/json" + in _error_content_type else _response.text, ), ), ) @@ -473,15 +542,18 @@ def _handle_response() -> HttpResponse[typing.Union[ProxyResponse, typing.Iterat else: _response_body = _response.text except JSONDecodeError: - raise ApiError( - status_code=_response.status_code, headers=dict(_response.headers), body=_response.text - ) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_body) + raise ApiError(status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text) + raise ApiError(status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_body) yield _handle_response() class AsyncRawProxyClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): self._client_wrapper = client_wrapper @@ -493,7 +565,8 @@ async def get( external_user_id: str, account_id: str, request_options: typing.Optional[RequestOptions] = None, - ) -> typing.AsyncIterator[AsyncHttpResponse[typing.Union[ProxyResponse, typing.AsyncIterator[bytes]]]]: + ) -> typing.AsyncIterator[AsyncHttpResponse[typing.Union[ + ProxyResponse, typing.AsyncIterator[bytes]]]]: """ Forward an authenticated GET request to an external API using an external user's account credentials @@ -517,25 +590,28 @@ async def get( proxy request successful """ async with self._client_wrapper.httpx_client.stream( - f"v1/connect/{jsonable_encoder(self._client_wrapper._project_id)}/proxy/{jsonable_encoder(url_64)}", - method="GET", - params={ - "external_user_id": external_user_id, - "account_id": account_id, - }, - request_options=request_options, + f"v1/connect/{jsonable_encoder(self._client_wrapper._project_id)}/proxy/{jsonable_encoder(url_64)}", + method="GET", + params={ + "external_user_id": external_user_id, + "account_id": account_id, + }, + request_options=request_options, ) as _response: - async def _handle_response() -> AsyncHttpResponse[typing.Union[ProxyResponse, typing.AsyncIterator[bytes]]]: + async def _handle_response() -> AsyncHttpResponse[typing.Union[ + ProxyResponse, typing.AsyncIterator[bytes]]]: try: if 200 <= _response.status_code < 300: - content_type = _response.headers.get("content-type", "").lower() + content_type = _response.headers.get( + "content-type", "").lower() is_json = "application/json" in content_type or not content_type if is_json: await _response.aread() if not _response.text.strip(): - return AsyncHttpResponse(response=_response, data=None) + return AsyncHttpResponse(response=_response, + data=None) _data = typing.cast( ProxyResponse, parse_obj_as( @@ -543,20 +619,30 @@ async def _handle_response() -> AsyncHttpResponse[typing.Union[ProxyResponse, ty object_=_response.json(), ), ) - return AsyncHttpResponse(response=_response, data=_data) + return AsyncHttpResponse(response=_response, + data=_data) else: - _chunk_size = request_options.get("chunk_size", None) if request_options is not None else None - return AsyncHttpResponse(response=_response, data=_response.aiter_bytes(chunk_size=_chunk_size)) + _chunk_size = request_options.get( + "chunk_size", + None) if request_options is not None else None + return AsyncHttpResponse( + response=_response, + data=_response.aiter_bytes( + chunk_size=_chunk_size)) await _response.aread() - _error_content_type = _response.headers.get("content-type", "").lower() + _error_content_type = _response.headers.get( + "content-type", "").lower() if _response.status_code == 429: raise TooManyRequestsError( headers=dict(_response.headers), body=typing.cast( typing.Optional[typing.Any], parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore - object_=_response.json() if "application/json" in _error_content_type else _response.text, + type_=typing.Optional[ + typing.Any], # type: ignore + object_=_response.json() + if "application/json" + in _error_content_type else _response.text, ), ), ) @@ -565,10 +651,12 @@ async def _handle_response() -> AsyncHttpResponse[typing.Union[ProxyResponse, ty else: _response_body = _response.text except JSONDecodeError: - raise ApiError( - status_code=_response.status_code, headers=dict(_response.headers), body=_response.text - ) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_body) + raise ApiError(status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text) + raise ApiError(status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_body) yield await _handle_response() @@ -581,7 +669,8 @@ async def post( account_id: str, request: typing.Dict[str, typing.Optional[typing.Any]], request_options: typing.Optional[RequestOptions] = None, - ) -> typing.AsyncIterator[AsyncHttpResponse[typing.Union[ProxyResponse, typing.AsyncIterator[bytes]]]]: + ) -> typing.AsyncIterator[AsyncHttpResponse[typing.Union[ + ProxyResponse, typing.AsyncIterator[bytes]]]]: """ Forward an authenticated POST request to an external API using an external user's account credentials @@ -607,30 +696,33 @@ async def post( proxy request successful """ async with self._client_wrapper.httpx_client.stream( - f"v1/connect/{jsonable_encoder(self._client_wrapper._project_id)}/proxy/{jsonable_encoder(url_64)}", - method="POST", - params={ - "external_user_id": external_user_id, - "account_id": account_id, - }, - json=request, - headers={ - "content-type": "application/json", - }, - request_options=request_options, - omit=OMIT, + f"v1/connect/{jsonable_encoder(self._client_wrapper._project_id)}/proxy/{jsonable_encoder(url_64)}", + method="POST", + params={ + "external_user_id": external_user_id, + "account_id": account_id, + }, + json=request, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, ) as _response: - async def _handle_response() -> AsyncHttpResponse[typing.Union[ProxyResponse, typing.AsyncIterator[bytes]]]: + async def _handle_response() -> AsyncHttpResponse[typing.Union[ + ProxyResponse, typing.AsyncIterator[bytes]]]: try: if 200 <= _response.status_code < 300: - content_type = _response.headers.get("content-type", "").lower() + content_type = _response.headers.get( + "content-type", "").lower() is_json = "application/json" in content_type or not content_type if is_json: await _response.aread() if not _response.text.strip(): - return AsyncHttpResponse(response=_response, data=None) + return AsyncHttpResponse(response=_response, + data=None) _data = typing.cast( ProxyResponse, parse_obj_as( @@ -638,20 +730,30 @@ async def _handle_response() -> AsyncHttpResponse[typing.Union[ProxyResponse, ty object_=_response.json(), ), ) - return AsyncHttpResponse(response=_response, data=_data) + return AsyncHttpResponse(response=_response, + data=_data) else: - _chunk_size = request_options.get("chunk_size", None) if request_options is not None else None - return AsyncHttpResponse(response=_response, data=_response.aiter_bytes(chunk_size=_chunk_size)) + _chunk_size = request_options.get( + "chunk_size", + None) if request_options is not None else None + return AsyncHttpResponse( + response=_response, + data=_response.aiter_bytes( + chunk_size=_chunk_size)) await _response.aread() - _error_content_type = _response.headers.get("content-type", "").lower() + _error_content_type = _response.headers.get( + "content-type", "").lower() if _response.status_code == 429: raise TooManyRequestsError( headers=dict(_response.headers), body=typing.cast( typing.Optional[typing.Any], parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore - object_=_response.json() if "application/json" in _error_content_type else _response.text, + type_=typing.Optional[ + typing.Any], # type: ignore + object_=_response.json() + if "application/json" + in _error_content_type else _response.text, ), ), ) @@ -660,10 +762,12 @@ async def _handle_response() -> AsyncHttpResponse[typing.Union[ProxyResponse, ty else: _response_body = _response.text except JSONDecodeError: - raise ApiError( - status_code=_response.status_code, headers=dict(_response.headers), body=_response.text - ) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_body) + raise ApiError(status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text) + raise ApiError(status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_body) yield await _handle_response() @@ -676,7 +780,8 @@ async def put( account_id: str, request: typing.Dict[str, typing.Optional[typing.Any]], request_options: typing.Optional[RequestOptions] = None, - ) -> typing.AsyncIterator[AsyncHttpResponse[typing.Union[ProxyResponse, typing.AsyncIterator[bytes]]]]: + ) -> typing.AsyncIterator[AsyncHttpResponse[typing.Union[ + ProxyResponse, typing.AsyncIterator[bytes]]]]: """ Forward an authenticated PUT request to an external API using an external user's account credentials @@ -702,30 +807,33 @@ async def put( proxy request successful """ async with self._client_wrapper.httpx_client.stream( - f"v1/connect/{jsonable_encoder(self._client_wrapper._project_id)}/proxy/{jsonable_encoder(url_64)}", - method="PUT", - params={ - "external_user_id": external_user_id, - "account_id": account_id, - }, - json=request, - headers={ - "content-type": "application/json", - }, - request_options=request_options, - omit=OMIT, + f"v1/connect/{jsonable_encoder(self._client_wrapper._project_id)}/proxy/{jsonable_encoder(url_64)}", + method="PUT", + params={ + "external_user_id": external_user_id, + "account_id": account_id, + }, + json=request, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, ) as _response: - async def _handle_response() -> AsyncHttpResponse[typing.Union[ProxyResponse, typing.AsyncIterator[bytes]]]: + async def _handle_response() -> AsyncHttpResponse[typing.Union[ + ProxyResponse, typing.AsyncIterator[bytes]]]: try: if 200 <= _response.status_code < 300: - content_type = _response.headers.get("content-type", "").lower() + content_type = _response.headers.get( + "content-type", "").lower() is_json = "application/json" in content_type or not content_type if is_json: await _response.aread() if not _response.text.strip(): - return AsyncHttpResponse(response=_response, data=None) + return AsyncHttpResponse(response=_response, + data=None) _data = typing.cast( ProxyResponse, parse_obj_as( @@ -733,20 +841,30 @@ async def _handle_response() -> AsyncHttpResponse[typing.Union[ProxyResponse, ty object_=_response.json(), ), ) - return AsyncHttpResponse(response=_response, data=_data) + return AsyncHttpResponse(response=_response, + data=_data) else: - _chunk_size = request_options.get("chunk_size", None) if request_options is not None else None - return AsyncHttpResponse(response=_response, data=_response.aiter_bytes(chunk_size=_chunk_size)) + _chunk_size = request_options.get( + "chunk_size", + None) if request_options is not None else None + return AsyncHttpResponse( + response=_response, + data=_response.aiter_bytes( + chunk_size=_chunk_size)) await _response.aread() - _error_content_type = _response.headers.get("content-type", "").lower() + _error_content_type = _response.headers.get( + "content-type", "").lower() if _response.status_code == 429: raise TooManyRequestsError( headers=dict(_response.headers), body=typing.cast( typing.Optional[typing.Any], parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore - object_=_response.json() if "application/json" in _error_content_type else _response.text, + type_=typing.Optional[ + typing.Any], # type: ignore + object_=_response.json() + if "application/json" + in _error_content_type else _response.text, ), ), ) @@ -755,10 +873,12 @@ async def _handle_response() -> AsyncHttpResponse[typing.Union[ProxyResponse, ty else: _response_body = _response.text except JSONDecodeError: - raise ApiError( - status_code=_response.status_code, headers=dict(_response.headers), body=_response.text - ) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_body) + raise ApiError(status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text) + raise ApiError(status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_body) yield await _handle_response() @@ -770,7 +890,8 @@ async def delete( external_user_id: str, account_id: str, request_options: typing.Optional[RequestOptions] = None, - ) -> typing.AsyncIterator[AsyncHttpResponse[typing.Union[ProxyResponse, typing.AsyncIterator[bytes]]]]: + ) -> typing.AsyncIterator[AsyncHttpResponse[typing.Union[ + ProxyResponse, typing.AsyncIterator[bytes]]]]: """ Forward an authenticated DELETE request to an external API using an external user's account credentials @@ -794,25 +915,28 @@ async def delete( proxy request successful """ async with self._client_wrapper.httpx_client.stream( - f"v1/connect/{jsonable_encoder(self._client_wrapper._project_id)}/proxy/{jsonable_encoder(url_64)}", - method="DELETE", - params={ - "external_user_id": external_user_id, - "account_id": account_id, - }, - request_options=request_options, + f"v1/connect/{jsonable_encoder(self._client_wrapper._project_id)}/proxy/{jsonable_encoder(url_64)}", + method="DELETE", + params={ + "external_user_id": external_user_id, + "account_id": account_id, + }, + request_options=request_options, ) as _response: - async def _handle_response() -> AsyncHttpResponse[typing.Union[ProxyResponse, typing.AsyncIterator[bytes]]]: + async def _handle_response() -> AsyncHttpResponse[typing.Union[ + ProxyResponse, typing.AsyncIterator[bytes]]]: try: if 200 <= _response.status_code < 300: - content_type = _response.headers.get("content-type", "").lower() + content_type = _response.headers.get( + "content-type", "").lower() is_json = "application/json" in content_type or not content_type if is_json: await _response.aread() if not _response.text.strip(): - return AsyncHttpResponse(response=_response, data=None) + return AsyncHttpResponse(response=_response, + data=None) _data = typing.cast( ProxyResponse, parse_obj_as( @@ -820,20 +944,30 @@ async def _handle_response() -> AsyncHttpResponse[typing.Union[ProxyResponse, ty object_=_response.json(), ), ) - return AsyncHttpResponse(response=_response, data=_data) + return AsyncHttpResponse(response=_response, + data=_data) else: - _chunk_size = request_options.get("chunk_size", None) if request_options is not None else None - return AsyncHttpResponse(response=_response, data=_response.aiter_bytes(chunk_size=_chunk_size)) + _chunk_size = request_options.get( + "chunk_size", + None) if request_options is not None else None + return AsyncHttpResponse( + response=_response, + data=_response.aiter_bytes( + chunk_size=_chunk_size)) await _response.aread() - _error_content_type = _response.headers.get("content-type", "").lower() + _error_content_type = _response.headers.get( + "content-type", "").lower() if _response.status_code == 429: raise TooManyRequestsError( headers=dict(_response.headers), body=typing.cast( typing.Optional[typing.Any], parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore - object_=_response.json() if "application/json" in _error_content_type else _response.text, + type_=typing.Optional[ + typing.Any], # type: ignore + object_=_response.json() + if "application/json" + in _error_content_type else _response.text, ), ), ) @@ -842,10 +976,12 @@ async def _handle_response() -> AsyncHttpResponse[typing.Union[ProxyResponse, ty else: _response_body = _response.text except JSONDecodeError: - raise ApiError( - status_code=_response.status_code, headers=dict(_response.headers), body=_response.text - ) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_body) + raise ApiError(status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text) + raise ApiError(status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_body) yield await _handle_response() @@ -858,7 +994,8 @@ async def patch( account_id: str, request: typing.Dict[str, typing.Optional[typing.Any]], request_options: typing.Optional[RequestOptions] = None, - ) -> typing.AsyncIterator[AsyncHttpResponse[typing.Union[ProxyResponse, typing.AsyncIterator[bytes]]]]: + ) -> typing.AsyncIterator[AsyncHttpResponse[typing.Union[ + ProxyResponse, typing.AsyncIterator[bytes]]]]: """ Forward an authenticated PATCH request to an external API using an external user's account credentials @@ -884,30 +1021,33 @@ async def patch( proxy request successful """ async with self._client_wrapper.httpx_client.stream( - f"v1/connect/{jsonable_encoder(self._client_wrapper._project_id)}/proxy/{jsonable_encoder(url_64)}", - method="PATCH", - params={ - "external_user_id": external_user_id, - "account_id": account_id, - }, - json=request, - headers={ - "content-type": "application/json", - }, - request_options=request_options, - omit=OMIT, + f"v1/connect/{jsonable_encoder(self._client_wrapper._project_id)}/proxy/{jsonable_encoder(url_64)}", + method="PATCH", + params={ + "external_user_id": external_user_id, + "account_id": account_id, + }, + json=request, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, ) as _response: - async def _handle_response() -> AsyncHttpResponse[typing.Union[ProxyResponse, typing.AsyncIterator[bytes]]]: + async def _handle_response() -> AsyncHttpResponse[typing.Union[ + ProxyResponse, typing.AsyncIterator[bytes]]]: try: if 200 <= _response.status_code < 300: - content_type = _response.headers.get("content-type", "").lower() + content_type = _response.headers.get( + "content-type", "").lower() is_json = "application/json" in content_type or not content_type if is_json: await _response.aread() if not _response.text.strip(): - return AsyncHttpResponse(response=_response, data=None) + return AsyncHttpResponse(response=_response, + data=None) _data = typing.cast( ProxyResponse, parse_obj_as( @@ -915,20 +1055,30 @@ async def _handle_response() -> AsyncHttpResponse[typing.Union[ProxyResponse, ty object_=_response.json(), ), ) - return AsyncHttpResponse(response=_response, data=_data) + return AsyncHttpResponse(response=_response, + data=_data) else: - _chunk_size = request_options.get("chunk_size", None) if request_options is not None else None - return AsyncHttpResponse(response=_response, data=_response.aiter_bytes(chunk_size=_chunk_size)) + _chunk_size = request_options.get( + "chunk_size", + None) if request_options is not None else None + return AsyncHttpResponse( + response=_response, + data=_response.aiter_bytes( + chunk_size=_chunk_size)) await _response.aread() - _error_content_type = _response.headers.get("content-type", "").lower() + _error_content_type = _response.headers.get( + "content-type", "").lower() if _response.status_code == 429: raise TooManyRequestsError( headers=dict(_response.headers), body=typing.cast( typing.Optional[typing.Any], parse_obj_as( - type_=typing.Optional[typing.Any], # type: ignore - object_=_response.json() if "application/json" in _error_content_type else _response.text, + type_=typing.Optional[ + typing.Any], # type: ignore + object_=_response.json() + if "application/json" + in _error_content_type else _response.text, ), ), ) @@ -937,9 +1087,11 @@ async def _handle_response() -> AsyncHttpResponse[typing.Union[ProxyResponse, ty else: _response_body = _response.text except JSONDecodeError: - raise ApiError( - status_code=_response.status_code, headers=dict(_response.headers), body=_response.text - ) - raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_body) + raise ApiError(status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text) + raise ApiError(status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_body) yield await _handle_response() From 1033d8a8f1818a46a62769a19576dfe0e7e17634 Mon Sep 17 00:00:00 2001 From: Jay Vercellone Date: Wed, 3 Dec 2025 12:45:34 -0300 Subject: [PATCH 12/13] Include query params in the encoded URL --- src/pipedream/proxy/client.py | 101 ++++++++++++++++++++++++---------- 1 file changed, 71 insertions(+), 30 deletions(-) diff --git a/src/pipedream/proxy/client.py b/src/pipedream/proxy/client.py index 7e293d3..0929c40 100644 --- a/src/pipedream/proxy/client.py +++ b/src/pipedream/proxy/client.py @@ -3,6 +3,7 @@ import base64 import typing from collections.abc import AsyncIterator, Iterator +from urllib.parse import parse_qs, urlencode, urlparse, urlunparse from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper from ..core.request_options import RequestOptions @@ -80,15 +81,19 @@ def get( params={"limit": 10}, ) """ + if params: + parsed = urlparse(url) + existing_params = parse_qs(parsed.query) + existing_params.update(params) + new_query = urlencode(existing_params, doseq=True) + url = urlunparse(parsed._replace(query=new_query)) url_64 = base64.urlsafe_b64encode(url.encode()).decode() downstream_headers = { f"x-pd-proxy-{header}": value for header, value in (headers or {}).items() } request_options = RequestOptions( - additional_headers=downstream_headers, - additional_query_parameters=params or {}, - ) + additional_headers=downstream_headers, ) ctx = self._raw_client.get( url_64, external_user_id=external_user_id, @@ -168,15 +173,19 @@ def post( body={"key": "value"}, ) """ + if params: + parsed = urlparse(url) + existing_params = parse_qs(parsed.query) + existing_params.update(params) + new_query = urlencode(existing_params, doseq=True) + url = urlunparse(parsed._replace(query=new_query)) url_64 = base64.urlsafe_b64encode(url.encode()).decode() downstream_headers = { f"x-pd-proxy-{header}": value for header, value in (headers or {}).items() } request_options = RequestOptions( - additional_headers=downstream_headers, - additional_query_parameters=params or {}, - ) + additional_headers=downstream_headers, ) ctx = self._raw_client.post( url_64, external_user_id=external_user_id, @@ -257,15 +266,19 @@ def put( body={"key": "value"}, ) """ + if params: + parsed = urlparse(url) + existing_params = parse_qs(parsed.query) + existing_params.update(params) + new_query = urlencode(existing_params, doseq=True) + url = urlunparse(parsed._replace(query=new_query)) url_64 = base64.urlsafe_b64encode(url.encode()).decode() downstream_headers = { f"x-pd-proxy-{header}": value for header, value in (headers or {}).items() } request_options = RequestOptions( - additional_headers=downstream_headers, - additional_query_parameters=params or {}, - ) + additional_headers=downstream_headers, ) ctx = self._raw_client.put( url_64, external_user_id=external_user_id, @@ -338,15 +351,19 @@ def delete( headers={"Extra-Downstream-Header": "some value"} ) """ + if params: + parsed = urlparse(url) + existing_params = parse_qs(parsed.query) + existing_params.update(params) + new_query = urlencode(existing_params, doseq=True) + url = urlunparse(parsed._replace(query=new_query)) url_64 = base64.urlsafe_b64encode(url.encode()).decode() downstream_headers = { f"x-pd-proxy-{header}": value for header, value in (headers or {}).items() } request_options = RequestOptions( - additional_headers=downstream_headers, - additional_query_parameters=params or {}, - ) + additional_headers=downstream_headers, ) ctx = self._raw_client.delete( url_64, external_user_id=external_user_id, @@ -426,15 +443,19 @@ def patch( body={"key": "value"}, ) """ + if params: + parsed = urlparse(url) + existing_params = parse_qs(parsed.query) + existing_params.update(params) + new_query = urlencode(existing_params, doseq=True) + url = urlunparse(parsed._replace(query=new_query)) url_64 = base64.urlsafe_b64encode(url.encode()).decode() downstream_headers = { f"x-pd-proxy-{header}": value for header, value in (headers or {}).items() } request_options = RequestOptions( - additional_headers=downstream_headers, - additional_query_parameters=params or {}, - ) + additional_headers=downstream_headers, ) ctx = self._raw_client.patch( url_64, external_user_id=external_user_id, @@ -534,15 +555,19 @@ async def main() -> None: asyncio.run(main()) """ + if params: + parsed = urlparse(url) + existing_params = parse_qs(parsed.query) + existing_params.update(params) + new_query = urlencode(existing_params, doseq=True) + url = urlunparse(parsed._replace(query=new_query)) url_64 = base64.urlsafe_b64encode(url.encode()).decode() downstream_headers = { f"x-pd-proxy-{header}": value for header, value in (headers or {}).items() } request_options = RequestOptions( - additional_headers=downstream_headers, - additional_query_parameters=params or {}, - ) + additional_headers=downstream_headers, ) ctx = self._raw_client.get( url_64, external_user_id=external_user_id, @@ -630,15 +655,19 @@ async def main() -> None: asyncio.run(main()) """ + if params: + parsed = urlparse(url) + existing_params = parse_qs(parsed.query) + existing_params.update(params) + new_query = urlencode(existing_params, doseq=True) + url = urlunparse(parsed._replace(query=new_query)) url_64 = base64.urlsafe_b64encode(url.encode()).decode() downstream_headers = { f"x-pd-proxy-{header}": value for header, value in (headers or {}).items() } request_options = RequestOptions( - additional_headers=downstream_headers, - additional_query_parameters=params or {}, - ) + additional_headers=downstream_headers, ) ctx = self._raw_client.post( url_64, external_user_id=external_user_id, @@ -727,15 +756,19 @@ async def main() -> None: asyncio.run(main()) """ + if params: + parsed = urlparse(url) + existing_params = parse_qs(parsed.query) + existing_params.update(params) + new_query = urlencode(existing_params, doseq=True) + url = urlunparse(parsed._replace(query=new_query)) url_64 = base64.urlsafe_b64encode(url.encode()).decode() downstream_headers = { f"x-pd-proxy-{header}": value for header, value in (headers or {}).items() } request_options = RequestOptions( - additional_headers=downstream_headers, - additional_query_parameters=params or {}, - ) + additional_headers=downstream_headers, ) ctx = self._raw_client.put( url_64, external_user_id=external_user_id, @@ -816,15 +849,19 @@ async def main() -> None: asyncio.run(main()) """ + if params: + parsed = urlparse(url) + existing_params = parse_qs(parsed.query) + existing_params.update(params) + new_query = urlencode(existing_params, doseq=True) + url = urlunparse(parsed._replace(query=new_query)) url_64 = base64.urlsafe_b64encode(url.encode()).decode() downstream_headers = { f"x-pd-proxy-{header}": value for header, value in (headers or {}).items() } request_options = RequestOptions( - additional_headers=downstream_headers, - additional_query_parameters=params or {}, - ) + additional_headers=downstream_headers, ) ctx = self._raw_client.delete( url_64, external_user_id=external_user_id, @@ -912,15 +949,19 @@ async def main() -> None: asyncio.run(main()) """ + if params: + parsed = urlparse(url) + existing_params = parse_qs(parsed.query) + existing_params.update(params) + new_query = urlencode(existing_params, doseq=True) + url = urlunparse(parsed._replace(query=new_query)) url_64 = base64.urlsafe_b64encode(url.encode()).decode() downstream_headers = { f"x-pd-proxy-{header}": value for header, value in (headers or {}).items() } request_options = RequestOptions( - additional_headers=downstream_headers, - additional_query_parameters=params or {}, - ) + additional_headers=downstream_headers, ) ctx = self._raw_client.patch( url_64, external_user_id=external_user_id, From 83f8a07821848c8a422c6b4ee0a592111ea60958 Mon Sep 17 00:00:00 2001 From: Jay Vercellone Date: Wed, 3 Dec 2025 13:07:44 -0300 Subject: [PATCH 13/13] Copilot feedback --- src/pipedream/proxy/client.py | 71 ++++++++++------------------------- 1 file changed, 20 insertions(+), 51 deletions(-) diff --git a/src/pipedream/proxy/client.py b/src/pipedream/proxy/client.py index 0929c40..afef844 100644 --- a/src/pipedream/proxy/client.py +++ b/src/pipedream/proxy/client.py @@ -14,6 +14,15 @@ OMIT = typing.cast(typing.Any, ...) +def _add_params_to_url(url: str, params: typing.Dict[str, typing.Any]) -> str: + parsed = urlparse(url) + existing_params = parse_qs(parsed.query) + for key, value in params.items(): + existing_params[key] = value if isinstance(value, list) else [value] + new_query = urlencode(existing_params, doseq=True) + return urlunparse(parsed._replace(query=new_query)) + + class ProxyClient: def __init__(self, *, client_wrapper: SyncClientWrapper): @@ -77,16 +86,12 @@ def get( url="https://example.com/api/endpoint", external_user_id="external_user_id", account_id="account_id", - headers={"Extra-Downstream-Header": "some value"} + headers={"Extra-Downstream-Header": "some value"}, params={"limit": 10}, ) """ if params: - parsed = urlparse(url) - existing_params = parse_qs(parsed.query) - existing_params.update(params) - new_query = urlencode(existing_params, doseq=True) - url = urlunparse(parsed._replace(query=new_query)) + url = _add_params_to_url(url, params) url_64 = base64.urlsafe_b64encode(url.encode()).decode() downstream_headers = { f"x-pd-proxy-{header}": value @@ -174,11 +179,7 @@ def post( ) """ if params: - parsed = urlparse(url) - existing_params = parse_qs(parsed.query) - existing_params.update(params) - new_query = urlencode(existing_params, doseq=True) - url = urlunparse(parsed._replace(query=new_query)) + url = _add_params_to_url(url, params) url_64 = base64.urlsafe_b64encode(url.encode()).decode() downstream_headers = { f"x-pd-proxy-{header}": value @@ -267,11 +268,7 @@ def put( ) """ if params: - parsed = urlparse(url) - existing_params = parse_qs(parsed.query) - existing_params.update(params) - new_query = urlencode(existing_params, doseq=True) - url = urlunparse(parsed._replace(query=new_query)) + url = _add_params_to_url(url, params) url_64 = base64.urlsafe_b64encode(url.encode()).decode() downstream_headers = { f"x-pd-proxy-{header}": value @@ -352,11 +349,7 @@ def delete( ) """ if params: - parsed = urlparse(url) - existing_params = parse_qs(parsed.query) - existing_params.update(params) - new_query = urlencode(existing_params, doseq=True) - url = urlunparse(parsed._replace(query=new_query)) + url = _add_params_to_url(url, params) url_64 = base64.urlsafe_b64encode(url.encode()).decode() downstream_headers = { f"x-pd-proxy-{header}": value @@ -444,11 +437,7 @@ def patch( ) """ if params: - parsed = urlparse(url) - existing_params = parse_qs(parsed.query) - existing_params.update(params) - new_query = urlencode(existing_params, doseq=True) - url = urlunparse(parsed._replace(query=new_query)) + url = _add_params_to_url(url, params) url_64 = base64.urlsafe_b64encode(url.encode()).decode() downstream_headers = { f"x-pd-proxy-{header}": value @@ -556,11 +545,7 @@ async def main() -> None: asyncio.run(main()) """ if params: - parsed = urlparse(url) - existing_params = parse_qs(parsed.query) - existing_params.update(params) - new_query = urlencode(existing_params, doseq=True) - url = urlunparse(parsed._replace(query=new_query)) + url = _add_params_to_url(url, params) url_64 = base64.urlsafe_b64encode(url.encode()).decode() downstream_headers = { f"x-pd-proxy-{header}": value @@ -656,11 +641,7 @@ async def main() -> None: asyncio.run(main()) """ if params: - parsed = urlparse(url) - existing_params = parse_qs(parsed.query) - existing_params.update(params) - new_query = urlencode(existing_params, doseq=True) - url = urlunparse(parsed._replace(query=new_query)) + url = _add_params_to_url(url, params) url_64 = base64.urlsafe_b64encode(url.encode()).decode() downstream_headers = { f"x-pd-proxy-{header}": value @@ -757,11 +738,7 @@ async def main() -> None: asyncio.run(main()) """ if params: - parsed = urlparse(url) - existing_params = parse_qs(parsed.query) - existing_params.update(params) - new_query = urlencode(existing_params, doseq=True) - url = urlunparse(parsed._replace(query=new_query)) + url = _add_params_to_url(url, params) url_64 = base64.urlsafe_b64encode(url.encode()).decode() downstream_headers = { f"x-pd-proxy-{header}": value @@ -850,11 +827,7 @@ async def main() -> None: asyncio.run(main()) """ if params: - parsed = urlparse(url) - existing_params = parse_qs(parsed.query) - existing_params.update(params) - new_query = urlencode(existing_params, doseq=True) - url = urlunparse(parsed._replace(query=new_query)) + url = _add_params_to_url(url, params) url_64 = base64.urlsafe_b64encode(url.encode()).decode() downstream_headers = { f"x-pd-proxy-{header}": value @@ -950,11 +923,7 @@ async def main() -> None: asyncio.run(main()) """ if params: - parsed = urlparse(url) - existing_params = parse_qs(parsed.query) - existing_params.update(params) - new_query = urlencode(existing_params, doseq=True) - url = urlunparse(parsed._replace(query=new_query)) + url = _add_params_to_url(url, params) url_64 = base64.urlsafe_b64encode(url.encode()).decode() downstream_headers = { f"x-pd-proxy-{header}": value