Skip to content

Commit 72f6299

Browse files
authored
Merge pull request #131 from neuphonic/joh/refactor-2.0.0
Refactor of Pyneuphonic
2 parents a9322f4 + cc4a830 commit 72f6299

File tree

13 files changed

+236
-253
lines changed

13 files changed

+236
-253
lines changed

pyneuphonic/_agents.py

Lines changed: 12 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
from typing import Optional
2-
import httpx
32

43
from pyneuphonic._endpoint import Endpoint
54
from pyneuphonic._websocket import AsyncAgentWebsocketClient
6-
from pyneuphonic.models import APIResponse, AgentObject # noqa: F401
5+
from pyneuphonic.models import APIResponse # noqa: F401
76

87

98
class Agents(Endpoint):
9+
"""Manage and interact with agents."""
10+
1011
def list(
1112
self,
1213
) -> APIResponse[dict]:
@@ -24,22 +25,14 @@ def list(
2425
Returns
2526
-------
2627
APIResponse[dict]
27-
response.data['agent'] will be an object of type AgentObject.
28+
response.data['agent'] will be an dictionary.
2829
2930
Raises
3031
------
3132
httpx.HTTPStatusError
3233
If the request fails to fetch.
3334
"""
34-
response = httpx.get(
35-
f"{self.http_url}/agents",
36-
headers=self.headers,
37-
timeout=self.timeout,
38-
)
39-
40-
self.raise_for_status(response=response, message="Failed to fetch agents.")
41-
42-
return APIResponse(**response.json())
35+
return super().get(endpoint="/agents", message="Failed to fetch agents.")
4336

4437
def get(
4538
self,
@@ -59,23 +52,17 @@ def get(
5952
Returns
6053
-------
6154
APIResponse[dict]
62-
response.data['agent'] will be an object of type AgentObject.
55+
response.data['agent'] will be a dictionary.
6356
6457
Raises
6558
------
6659
httpx.HTTPStatusError
6760
If the request fails to fetch.
6861
"""
69-
response = httpx.get(
70-
f"{self.http_url}/agents/{agent_id}",
71-
headers=self.headers,
72-
timeout=self.timeout,
62+
return super().get(
63+
id=agent_id, endpoint="/agents/", message="Failed to fetch agent."
7364
)
7465

75-
self.raise_for_status(response=response, message="Failed to fetch agents.")
76-
77-
return APIResponse(**response.json())
78-
7966
def create(
8067
self,
8168
name: str,
@@ -110,17 +97,10 @@ def create(
11097
"greeting": greeting,
11198
}
11299

113-
response = httpx.post(
114-
f"{self.http_url}/agents",
115-
json=data,
116-
headers=self.headers,
117-
timeout=self.timeout,
100+
return super().post(
101+
data=data, endpoint="/agents", message="Failed to create agent."
118102
)
119103

120-
self.raise_for_status(response=response, message="Failed to create agent.")
121-
122-
return APIResponse(**response.json())
123-
124104
def delete(
125105
self,
126106
agent_id: str,
@@ -143,15 +123,9 @@ def delete(
143123
httpx.HTTPStatusError
144124
If the request fails to delete.
145125
"""
146-
response = httpx.delete(
147-
f"{self.http_url}/agents/{agent_id}",
148-
headers=self.headers,
149-
timeout=self.timeout,
126+
return super().delete(
127+
id=agent_id, endpoint="/agents/", message="Failed to delete agent."
150128
)
151129

152-
self.raise_for_status(response=response, message="Failed to delete agent.")
153-
154-
return APIResponse(**response.json())
155-
156130
def AsyncWebsocketClient(self):
157131
return AsyncAgentWebsocketClient(api_key=self._api_key, base_url=self._base_url)

pyneuphonic/_endpoint.py

Lines changed: 147 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,21 @@
1-
import ssl
2-
import certifi
31
import httpx
42
from typing import Optional
3+
from pyneuphonic.models import APIResponse
54

65

76
class Endpoint:
7+
"""Base class for all API endpoints.
8+
9+
Parameters
10+
----------
11+
api_key : str
12+
The API key for authentication.
13+
base_url : str
14+
The base URL for the API.
15+
timeout : int
16+
The timeout for API requests in seconds. Default is 10 seconds.
17+
"""
18+
819
def __init__(
920
self,
1021
api_key: str,
@@ -36,20 +47,144 @@ def ws_url(self):
3647
prefix = "ws" if self._is_localhost() else "wss"
3748
return f"{prefix}://{self.base_url}"
3849

39-
@property
40-
def ssl_context(self):
41-
ssl_context = (
42-
None
43-
if self._is_localhost()
44-
else ssl.create_default_context(cafile=certifi.where())
45-
)
46-
47-
return ssl_context
48-
4950
def raise_for_status(self, response: httpx.Response, message: Optional[str] = None):
51+
"""
52+
Raises an `httpx.HTTPStatusError` if the response status code indicates an error.
53+
Parameters
54+
----------
55+
response : httpx.Response
56+
The HTTP response to check.
57+
message : Optional[str]
58+
An optional message to include in the error.
59+
Raises
60+
------
61+
httpx.HTTPStatusError
62+
If the response status code indicates an error.
63+
"""
5064
if not response.is_success:
5165
raise httpx.HTTPStatusError(
5266
f'{message + " " if message is not None else ""}Status code: {response.status_code}. Error: {response.text}',
5367
request=response.request,
5468
response=response,
5569
)
70+
71+
def get(
72+
self,
73+
id: str = "",
74+
endpoint: str = "",
75+
message: str = "Failed to fetch.",
76+
) -> APIResponse[dict]:
77+
"""
78+
Fetch items from the API.
79+
80+
Parameters
81+
----------
82+
id
83+
The ID of item to fetch.
84+
85+
Returns
86+
-------
87+
APIResponse[dict]
88+
response.data will be an object of type dict.
89+
90+
Raises
91+
------
92+
httpx.HTTPStatusError
93+
If the request fails to fetch.
94+
"""
95+
96+
response = httpx.get(
97+
f"{self.http_url}{endpoint}{id}",
98+
headers=self.headers,
99+
timeout=self.timeout,
100+
)
101+
102+
self.raise_for_status(response=response, message=message)
103+
104+
return APIResponse(**response.json())
105+
106+
def post(
107+
self,
108+
data: str = None,
109+
params: dict = None,
110+
files: dict = None,
111+
endpoint: str = "",
112+
message: str = "Failed to create.",
113+
) -> APIResponse[dict]:
114+
"""
115+
Post data to create a new resource.
116+
117+
Parameters
118+
----------
119+
data : str
120+
The JSON data to send in the request body.
121+
params : dict
122+
The query parameters to include in the request.
123+
files : dict
124+
The files to upload in the request.
125+
endpoint : str
126+
The API endpoint to send the request to.
127+
message : str
128+
The error message to include in the exception if the request fails.
129+
130+
Returns
131+
-------
132+
APIResponse[dict]
133+
response.data will contain a success message on successful creation.
134+
135+
Raises
136+
------
137+
httpx.HTTPStatusError
138+
If the request fails to create.
139+
"""
140+
141+
response = httpx.post(
142+
f"{self.http_url}{endpoint}",
143+
json=data,
144+
params=params,
145+
files=files,
146+
headers=self.headers,
147+
timeout=self.timeout,
148+
)
149+
150+
self.raise_for_status(response=response, message=message)
151+
152+
return APIResponse(**response.json())
153+
154+
def delete(
155+
self,
156+
id: str,
157+
endpoint: str,
158+
message: str = "Failed to delete.",
159+
) -> APIResponse[dict]:
160+
"""
161+
Delete a resource by its ID.
162+
163+
Parameters
164+
----------
165+
id : str
166+
The ID of the item to delete.
167+
endpoint : str
168+
The API endpoint to send the request to.
169+
message : str
170+
The error message to include in the exception if the request fails.
171+
172+
Returns
173+
-------
174+
APIResponse[dict]
175+
response.data will contain a delete message on successful deletion.
176+
177+
Raises
178+
------
179+
httpx.HTTPStatusError
180+
If the request fails to delete.
181+
"""
182+
response = httpx.delete(
183+
f"{self.http_url}{endpoint}{id}",
184+
headers=self.headers,
185+
timeout=self.timeout,
186+
)
187+
188+
self.raise_for_status(response=response, message=message)
189+
190+
return APIResponse(**response.json())

pyneuphonic/_longform_inference.py

Lines changed: 11 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
1-
import httpx
21
from typing import Generator, Union
3-
42
from pyneuphonic._endpoint import Endpoint
5-
from pyneuphonic.models import TTSConfig, APIResponse, TTSResponse, to_dict
3+
from pyneuphonic.models import TTSConfig, APIResponse, to_dict
64

75

86
class LongformInference(Endpoint):
@@ -17,25 +15,17 @@ def get(self, job_id) -> APIResponse[dict]:
1715
APIResponse[dict]
1816
An APIResponse object containing the status and details of the longform TTS job.
1917
"""
20-
21-
# Accept case if user only provide name
22-
if job_id is None:
23-
raise ValueError("Please provide a job_id")
24-
25-
response = httpx.get(
26-
url=f"{self.http_url}/speak/longform?job_id={job_id}",
27-
headers=self.headers,
28-
timeout=30,
18+
return super().get(
19+
id=job_id,
20+
endpoint="/speak/longform?job_id=",
21+
message="Failed to fetch longform TTS job status.",
2922
)
3023

31-
return APIResponse(**response.json())
32-
3324
def post(
3425
self,
3526
text: str,
3627
tts_config: Union[TTSConfig, dict] = TTSConfig(),
37-
timeout: float = 20,
38-
) -> Generator[APIResponse[TTSResponse], None, None]:
28+
) -> Generator[APIResponse[dict], None, None]:
3929
"""
4030
Send a text to the TTS (text-to-speech) service and receive a stream of APIResponse messages.
4131
@@ -51,28 +41,16 @@ def post(
5141
5242
Returns
5343
-------
54-
APIResponse[TTSResponse]
44+
APIResponse[dict]
5545
An APIResponse object containing the status and details of the longform TTS job.
5646
"""
5747
if not isinstance(tts_config, TTSConfig):
5848
tts_config = TTSConfig(**tts_config)
5949

6050
assert isinstance(text, str), "`text` should be an instance of type `str`."
6151

62-
response = httpx.post(
63-
url=f"{self.http_url}/speak/longform",
64-
headers=self.headers,
65-
json={"text": text, **to_dict(tts_config)},
66-
timeout=timeout,
52+
return super().post(
53+
data={"text": text, **to_dict(tts_config)},
54+
endpoint="/speak/longform",
55+
message="Failed to send text to TTS service.",
6756
)
68-
69-
# Handle response errors
70-
if not response.is_success:
71-
raise httpx.HTTPStatusError(
72-
f"Failed to post longform inference job. Status code: {response.status_code}. Error: {response.text}",
73-
request=response.request,
74-
response=response,
75-
)
76-
77-
# Return the JSON response content as a dictionary
78-
return APIResponse(**response.json())

0 commit comments

Comments
 (0)