Skip to content

Commit 6226293

Browse files
devin-ai-integration[bot]blank@buildwithfern.com
andcommitted
Add AgoraPool and AsyncAgoraPool wrapper clients
- Create AgoraPool that wraps Agora with domain pool - Create AsyncAgoraPool that wraps AsyncAgora with domain pool - Automatically use pool's current URL as base URL - Expose pool, next_region(), select_best_domain(), get_current_url() methods - Export AgoraPool and AsyncAgoraPool from main package Co-Authored-By: blank@buildwithfern.com <blank@buildwithfern.com>
1 parent b86c1c0 commit 6226293

2 files changed

Lines changed: 283 additions & 1 deletion

File tree

src/agoraio/__init__.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,14 @@
99
from . import agents, core, phone_numbers, telephony
1010
from .client import Agora, AsyncAgora
1111
from .core.domain import Area, Pool, create_pool
12+
from .pool_client import AgoraPool, AsyncAgoraPool
1213
from .version import __version__
1314
_dynamic_imports: typing.Dict[str, str] = {
1415
"Agora": ".client",
16+
"AgoraPool": ".pool_client",
1517
"Area": ".core.domain",
1618
"AsyncAgora": ".client",
19+
"AsyncAgoraPool": ".pool_client",
1720
"Pool": ".core.domain",
1821
"__version__": ".version",
1922
"agents": ".agents",
@@ -45,4 +48,4 @@ def __dir__():
4548
return sorted(lazy_attrs)
4649

4750

48-
__all__ = ["Agora", "Area", "AsyncAgora", "Pool", "__version__", "agents", "core", "create_pool", "phone_numbers", "telephony"]
51+
__all__ = ["Agora", "AgoraPool", "Area", "AsyncAgora", "AsyncAgoraPool", "Pool", "__version__", "agents", "core", "create_pool", "phone_numbers", "telephony"]

src/agoraio/pool_client.py

Lines changed: 279 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,279 @@
1+
# This file was auto-generated by Fern from our API Definition.
2+
3+
from __future__ import annotations
4+
5+
import typing
6+
7+
import httpx
8+
9+
from .core.client_wrapper import AsyncClientWrapper, SyncClientWrapper
10+
from .core.domain import Area, Pool
11+
12+
if typing.TYPE_CHECKING:
13+
from .agents.client import AgentsClient, AsyncAgentsClient
14+
from .phone_numbers.client import AsyncPhoneNumbersClient, PhoneNumbersClient
15+
from .telephony.client import AsyncTelephonyClient, TelephonyClient
16+
17+
18+
class AgoraPool:
19+
"""
20+
AgoraPool is a wrapper around Agora that uses a domain pool
21+
for regional URL cycling and automatic domain selection.
22+
23+
This client automatically:
24+
- Selects the best domain based on DNS resolution
25+
- Cycles through region prefixes on request failures
26+
- Supports US, EU, AP, and CN areas
27+
28+
Parameters
29+
----------
30+
area : Area
31+
The area to use for regional URL selection.
32+
33+
username : typing.Union[str, typing.Callable[[], str]]
34+
password : typing.Union[str, typing.Callable[[], str]]
35+
headers : typing.Optional[typing.Dict[str, str]]
36+
Additional headers to send with every request.
37+
38+
timeout : typing.Optional[float]
39+
The timeout to be used, in seconds, for requests. By default the timeout is 60 seconds, unless a custom httpx client is used, in which case this default is not enforced.
40+
41+
follow_redirects : typing.Optional[bool]
42+
Whether the default httpx client follows redirects or not, this is irrelevant if a custom httpx client is passed in.
43+
44+
httpx_client : typing.Optional[httpx.Client]
45+
The httpx client to use for making requests, a preconfigured client is used by default, however this is useful should you want to pass in any custom httpx configuration.
46+
47+
Examples
48+
--------
49+
from agoraio import AgoraPool, Area
50+
51+
client = AgoraPool(
52+
area=Area.US,
53+
username="YOUR_USERNAME",
54+
password="YOUR_PASSWORD",
55+
)
56+
"""
57+
58+
def __init__(
59+
self,
60+
*,
61+
area: Area,
62+
username: typing.Union[str, typing.Callable[[], str]],
63+
password: typing.Union[str, typing.Callable[[], str]],
64+
headers: typing.Optional[typing.Dict[str, str]] = None,
65+
timeout: typing.Optional[float] = None,
66+
follow_redirects: typing.Optional[bool] = True,
67+
httpx_client: typing.Optional[httpx.Client] = None,
68+
):
69+
self._pool = Pool(area)
70+
_defaulted_timeout = (
71+
timeout if timeout is not None else 60 if httpx_client is None else httpx_client.timeout.read
72+
)
73+
self._client_wrapper = SyncClientWrapper(
74+
base_url=self._pool.get_current_url(),
75+
username=username,
76+
password=password,
77+
headers=headers,
78+
httpx_client=httpx_client
79+
if httpx_client is not None
80+
else httpx.Client(timeout=_defaulted_timeout, follow_redirects=follow_redirects)
81+
if follow_redirects is not None
82+
else httpx.Client(timeout=_defaulted_timeout),
83+
timeout=_defaulted_timeout,
84+
)
85+
self._agents: typing.Optional[AgentsClient] = None
86+
self._telephony: typing.Optional[TelephonyClient] = None
87+
self._phone_numbers: typing.Optional[PhoneNumbersClient] = None
88+
89+
@property
90+
def pool(self) -> Pool:
91+
"""
92+
Get the underlying domain pool for advanced usage.
93+
You can use this to manually cycle regions or select the best domain.
94+
"""
95+
return self._pool
96+
97+
def next_region(self) -> None:
98+
"""
99+
Cycle to the next region prefix in the pool.
100+
Call this when a request fails to try a different region.
101+
"""
102+
self._pool.next_region()
103+
self._update_base_url()
104+
105+
def select_best_domain(self) -> None:
106+
"""
107+
Select the best domain using DNS resolution.
108+
This is automatically called periodically, but you can call it manually.
109+
"""
110+
self._pool.select_best_domain()
111+
self._update_base_url()
112+
113+
def get_current_url(self) -> str:
114+
"""
115+
Get the current URL being used for requests.
116+
"""
117+
return self._pool.get_current_url()
118+
119+
def _update_base_url(self) -> None:
120+
"""
121+
Update the base URL in the client wrapper to match the pool's current URL.
122+
"""
123+
self._client_wrapper._base_url = self._pool.get_current_url()
124+
125+
@property
126+
def agents(self):
127+
if self._agents is None:
128+
from .agents.client import AgentsClient # noqa: E402
129+
130+
self._agents = AgentsClient(client_wrapper=self._client_wrapper)
131+
return self._agents
132+
133+
@property
134+
def telephony(self):
135+
if self._telephony is None:
136+
from .telephony.client import TelephonyClient # noqa: E402
137+
138+
self._telephony = TelephonyClient(client_wrapper=self._client_wrapper)
139+
return self._telephony
140+
141+
@property
142+
def phone_numbers(self):
143+
if self._phone_numbers is None:
144+
from .phone_numbers.client import PhoneNumbersClient # noqa: E402
145+
146+
self._phone_numbers = PhoneNumbersClient(client_wrapper=self._client_wrapper)
147+
return self._phone_numbers
148+
149+
150+
class AsyncAgoraPool:
151+
"""
152+
AsyncAgoraPool is a wrapper around AsyncAgora that uses a domain pool
153+
for regional URL cycling and automatic domain selection.
154+
155+
This client automatically:
156+
- Selects the best domain based on DNS resolution
157+
- Cycles through region prefixes on request failures
158+
- Supports US, EU, AP, and CN areas
159+
160+
Parameters
161+
----------
162+
area : Area
163+
The area to use for regional URL selection.
164+
165+
username : typing.Union[str, typing.Callable[[], str]]
166+
password : typing.Union[str, typing.Callable[[], str]]
167+
headers : typing.Optional[typing.Dict[str, str]]
168+
Additional headers to send with every request.
169+
170+
timeout : typing.Optional[float]
171+
The timeout to be used, in seconds, for requests. By default the timeout is 60 seconds, unless a custom httpx client is used, in which case this default is not enforced.
172+
173+
follow_redirects : typing.Optional[bool]
174+
Whether the default httpx client follows redirects or not, this is irrelevant if a custom httpx client is passed in.
175+
176+
httpx_client : typing.Optional[httpx.AsyncClient]
177+
The httpx client to use for making requests, a preconfigured client is used by default, however this is useful should you want to pass in any custom httpx configuration.
178+
179+
Examples
180+
--------
181+
from agoraio import AsyncAgoraPool, Area
182+
183+
client = AsyncAgoraPool(
184+
area=Area.US,
185+
username="YOUR_USERNAME",
186+
password="YOUR_PASSWORD",
187+
)
188+
"""
189+
190+
def __init__(
191+
self,
192+
*,
193+
area: Area,
194+
username: typing.Union[str, typing.Callable[[], str]],
195+
password: typing.Union[str, typing.Callable[[], str]],
196+
headers: typing.Optional[typing.Dict[str, str]] = None,
197+
timeout: typing.Optional[float] = None,
198+
follow_redirects: typing.Optional[bool] = True,
199+
httpx_client: typing.Optional[httpx.AsyncClient] = None,
200+
):
201+
self._pool = Pool(area)
202+
_defaulted_timeout = (
203+
timeout if timeout is not None else 60 if httpx_client is None else httpx_client.timeout.read
204+
)
205+
self._client_wrapper = AsyncClientWrapper(
206+
base_url=self._pool.get_current_url(),
207+
username=username,
208+
password=password,
209+
headers=headers,
210+
httpx_client=httpx_client
211+
if httpx_client is not None
212+
else httpx.AsyncClient(timeout=_defaulted_timeout, follow_redirects=follow_redirects)
213+
if follow_redirects is not None
214+
else httpx.AsyncClient(timeout=_defaulted_timeout),
215+
timeout=_defaulted_timeout,
216+
)
217+
self._agents: typing.Optional[AsyncAgentsClient] = None
218+
self._telephony: typing.Optional[AsyncTelephonyClient] = None
219+
self._phone_numbers: typing.Optional[AsyncPhoneNumbersClient] = None
220+
221+
@property
222+
def pool(self) -> Pool:
223+
"""
224+
Get the underlying domain pool for advanced usage.
225+
You can use this to manually cycle regions or select the best domain.
226+
"""
227+
return self._pool
228+
229+
def next_region(self) -> None:
230+
"""
231+
Cycle to the next region prefix in the pool.
232+
Call this when a request fails to try a different region.
233+
"""
234+
self._pool.next_region()
235+
self._update_base_url()
236+
237+
async def select_best_domain(self) -> None:
238+
"""
239+
Select the best domain using DNS resolution (async).
240+
This is automatically called periodically, but you can call it manually.
241+
"""
242+
await self._pool.select_best_domain_async()
243+
self._update_base_url()
244+
245+
def get_current_url(self) -> str:
246+
"""
247+
Get the current URL being used for requests.
248+
"""
249+
return self._pool.get_current_url()
250+
251+
def _update_base_url(self) -> None:
252+
"""
253+
Update the base URL in the client wrapper to match the pool's current URL.
254+
"""
255+
self._client_wrapper._base_url = self._pool.get_current_url()
256+
257+
@property
258+
def agents(self):
259+
if self._agents is None:
260+
from .agents.client import AsyncAgentsClient # noqa: E402
261+
262+
self._agents = AsyncAgentsClient(client_wrapper=self._client_wrapper)
263+
return self._agents
264+
265+
@property
266+
def telephony(self):
267+
if self._telephony is None:
268+
from .telephony.client import AsyncTelephonyClient # noqa: E402
269+
270+
self._telephony = AsyncTelephonyClient(client_wrapper=self._client_wrapper)
271+
return self._telephony
272+
273+
@property
274+
def phone_numbers(self):
275+
if self._phone_numbers is None:
276+
from .phone_numbers.client import AsyncPhoneNumbersClient # noqa: E402
277+
278+
self._phone_numbers = AsyncPhoneNumbersClient(client_wrapper=self._client_wrapper)
279+
return self._phone_numbers

0 commit comments

Comments
 (0)