Skip to content

Commit fc88352

Browse files
authored
Merge pull request #42 from JigsawStack/v3
V3
2 parents 2b1c791 + ff5520d commit fc88352

File tree

11 files changed

+368
-253
lines changed

11 files changed

+368
-253
lines changed

README.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,11 @@ To learn more about all available JigsawStack AI services, view the [Documentati
1818
| ----------------- | -------------------------------------------------- |
1919
| **👉 General** | Translation, Summarization, Sentiment Analysis |
2020
| **🌐 Web** | AI Web Scraping, AI Web Search |
21-
| **🎵 Audio** | Text to Speech, Speech to Text (Whisper large v3) |
21+
| **🎵 Audio** | Text to Speech, Speech to Text |
2222
| **👀 Vision** | vOCR, Object Detection |
2323
| **🧠 LLMs** | Prompt Engine |
2424
| **🖼️ Generative** | AI Image (Flux, SD, SDXL-Fast & more), HTML to Any |
25-
| **🌍 Geo** | Location search |
2625
| **✅ Validation** | Email, NSFW images, profanity & more |
27-
| **📁 Store** | Simple File Storage |
2826

2927
Learn more of about each category in the [API reference](https://docs.jigsawstack.com/api-reference)
3028

jigsawstack/__init__.py

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
from .sentiment import Sentiment, AsyncSentiment
1212
from .validate import Validate, AsyncValidate
1313
from .summary import Summary, AsyncSummary
14-
from .geo import Geo, AsyncGeo
1514
from .prompt_engine import PromptEngine, AsyncPromptEngine
1615
from .embedding import Embedding, AsyncEmbedding
1716
from .exceptions import JigsawStackError
@@ -25,7 +24,6 @@ class JigsawStack:
2524
file: Store
2625
web: Web
2726
search: Search
28-
geo: Geo
2927
prompt_engine: PromptEngine
3028
api_key: str
3129
api_url: str
@@ -103,11 +101,7 @@ def __init__(
103101
api_url=api_url,
104102
disable_request_logging=disable_request_logging,
105103
).translate
106-
self.geo = Geo(
107-
api_key=api_key,
108-
api_url=api_url,
109-
disable_request_logging=disable_request_logging,
110-
)
104+
111105
self.prompt_engine = PromptEngine(
112106
api_key=api_key,
113107
api_url=api_url,
@@ -126,7 +120,6 @@ def __init__(
126120

127121

128122
class AsyncJigsawStack:
129-
geo: AsyncGeo
130123
validate: AsyncValidate
131124
web: AsyncWeb
132125
audio: AsyncAudio
@@ -166,12 +159,6 @@ def __init__(
166159
disable_request_logging=disable_request_logging,
167160
)
168161

169-
self.geo = AsyncGeo(
170-
api_key=api_key,
171-
api_url=api_url,
172-
disable_request_logging=disable_request_logging,
173-
)
174-
175162
self.validate = AsyncValidate(
176163
api_key=api_key,
177164
api_url=api_url,

jigsawstack/async_request.py

Lines changed: 90 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -36,75 +36,89 @@ def __init__(
3636
self.disable_request_logging = config.get("disable_request_logging")
3737
self.stream = stream
3838

39+
def __convert_params(self, params: Union[Dict[Any, Any], List[Dict[Any, Any]]]) -> Dict[str, str]:
40+
"""
41+
Convert parameters to string values for URL encoding.
42+
"""
43+
if params is None:
44+
return {}
45+
46+
if isinstance(params, str):
47+
return params
48+
49+
if isinstance(params, list):
50+
return {} # List params are only used in JSON body
51+
52+
converted = {}
53+
for key, value in params.items():
54+
if isinstance(value, bool):
55+
converted[key] = str(value).lower()
56+
else:
57+
converted[key] = str(value)
58+
return converted
59+
3960
async def perform(self) -> Union[T, None]:
4061
"""
4162
Async method to make an HTTP request to the JigsawStack API.
42-
43-
Returns:
44-
Union[T, None]: A generic type of the Request class or None
45-
46-
Raises:
47-
aiohttp.ClientResponseError: If the request fails
4863
"""
4964
async with self.__get_session() as session:
5065
resp = await self.make_request(session, url=f"{self.api_url}{self.path}")
5166

52-
# delete calls do not return a body
53-
if await resp.text() == "" and resp.status == 200:
54-
return None
55-
56-
# safety net for non-JSON responses
57-
content_type = resp.headers.get("content-type", "")
58-
if "application/json" not in content_type:
59-
raise_for_code_and_type(
60-
code=500,
61-
message="Failed to parse JigsawStack API response. Please try again.",
62-
)
67+
# For binary responses
68+
if resp.status == 200:
69+
content_type = resp.headers.get("content-type", "")
70+
if not resp.text or any(t in content_type for t in ["audio/", "image/", "application/octet-stream", "image/png"]):
71+
content = await resp.read()
72+
return cast(T, content)
6373

64-
# handle error responses
74+
# For error responses
6575
if resp.status != 200:
66-
error = await resp.json()
67-
raise_for_code_and_type(
68-
code=resp.status,
69-
message=error.get("message"),
70-
err=error.get("error"),
71-
)
72-
73-
return cast(T, await resp.json())
74-
75-
async def perform_file(self) -> Union[aiohttp.ClientResponse, None]:
76-
"""
77-
Async method to make an HTTP request and return the raw response.
78-
79-
Returns:
80-
Union[aiohttp.ClientResponse, None]: The raw response object
81-
"""
76+
try:
77+
error = await resp.json()
78+
raise_for_code_and_type(
79+
code=resp.status,
80+
message=error.get("message"),
81+
err=error.get("error"),
82+
)
83+
except json.JSONDecodeError:
84+
raise_for_code_and_type(
85+
code=500,
86+
message="Failed to parse response. Invalid content type or encoding.",
87+
)
88+
89+
# For JSON responses
90+
try:
91+
return cast(T, await resp.json())
92+
except json.JSONDecodeError:
93+
content = await resp.read()
94+
return cast(T, content)
95+
96+
async def perform_file(self) -> Union[T, None]:
8297
async with self.__get_session() as session:
8398
resp = await self.make_request(session, url=f"{self.api_url}{self.path}")
8499

85-
# delete calls do not return a body
86-
if await resp.text() == "" and resp.status == 200:
87-
return None
88-
89-
# handle error responses
90-
if (
91-
"application/json" not in resp.headers.get("content-type", "")
92-
and resp.status != 200
93-
):
94-
raise_for_code_and_type(
95-
code=500,
96-
message="Failed to parse JigsawStack API response. Please try again.",
97-
error_type="InternalServerError",
98-
)
99-
100100
if resp.status != 200:
101-
error = await resp.json()
102-
raise_for_code_and_type(
103-
code=resp.status,
104-
message=error.get("message"),
105-
err=error.get("error"),
106-
)
107-
return resp
101+
try:
102+
error = await resp.json()
103+
raise_for_code_and_type(
104+
code=resp.status,
105+
message=error.get("message"),
106+
err=error.get("error"),
107+
)
108+
except json.JSONDecodeError:
109+
raise_for_code_and_type(
110+
code=500,
111+
message="Failed to parse response. Invalid content type or encoding.",
112+
)
113+
114+
# For binary responses
115+
if resp.status == 200:
116+
content_type = resp.headers.get("content-type", "")
117+
if "application/json" not in content_type:
118+
content = await resp.read()
119+
return cast(T, content)
120+
121+
return cast(T, await resp.json())
108122

109123
async def perform_with_content(self) -> T:
110124
"""
@@ -203,34 +217,36 @@ async def perform_with_content_streaming(
203217
async def make_request(
204218
self, session: aiohttp.ClientSession, url: str
205219
) -> aiohttp.ClientResponse:
206-
"""
207-
Make the actual async HTTP request.
208-
209-
Args:
210-
session (aiohttp.ClientSession): The client session
211-
url (str): The URL to make the request to
212-
213-
Returns:
214-
aiohttp.ClientResponse: The response object from the request
215-
"""
216220
headers = self.__get_headers()
217-
params = self.params
218221
verb = self.verb
219222
data = self.data
220223

221-
request_params = None if verb.lower() not in ["get", "delete"] else params
224+
# Convert params to string values for URL encoding
225+
converted_params = self.__convert_params(self.params)
222226

223-
try:
227+
if verb.lower() in ["get", "delete"]:
224228
return await session.request(
225229
verb,
226230
url,
227-
params=request_params,
228-
json=params,
231+
params=converted_params,
229232
headers=headers,
230-
data=data,
231233
)
232-
except aiohttp.ClientError as e:
233-
raise e
234+
else:
235+
if data is not None:
236+
return await session.request(
237+
verb,
238+
url,
239+
data=data,
240+
params=converted_params, # Use converted params
241+
headers=headers,
242+
)
243+
else:
244+
return await session.request(
245+
verb,
246+
url,
247+
json=self.params, # Keep JSON body as original
248+
headers=headers,
249+
)
234250

235251
def __get_session(self) -> aiohttp.ClientSession:
236252
"""

jigsawstack/helpers.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
2+
from typing import Dict, Optional, Union
3+
from urllib.parse import urlencode
4+
5+
def build_path(base_path: str, params: Optional[Dict[str, Union[str, int, bool]]] = None) -> str:
6+
"""
7+
Build an API endpoint path with query parameters.
8+
9+
Args:
10+
base_path (str): The base path endpoint (e.g. '/store/file')
11+
params (Optional[Dict[str, Union[str, int, bool]]]): A dictionary of query parameters
12+
13+
Returns:
14+
str: The constructed path with query parameters
15+
"""
16+
if params is None:
17+
return base_path
18+
19+
#remove None values from the parameters
20+
filtered_params = {k: v for k, v in params.items() if v is not None}
21+
22+
#encode the parameters
23+
return f"{base_path}?{urlencode(filtered_params)}" if filtered_params else base_path
24+
25+

jigsawstack/prompt_engine.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from .async_request import AsyncRequest
55
from typing import List, Union
66
from ._config import ClientConfig
7+
from .helpers import build_path
78

89

910
class PromptEngineResult(TypedDict):
@@ -137,10 +138,10 @@ def list(
137138
if params.get("page") is None:
138139
params["page"] = 0
139140

140-
limit = params.get("limit")
141-
page = params.get("page")
142-
143-
path = f"/prompt_engine?limit={limit}&page={page}"
141+
path = build_path(
142+
base_path="/prompt_engine",
143+
params=params,
144+
)
144145
resp = Request(
145146
config=self.config, path=path, params={}, verb="get"
146147
).perform_with_content()
@@ -253,10 +254,10 @@ async def list(
253254
if params.get("page") is None:
254255
params["page"] = 0
255256

256-
limit = params.get("limit")
257-
page = params.get("page")
258-
259-
path = f"/prompt_engine?limit={limit}&page={page}"
257+
path = build_path(
258+
base_path="/prompt_engine",
259+
params=params,
260+
)
260261
resp = await AsyncRequest(
261262
config=self.config, path=path, params={}, verb="get"
262263
).perform_with_content()

0 commit comments

Comments
 (0)