Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
86db626
fix: multipart form requests to utlize file and data.
Khurdhula-Harshavardhan Sep 12, 2025
08c6c42
fix: maintain same structure and logic as sync request.
Khurdhula-Harshavardhan Sep 12, 2025
73d5647
fix: mutlipart form request for STT.
Khurdhula-Harshavardhan Sep 13, 2025
caed1b5
fix: multiform request for obj-detection.
Khurdhula-Harshavardhan Sep 13, 2025
4146c25
test: defining test cases for vocr
Khurdhula-Harshavardhan Sep 13, 2025
411bf89
fix: multipart-form request for image translation.
Khurdhula-Harshavardhan Sep 13, 2025
d61fe05
fix: multipart request for NSFW.
Khurdhula-Harshavardhan Sep 13, 2025
306eeb5
fix: multipartform request.
Khurdhula-Harshavardhan Sep 13, 2025
b8bbf70
chore: formatting.
Khurdhula-Harshavardhan Sep 13, 2025
f5b73b2
fix: linting
Khurdhula-Harshavardhan Sep 13, 2025
085907b
test: updating ci to include vocr tests.
Khurdhula-Harshavardhan Sep 13, 2025
b5ec3b3
rm unnecessary params
winzamark123 Sep 15, 2025
762ce6a
fix: drop unnecessary data param, as every request is not multipartform.
Khurdhula-Harshavardhan Sep 15, 2025
2127ca1
chore: ruff formatting.
Khurdhula-Harshavardhan Sep 15, 2025
563f65e
feat: update version to 0.3.4
Khurdhula-Harshavardhan Sep 15, 2025
777706d
feat: ruff formatting.
Khurdhula-Harshavardhan Sep 15, 2025
f97b559
fix: drop unused param.
Khurdhula-Harshavardhan Sep 15, 2025
50d8e5d
fix: updating properties for JigsawStack & AsyncJigsawStack, and embe…
Khurdhula-Harshavardhan Sep 15, 2025
8fa4631
chore: drop redundant disable logging flag.
Khurdhula-Harshavardhan Sep 16, 2025
0c3aa60
fix: pass user defined headers as config.
Khurdhula-Harshavardhan Sep 16, 2025
2d7e43a
fix: pass logging param withing header, rename api_url as base_url an…
Khurdhula-Harshavardhan Sep 16, 2025
160c859
fix: env variable to still be API_URL
Khurdhula-Harshavardhan Sep 16, 2025
d81794d
feat: formatting with ruff
Khurdhula-Harshavardhan Sep 16, 2025
ad9ab56
feat: pass headers to endpoint.
Khurdhula-Harshavardhan Sep 16, 2025
6bef4e1
feat: pass headers to endpoint.
Khurdhula-Harshavardhan Sep 16, 2025
5b29bef
fix: AsyncPromptEngine must accept headers.
Khurdhula-Harshavardhan Sep 16, 2025
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
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ jobs:
- test_web.py
- test_deep_research.py
- test_ai_scrape.py
- test_vocr.py
steps:
- uses: actions/checkout@v4

Expand Down
103 changes: 69 additions & 34 deletions jigsawstack/async_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ def __init__(
headers: Dict[str, str] = None,
data: Union[bytes, None] = None,
stream: Union[bool, None] = False,
files: Union[Dict[str, Any], None] = None, # Add files parameter
):
self.path = path
self.params = params
Expand All @@ -38,6 +39,7 @@ def __init__(
self.headers = headers or {"Content-Type": "application/json"}
self.disable_request_logging = config.get("disable_request_logging")
self.stream = stream
self.files = files # Store files for multipart requests

def __convert_params(
self, params: Union[Dict[Any, Any], List[Dict[Any, Any]]]
Expand Down Expand Up @@ -171,15 +173,23 @@ def __get_headers(self) -> Dict[str, str]:
Dict[str, str]: Configured HTTP Headers
"""
h = {
"Content-Type": "application/json",
"Accept": "application/json",
"x-api-key": f"{self.api_key}",
}

# only add Content-Type if not using multipart (files)
if not self.files and not self.data:
h["Content-Type"] = "application/json"

if self.disable_request_logging:
h["x-jigsaw-no-request-log"] = "true"

_headers = h.copy()

# don't override Content-Type if using multipart
if self.files and "Content-Type" in self.headers:
self.headers.pop("Content-Type")

_headers.update(self.headers)

return _headers
Expand Down Expand Up @@ -231,50 +241,75 @@ async def make_request(
self, session: aiohttp.ClientSession, url: str
) -> aiohttp.ClientResponse:
headers = self.__get_headers()
params = self.params
verb = self.verb
data = self.data
files = self.files

# Convert params to string values for URL encoding
converted_params = self.__convert_params(self.params)
_params = None
_json = None
_data = None
_form_data = None

if verb.lower() in ["get", "delete"]:
# convert params for URL encoding if needed
_params = self.__convert_params(params)
elif files:
# for multipart requests - matches request.py behavior
_form_data = aiohttp.FormData()

# add file(s) to form data
for field_name, file_data in files.items():
if isinstance(file_data, bytes):
# just pass the blob without filename
_form_data.add_field(
field_name, BytesIO(file_data), content_type="application/octet-stream"
)
elif isinstance(file_data, tuple):
# if tuple format (filename, data, content_type)
filename, content, content_type = file_data
_form_data.add_field(
field_name, content, filename=filename, content_type=content_type
)

# add params as 'body' field in multipart form (JSON stringified)
if params and isinstance(params, dict):
_form_data.add_field("body", json.dumps(params), content_type="application/json")
elif data:
# for binary data without multipart
_data = data
# pass params as query parameters for binary uploads
if params and isinstance(params, dict):
_params = self.__convert_params(params)
else:
# for JSON requests
_json = params

# m,ake the request based on the data type
if _form_data:
return await session.request(
verb,
url,
params=_params,
data=_form_data,
headers=headers,
)
elif _json is not None:
return await session.request(
verb,
url,
params=converted_params,
params=_params,
json=_json,
headers=headers,
)
else:
if data is not None:
form_data = aiohttp.FormData()
form_data.add_field(
"file",
BytesIO(data),
content_type=headers.get("Content-Type", "application/octet-stream"),
filename="file",
)

if self.params and isinstance(self.params, dict):
form_data.add_field(
"body", json.dumps(self.params), content_type="application/json"
)

multipart_headers = headers.copy()
multipart_headers.pop("Content-Type", None)

return await session.request(
verb,
url,
data=form_data,
headers=multipart_headers,
)
else:
return await session.request(
verb,
url,
json=self.params, # Keep JSON body as original
headers=headers,
)
return await session.request(
verb,
url,
params=_params,
data=_data,
headers=headers,
)

def __get_session(self) -> aiohttp.ClientSession:
"""
Expand Down
23 changes: 10 additions & 13 deletions jigsawstack/audio.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,9 @@ def speech_to_text(
) -> Union[SpeechToTextResponse, SpeechToTextWebhookResponse]:
options = options or {}
path = "/ai/transcribe"
content_type = options.get("content_type", "application/octet-stream")
headers = {"Content-Type": content_type}
if isinstance(
blob, dict
): # If params is provided as a dict, we assume it's the first argument
params = options or {}
if isinstance(blob, dict):
# URL or file_store_key based request
resp = Request(
config=self.config,
path=path,
Expand All @@ -93,13 +91,13 @@ def speech_to_text(
).perform_with_content()
return resp

files = {"file": blob}
resp = Request(
config=self.config,
path=path,
params=options,
data=blob,
headers=headers,
params=params,
verb="post",
files=files,
).perform_with_content()
return resp

Expand Down Expand Up @@ -136,8 +134,7 @@ async def speech_to_text(
) -> Union[SpeechToTextResponse, SpeechToTextWebhookResponse]:
options = options or {}
path = "/ai/transcribe"
content_type = options.get("content_type", "application/octet-stream")
headers = {"Content-Type": content_type}
params = options or {}
Comment thread
Khurdhula-Harshavardhan marked this conversation as resolved.
Outdated
if isinstance(blob, dict):
resp = await AsyncRequest(
config=self.config,
Expand All @@ -147,12 +144,12 @@ async def speech_to_text(
).perform_with_content()
return resp

files = {"file": blob}
resp = await AsyncRequest(
config=self.config,
path=path,
params=options,
data=blob,
headers=headers,
params=params,
verb="post",
files=files,
).perform_with_content()
return resp
19 changes: 6 additions & 13 deletions jigsawstack/embedding.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
from ._config import ClientConfig
from ._types import BaseResponse
from .async_request import AsyncRequest
from .helpers import build_path
from .request import Request, RequestConfig


Expand Down Expand Up @@ -55,6 +54,7 @@ def execute(
options: EmbeddingParams = None,
) -> EmbeddingResponse:
path = "/embedding"
options = options or {}
if isinstance(blob, dict):
resp = Request(
config=self.config,
Expand All @@ -64,17 +64,13 @@ def execute(
).perform_with_content()
return resp

options = options or {}
path = build_path(base_path=path, params=options)
content_type = options.get("content_type", "application/octet-stream")
_headers = {"Content-Type": content_type}

files = {"file": blob}
resp = Request(
config=self.config,
path=path,
params=options,
data=blob,
headers=_headers,
files=files,
verb="post",
).perform_with_content()
return resp
Expand Down Expand Up @@ -107,6 +103,7 @@ async def execute(
options: EmbeddingParams = None,
) -> EmbeddingResponse:
path = "/embedding"
options = options or {}
if isinstance(blob, dict):
resp = await AsyncRequest(
config=self.config,
Expand All @@ -116,17 +113,13 @@ async def execute(
).perform_with_content()
return resp

options = options or {}
path = build_path(base_path=path, params=options)
content_type = options.get("content_type", "application/octet-stream")
_headers = {"Content-Type": content_type}

files = {"file": blob}
resp = await AsyncRequest(
config=self.config,
path=path,
params=options,
data=blob,
headers=_headers,
files=files,
verb="post",
).perform_with_content()
return resp
19 changes: 6 additions & 13 deletions jigsawstack/embedding_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
from ._config import ClientConfig
from .async_request import AsyncRequest
from .embedding import Chunk
from .helpers import build_path
from .request import Request, RequestConfig


Expand Down Expand Up @@ -53,6 +52,7 @@ def execute(
options: EmbeddingV2Params = None,
) -> EmbeddingV2Response:
path = "/embedding"
options = options or {}
if isinstance(blob, dict):
resp = Request(
config=self.config,
Expand All @@ -62,17 +62,13 @@ def execute(
).perform_with_content()
return resp

options = options or {}
path = build_path(base_path=path, params=options)
content_type = options.get("content_type", "application/octet-stream")
_headers = {"Content-Type": content_type}

files = {"file": blob}
resp = Request(
config=self.config,
path=path,
params=options,
data=blob,
headers=_headers,
files=files,
verb="post",
).perform_with_content()
return resp
Expand Down Expand Up @@ -107,6 +103,7 @@ async def execute(
options: EmbeddingV2Params = None,
) -> EmbeddingV2Response:
path = "/embedding"
options = options or {}
if isinstance(blob, dict):
resp = await AsyncRequest(
config=self.config,
Expand All @@ -116,17 +113,13 @@ async def execute(
).perform_with_content()
return resp

options = options or {}
path = build_path(base_path=path, params=options)
content_type = options.get("content_type", "application/octet-stream")
_headers = {"Content-Type": content_type}

files = {"file": blob}
resp = await AsyncRequest(
config=self.config,
path=path,
params=options,
data=blob,
headers=_headers,
files=files,
verb="post",
).perform_with_content()
return resp
Loading