Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/plivo_agent/_http.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ def __init__(
def auth_id(self) -> str:
return self._auth_id

@property
def agents_base_url(self) -> str:
return f"{self._base_url}/v1/Account/{self._auth_id}"

async def request(
self,
method: str,
Expand Down
75 changes: 40 additions & 35 deletions src/plivo_agent/agent/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,59 +12,61 @@


class AgentResource:
"""Agent CRUD -- POST/GET/PATCH/DELETE /v1/Agent
"""Agent CRUD -- POST/GET/PATCH/DELETE /Agent

Agent IDs are UUIDs (e.g. "550e8400-e29b-41d4-a716-446655440000").
Creating an agent requires ``agent_name``.
"""

def __init__(self, http: HttpTransport) -> None:
def __init__(self, http: HttpTransport, prefix: str) -> None:
self._http = http
self._prefix = prefix

async def create(self, **kwargs: Any) -> dict:
"""POST /v1/Agent
"""POST /Agent

Required fields: ``agent_name``, ``websocket_url``.
Returns the created agent with ``agent_uuid`` as the identifier.
"""
return await self._http.request("POST", "/v1/Agent", json=kwargs)
return await self._http.request("POST", f"{self._prefix}/Agent", json=kwargs)

async def get(self, agent_uuid: str) -> dict:
"""GET /v1/Agent/{agent_uuid}"""
return await self._http.request("GET", f"/v1/Agent/{agent_uuid}")
"""GET /Agent/{agent_uuid}"""
return await self._http.request("GET", f"{self._prefix}/Agent/{agent_uuid}")

async def list(self, **params: Any) -> dict:
"""GET /v1/Agent -- paginated list.
"""GET /Agent -- paginated list.

Optional query params: page, per_page, sort_by, sort_order,
agent_mode, participant_mode.

Returns ``{"data": [...], "meta": {"page", "per_page", "total", "total_pages"}}``.
"""
return await self._http.request("GET", "/v1/Agent", params=params)
return await self._http.request("GET", f"{self._prefix}/Agent", params=params)

async def update(self, agent_uuid: str, **kwargs: Any) -> dict:
"""PATCH /v1/Agent/{agent_uuid}"""
"""PATCH /Agent/{agent_uuid}"""
return await self._http.request(
"PATCH", f"/v1/Agent/{agent_uuid}", json=kwargs
"PATCH", f"{self._prefix}/Agent/{agent_uuid}", json=kwargs
)

async def delete(self, agent_uuid: str) -> None:
"""DELETE /v1/Agent/{agent_uuid}"""
await self._http.request("DELETE", f"/v1/Agent/{agent_uuid}")
"""DELETE /Agent/{agent_uuid}"""
await self._http.request("DELETE", f"{self._prefix}/Agent/{agent_uuid}")


class CallResource:
"""Call management -- connect, initiate, dial."""

def __init__(self, http: HttpTransport) -> None:
def __init__(self, http: HttpTransport, prefix: str) -> None:
self._http = http
self._prefix = prefix

async def connect(self, call_uuid: str, agent_uuid: str) -> dict:
"""POST /v1/AgentCall/{call_uuid}/connect -- connect an active call to an agent."""
"""POST /AgentCall/{call_uuid}/connect -- connect an active call to an agent."""
return await self._http.request(
"POST",
f"/v1/AgentCall/{call_uuid}/connect",
f"{self._prefix}/AgentCall/{call_uuid}/connect",
json={"agent_id": agent_uuid},
)

Expand All @@ -77,7 +79,7 @@ async def initiate(
voicemail_detect: bool = False,
**kwargs: Any,
) -> dict:
"""POST /v1/AgentCall -- initiate an outbound call.
"""POST /AgentCall -- initiate an outbound call.

Args:
agent_uuid: Agent UUID to handle the call.
Expand All @@ -93,15 +95,15 @@ async def initiate(
if voicemail_detect:
body["voicemail_detect"] = True
body.update(kwargs)
return await self._http.request("POST", "/v1/AgentCall", json=body)
return await self._http.request("POST", f"{self._prefix}/AgentCall", json=body)

async def dial(
self,
call_uuid: str,
targets: list[dict],
**kwargs: Any,
) -> dict:
"""POST /v1/AgentCall/{call_uuid}/dial -- dial out to one or more targets.
"""POST /AgentCall/{call_uuid}/dial -- dial out to one or more targets.

Args:
call_uuid: Active call UUID.
Expand All @@ -110,7 +112,7 @@ async def dial(
"""
body: dict[str, Any] = {"targets": targets, **kwargs}
return await self._http.request(
"POST", f"/v1/AgentCall/{call_uuid}/dial", json=body
"POST", f"{self._prefix}/AgentCall/{call_uuid}/dial", json=body
)


Expand All @@ -120,49 +122,51 @@ class NumberResource:
Numbers are in E.164 format (e.g. "+14155551234").
"""

def __init__(self, http: HttpTransport) -> None:
def __init__(self, http: HttpTransport, prefix: str) -> None:
self._http = http
self._prefix = prefix

async def assign(self, agent_uuid: str, number: str) -> dict:
"""POST /v1/Agent/{agent_uuid}/Number -- assign a number to an agent."""
"""POST /Agent/{agent_uuid}/Number -- assign a number to an agent."""
return await self._http.request(
"POST",
f"/v1/Agent/{agent_uuid}/Number",
f"{self._prefix}/Agent/{agent_uuid}/Number",
json={"number": number},
)

async def list(self, agent_uuid: str) -> dict:
"""GET /v1/Agent/{agent_uuid}/Number -- list numbers for an agent."""
"""GET /Agent/{agent_uuid}/Number -- list numbers for an agent."""
return await self._http.request(
"GET", f"/v1/Agent/{agent_uuid}/Number"
"GET", f"{self._prefix}/Agent/{agent_uuid}/Number"
)

async def unassign(self, agent_uuid: str, number: str) -> None:
"""DELETE /v1/Agent/{agent_uuid}/Number/{number} -- unassign a number."""
"""DELETE /Agent/{agent_uuid}/Number/{number} -- unassign a number."""
await self._http.request(
"DELETE", f"/v1/Agent/{agent_uuid}/Number/{number}"
"DELETE", f"{self._prefix}/Agent/{agent_uuid}/Number/{number}"
)


class SessionResource:
"""Session history -- list and get agent sessions."""

def __init__(self, http: HttpTransport) -> None:
def __init__(self, http: HttpTransport, prefix: str) -> None:
self._http = http
self._prefix = prefix

async def list(self, agent_uuid: str, **params: Any) -> dict:
"""GET /v1/Agent/{agent_uuid}/Session -- list sessions.
"""GET /Agent/{agent_uuid}/Session -- list sessions.

Optional query params: page, per_page, sort_by, sort_order, agent_mode.
"""
return await self._http.request(
"GET", f"/v1/Agent/{agent_uuid}/Session", params=params
"GET", f"{self._prefix}/Agent/{agent_uuid}/Session", params=params
)

async def get(self, agent_uuid: str, session_id: str) -> dict:
"""GET /v1/Agent/{agent_uuid}/Session/{session_id} -- get session details."""
"""GET /Agent/{agent_uuid}/Session/{session_id} -- get session details."""
return await self._http.request(
"GET", f"/v1/Agent/{agent_uuid}/Session/{session_id}"
"GET", f"{self._prefix}/Agent/{agent_uuid}/Session/{session_id}"
)


Expand All @@ -178,7 +182,8 @@ class AgentClient:

def __init__(self, http: HttpTransport) -> None:
self._http = http
self.agents = AgentResource(http)
self.calls = CallResource(http)
self.numbers = NumberResource(http)
self.sessions = SessionResource(http)
prefix = f"/v1/Account/{http.auth_id}"
self.agents = AgentResource(http, prefix)
self.calls = CallResource(http, prefix)
self.numbers = NumberResource(http, prefix)
self.sessions = SessionResource(http, prefix)
44 changes: 22 additions & 22 deletions tests/test_agent/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@


async def test_create_agent(mock_api, http_transport):
"""POST /v1/Agent creates an agent."""
mock_api.post("/v1/Agent").mock(
"""POST /v1/Account/TESTAUTH123/Agent creates an agent."""
mock_api.post("/v1/Account/TESTAUTH123/Agent").mock(
return_value=httpx.Response(
200,
json={"agent_uuid": AGENT_UUID, "agent_name": "My Agent"},
Expand All @@ -25,8 +25,8 @@ async def test_create_agent(mock_api, http_transport):


async def test_get_agent(mock_api, http_transport):
"""GET /v1/Agent/{uuid} retrieves an agent."""
mock_api.get(f"/v1/Agent/{AGENT_UUID}").mock(
"""GET /v1/Account/TESTAUTH123/Agent/{uuid} retrieves an agent."""
mock_api.get(f"/v1/Account/TESTAUTH123/Agent/{AGENT_UUID}").mock(
return_value=httpx.Response(
200,
json={"agent_uuid": AGENT_UUID, "agent_name": "My Agent"},
Expand All @@ -38,8 +38,8 @@ async def test_get_agent(mock_api, http_transport):


async def test_list_agents(mock_api, http_transport):
"""GET /v1/Agent lists agents with query params."""
mock_api.get("/v1/Agent").mock(
"""GET /v1/Account/TESTAUTH123/Agent lists agents with query params."""
mock_api.get("/v1/Account/TESTAUTH123/Agent").mock(
return_value=httpx.Response(
200,
json={"data": [{"agent_uuid": AGENT_UUID}], "meta": {"total": 1}},
Expand All @@ -52,8 +52,8 @@ async def test_list_agents(mock_api, http_transport):


async def test_update_agent(mock_api, http_transport):
"""PATCH /v1/Agent/{uuid} updates an agent."""
mock_api.patch(f"/v1/Agent/{AGENT_UUID}").mock(
"""PATCH /v1/Account/TESTAUTH123/Agent/{uuid} updates an agent."""
mock_api.patch(f"/v1/Account/TESTAUTH123/Agent/{AGENT_UUID}").mock(
return_value=httpx.Response(
200,
json={"agent_uuid": AGENT_UUID, "agent_name": "Updated Agent"},
Expand All @@ -65,8 +65,8 @@ async def test_update_agent(mock_api, http_transport):


async def test_delete_agent(mock_api, http_transport):
"""DELETE /v1/Agent/{uuid} deletes an agent (204)."""
mock_api.delete(f"/v1/Agent/{AGENT_UUID}").mock(
"""DELETE /v1/Account/TESTAUTH123/Agent/{uuid} deletes an agent (204)."""
mock_api.delete(f"/v1/Account/TESTAUTH123/Agent/{AGENT_UUID}").mock(
return_value=httpx.Response(204)
)
client = AgentClient(http_transport)
Expand All @@ -75,8 +75,8 @@ async def test_delete_agent(mock_api, http_transport):


async def test_call_initiate(mock_api, http_transport):
"""POST /v1/AgentCall initiates an outbound call."""
mock_api.post("/v1/AgentCall").mock(
"""POST /v1/Account/TESTAUTH123/AgentCall initiates an outbound call."""
mock_api.post("/v1/Account/TESTAUTH123/AgentCall").mock(
return_value=httpx.Response(
200,
json={"call_uuid": CALL_UUID, "status": "initiated"},
Expand All @@ -93,8 +93,8 @@ async def test_call_initiate(mock_api, http_transport):


async def test_call_connect(mock_api, http_transport):
"""POST /v1/AgentCall/{uuid}/connect connects a call to an agent."""
mock_api.post(f"/v1/AgentCall/{CALL_UUID}/connect").mock(
"""POST /v1/Account/TESTAUTH123/AgentCall/{uuid}/connect connects a call to an agent."""
mock_api.post(f"/v1/Account/TESTAUTH123/AgentCall/{CALL_UUID}/connect").mock(
return_value=httpx.Response(
200,
json={"status": "connected"},
Expand All @@ -106,8 +106,8 @@ async def test_call_connect(mock_api, http_transport):


async def test_number_assign(mock_api, http_transport):
"""POST /v1/Agent/{uuid}/Number assigns a number to an agent."""
mock_api.post(f"/v1/Agent/{AGENT_UUID}/Number").mock(
"""POST /v1/Account/TESTAUTH123/Agent/{uuid}/Number assigns a number to an agent."""
mock_api.post(f"/v1/Account/TESTAUTH123/Agent/{AGENT_UUID}/Number").mock(
return_value=httpx.Response(
200,
json={"status": "assigned", "number": "+14155551234"},
Expand All @@ -120,9 +120,9 @@ async def test_number_assign(mock_api, http_transport):


async def test_number_unassign(mock_api, http_transport):
"""DELETE /v1/Agent/{uuid}/Number/{num} unassigns a number."""
"""DELETE /v1/Account/TESTAUTH123/Agent/{uuid}/Number/{num} unassigns a number."""
number = "+14155551234"
mock_api.delete(f"/v1/Agent/{AGENT_UUID}/Number/{number}").mock(
mock_api.delete(f"/v1/Account/TESTAUTH123/Agent/{AGENT_UUID}/Number/{number}").mock(
return_value=httpx.Response(204)
)
client = AgentClient(http_transport)
Expand All @@ -134,8 +134,8 @@ async def test_number_unassign(mock_api, http_transport):


async def test_session_list(mock_api, http_transport):
"""GET /v1/Agent/{uuid}/Session lists sessions."""
mock_api.get(f"/v1/Agent/{AGENT_UUID}/Session").mock(
"""GET /v1/Account/TESTAUTH123/Agent/{uuid}/Session lists sessions."""
mock_api.get(f"/v1/Account/TESTAUTH123/Agent/{AGENT_UUID}/Session").mock(
return_value=httpx.Response(
200,
json={
Expand All @@ -153,8 +153,8 @@ async def test_session_list(mock_api, http_transport):


async def test_session_get(mock_api, http_transport):
"""GET /v1/Agent/{uuid}/Session/{session_id} gets session details."""
mock_api.get(f"/v1/Agent/{AGENT_UUID}/Session/{SESSION_ID}").mock(
"""GET /v1/Account/TESTAUTH123/Agent/{uuid}/Session/{session_id} gets session details."""
mock_api.get(f"/v1/Account/TESTAUTH123/Agent/{AGENT_UUID}/Session/{SESSION_ID}").mock(
return_value=httpx.Response(
200,
json={
Expand Down