From 86db626b1ddc3a150785b8f0301a4518ecaf8e01 Mon Sep 17 00:00:00 2001 From: Khurdhula-Harshavardhan Date: Fri, 12 Sep 2025 16:47:21 -0700 Subject: [PATCH 01/26] fix: multipart form requests to utlize file and data. --- jigsawstack/request.py | 38 +++++++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/jigsawstack/request.py b/jigsawstack/request.py index c1967a4..069d65a 100644 --- a/jigsawstack/request.py +++ b/jigsawstack/request.py @@ -1,3 +1,4 @@ +from importlib.resources import files import json from typing import Any, Dict, Generator, Generic, List, TypedDict, Union, cast @@ -28,6 +29,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, # Change from 'file' to 'files' ): self.path = path self.params = params @@ -38,6 +40,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 # Change from 'file' to 'files' def perform(self) -> Union[T, None]: """Is the main function that makes the HTTP request @@ -152,15 +155,23 @@ def __get_headers(self) -> Dict[Any, Any]: """ 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 @@ -243,20 +254,41 @@ def make_request(self, url: str) -> requests.Response: params = self.params verb = self.verb data = self.data + files = self.files # Change from 'file' to 'files' _requestParams = None + _json = None + _data = None + _files = None if verb.lower() in ["get", "delete"]: _requestParams = params + elif files: + # For multipart requests + _files = files + # Add params as 'body' field in multipart form (JSON stringified) + if params and isinstance(params, dict): + # Convert params to JSON string and add as 'body' field + _data = {"body": json.dumps(params)} + elif data: + # For binary data without multipart + _data = data + # Pass params as query parameters for binary uploads + if params and isinstance(params, dict): + _requestParams = params + else: + # For JSON requests + _json = params try: return requests.request( verb, url, params=_requestParams, - json=params, + json=_json, headers=headers, - data=data, + data=_data, + files=_files, stream=self.stream, ) except requests.HTTPError as e: From 08c6c42107a4e2e6c2fd2dce9ce55c6dc5cc57a0 Mon Sep 17 00:00:00 2001 From: Khurdhula-Harshavardhan Date: Fri, 12 Sep 2025 16:48:52 -0700 Subject: [PATCH 02/26] fix: maintain same structure and logic as sync request. --- jigsawstack/async_request.py | 115 ++++++++++++++++++++++++----------- 1 file changed, 79 insertions(+), 36 deletions(-) diff --git a/jigsawstack/async_request.py b/jigsawstack/async_request.py index 26a7e53..1fbf44d 100644 --- a/jigsawstack/async_request.py +++ b/jigsawstack/async_request.py @@ -1,7 +1,6 @@ import json -from io import BytesIO from typing import Any, AsyncGenerator, Dict, Generic, List, TypedDict, Union, cast - +from io import BytesIO import aiohttp from typing_extensions import Literal, TypeVar @@ -28,6 +27,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 @@ -38,6 +38,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]]] @@ -171,15 +172,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 @@ -231,50 +240,84 @@ 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: """ From 73d5647a8a1626fb7952d15e9d6bd599f5000449 Mon Sep 17 00:00:00 2001 From: Khurdhula-Harshavardhan Date: Fri, 12 Sep 2025 18:39:02 -0700 Subject: [PATCH 03/26] fix: mutlipart form request for STT. --- jigsawstack/audio.py | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/jigsawstack/audio.py b/jigsawstack/audio.py index cadfd25..043be8c 100644 --- a/jigsawstack/audio.py +++ b/jigsawstack/audio.py @@ -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, @@ -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 @@ -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 {} if isinstance(blob, dict): resp = await AsyncRequest( config=self.config, @@ -146,13 +143,13 @@ async def speech_to_text( verb="post", ).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 From caed1b57fbc673b02a51c71ea132b263afe243e5 Mon Sep 17 00:00:00 2001 From: Khurdhula-Harshavardhan Date: Fri, 12 Sep 2025 18:40:38 -0700 Subject: [PATCH 04/26] fix: multiform request for obj-detection. --- jigsawstack/vision.py | 32 ++++++++++++-------------------- 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/jigsawstack/vision.py b/jigsawstack/vision.py index 6df4e37..ed1d2a9 100644 --- a/jigsawstack/vision.py +++ b/jigsawstack/vision.py @@ -1,4 +1,5 @@ from typing import Any, Dict, List, Optional, Union, cast, overload +from wsgiref import headers from typing_extensions import Literal, NotRequired, TypedDict @@ -159,10 +160,10 @@ class OCRResponse(BaseResponse): tags: List[str] has_text: bool sections: List[object] - total_pages: Optional[int] # Only available for PDFs - page_ranges: Optional[ + total_pages: Optional[int] + page_range: Optional[ List[int] - ] # Only available if page_ranges is set in the request parameters. + ] # Only available if page_range is set in the request parameters. class Vision(ClientConfig): @@ -204,15 +205,13 @@ def vocr( ).perform_with_content() return resp - 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 @@ -239,16 +238,13 @@ def object_detection( verb="post", ).perform_with_content() return resp - - 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 @@ -291,15 +287,13 @@ async def vocr( ).perform_with_content() return resp - 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 @@ -329,15 +323,13 @@ async def object_detection( ).perform_with_content() return resp - 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 From 4146c25464692d468c9b2df261ec23eabff5f978 Mon Sep 17 00:00:00 2001 From: Khurdhula-Harshavardhan Date: Fri, 12 Sep 2025 18:42:50 -0700 Subject: [PATCH 05/26] test: defining test cases for vocr --- tests/test_vocr.py | 260 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 260 insertions(+) create mode 100644 tests/test_vocr.py diff --git a/tests/test_vocr.py b/tests/test_vocr.py new file mode 100644 index 0000000..df809d2 --- /dev/null +++ b/tests/test_vocr.py @@ -0,0 +1,260 @@ +import logging +import os + +import pytest +import requests +from dotenv import load_dotenv + +import jigsawstack +from jigsawstack.exceptions import JigsawStackError + +load_dotenv() + +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + +jigsaw = jigsawstack.JigsawStack(api_url="http://localhost:3000/api/", api_key=os.getenv("JIGSAWSTACK_API_KEY")) +async_jigsaw = jigsawstack.AsyncJigsawStack(api_url="http://localhost:3000/api/", api_key=os.getenv("JIGSAWSTACK_API_KEY")) + +IMAGE_URL = "https://jigsawstack.com/preview/vocr-example.jpg" + +# PDF URL for testing page_range functionality +PDF_URL = "https://arxiv.org/pdf/1706.03762" + +TEST_CASES = [ + { + "name": "with_url_only", + "params": {"url": IMAGE_URL}, + "blob": None, + "options": None, + }, + { + "name": "with_blob_only", + "params": None, + "blob": IMAGE_URL, + "options": None, + }, + { + "name": "with_string_prompt", + "blob": IMAGE_URL, + "options": {"prompt": "Extract all text from the image"}, + }, + { + "name": "with_list_prompt", + "blob": IMAGE_URL, + "options": { + "prompt": [ + "What is the main heading?", + "Extract any dates mentioned", + "What are the key points?" + ] + }, + }, + { + "name": "with_dict_prompt", + "blob": IMAGE_URL, + "options": { + "prompt": { + "title": "Extract the main title", + "content": "What is the main content?", + "metadata": "Extract any metadata or additional information" + } + }, + }, + { + "name": "url_with_string_prompt", + "params": { + "url": IMAGE_URL, + "prompt": "Summarize the text content" + }, + "blob": None, + "options": None, + }, + { + "name": "url_with_list_prompt", + "params": { + "url": IMAGE_URL, + "prompt": ["Extract headers", "Extract body text"] + }, + "blob": None, + "options": None, + }, +] + +# PDF specific test cases +PDF_TEST_CASES = [ + { + "name": "pdf_with_page_range", + "params": { + "url": PDF_URL, + "page_range": [1, 3], + "prompt": "Extract text from these pages" + }, + "blob": None, + "options": None, + }, + { + "name": "pdf_single_page", + "params": { + "url": PDF_URL, + "page_range": [1, 1], + "prompt": "What is on the first page?" + }, + "blob": None, + "options": None, + }, + { + "name": "pdf_blob_with_page_range", + "blob": PDF_URL, + "options": { + "page_range": [1, 3], + "prompt": "what is this about?" + }, + }, +] + + +class TestVOCRSync: + """Test synchronous VOCR methods""" + + sync_test_cases = TEST_CASES + pdf_test_cases = PDF_TEST_CASES + + @pytest.mark.parametrize( + "test_case", sync_test_cases, ids=[tc["name"] for tc in sync_test_cases] + ) + def test_vocr(self, test_case): + """Test synchronous VOCR with various inputs""" + try: + if test_case.get("blob"): + # Download blob content + blob_content = requests.get(test_case["blob"]).content + result = jigsaw.vision.vocr(blob_content, test_case.get("options", {})) + else: + # Use params directly + result = jigsaw.vision.vocr(test_case["params"]) + + print(f"Test {test_case['name']}: Success={result.get('success')}") + + # Verify response structure + assert result["success"] is True + if "prompt" in (test_case.get("params") or {}): + assert "context" in result + assert "width" in result + assert "height" in result + assert "has_text" in result + assert "tags" in result + assert isinstance(result["tags"], list) + assert "sections" in result + assert isinstance(result["sections"], list) + + except JigsawStackError as e: + pytest.fail(f"Unexpected JigsawStackError in {test_case['name']}: {e}") + + @pytest.mark.parametrize( + "test_case", pdf_test_cases, ids=[tc["name"] for tc in pdf_test_cases] + ) + def test_vocr_pdf(self, test_case): + """Test synchronous VOCR with PDF inputs""" + try: + if test_case.get("blob"): + # Download blob content + blob_content = requests.get(test_case["blob"]).content + result = jigsaw.vision.vocr(blob_content, test_case.get("options", {})) + else: + # Use params directly + result = jigsaw.vision.vocr(test_case["params"]) + + # Verify response structure + assert result["success"] is True + if "prompt" in (test_case.get("params") or {}): + assert "context" in result + assert "total_pages" in result + + if test_case.get("params", {}).get("page_range") or test_case.get("options", {}).get("page_range"): + assert "page_range" in result + assert isinstance(result["page_range"], list) + + logger.info(f"Test {test_case['name']}: total_pages={result.get('total_pages')}") + + except JigsawStackError as e: + pytest.fail(f"Unexpected JigsawStackError in {test_case['name']}: {e}") + + +class TestVOCRAsync: + """Test asynchronous VOCR methods""" + + async_test_cases = TEST_CASES + pdf_test_cases = PDF_TEST_CASES + + @pytest.mark.parametrize( + "test_case", async_test_cases, ids=[tc["name"] for tc in async_test_cases] + ) + @pytest.mark.asyncio + async def test_vocr_async(self, test_case): + """Test asynchronous VOCR with various inputs""" + try: + if test_case.get("blob"): + # Download blob content + blob_content = requests.get(test_case["blob"]).content + result = await async_jigsaw.vision.vocr( + blob_content, test_case.get("options", {}) + ) + else: + # Use params directly + result = await async_jigsaw.vision.vocr(test_case["params"]) + + print(f"Test {test_case['name']}: Success={result.get('success')}") + + # Verify response structure + assert result["success"] is True + if "prompt" in (test_case.get("params") or {}): + assert "context" in result + assert "width" in result + assert "height" in result + assert "has_text" in result + assert "tags" in result + assert isinstance(result["tags"], list) + assert "sections" in result + assert isinstance(result["sections"], list) + + # Log some details + logger.info(f"Test {test_case['name']}: has_text={result['has_text']}, tags={result['tags'][:3] if result['tags'] else []}") + + except JigsawStackError as e: + pytest.fail(f"Unexpected JigsawStackError in {test_case['name']}: {e}") + + @pytest.mark.parametrize( + "test_case", pdf_test_cases, ids=[tc["name"] for tc in pdf_test_cases] + ) + @pytest.mark.asyncio + async def test_vocr_pdf_async(self, test_case): + """Test asynchronous VOCR with PDF inputs""" + try: + if test_case.get("blob"): + # Download blob content + blob_content = requests.get(test_case["blob"]).content + result = await async_jigsaw.vision.vocr( + blob_content, test_case.get("options", {}) + ) + else: + # Use params directly + result = await async_jigsaw.vision.vocr(test_case["params"]) + + print(f"Test {test_case['name']}: Success={result.get('success')}") + + # Verify response structure + assert result["success"] is True + if "prompt" in (test_case.get("params") or {}): + assert "context" in result + assert "total_pages" in result # PDF specific + + # Check if page_range is in response when requested + if test_case.get("params", {}).get("page_range") or test_case.get("options", {}).get("page_range"): + assert "page_range" in result + assert isinstance(result["page_range"], list) + + logger.info(f"Test {test_case['name']}: total_pages={result.get('total_pages')}") + + except JigsawStackError as e: + pytest.fail(f"Unexpected JigsawStackError in {test_case['name']}: {e}") \ No newline at end of file From 411bf893c69ee1e86c797e0e1ac88dc9a199c776 Mon Sep 17 00:00:00 2001 From: Khurdhula-Harshavardhan Date: Fri, 12 Sep 2025 18:54:25 -0700 Subject: [PATCH 06/26] fix: multipart-form request for image translation. --- jigsawstack/translate.py | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/jigsawstack/translate.py b/jigsawstack/translate.py index 63b7fa5..2967514 100644 --- a/jigsawstack/translate.py +++ b/jigsawstack/translate.py @@ -95,6 +95,8 @@ def image( blob: Union[TranslateImageParams, bytes], options: TranslateImageParams = None, ) -> Union[TranslateImageResponse, bytes]: + path = "/ai/translate/image" + options = options or {} if isinstance( blob, dict ): # If params is provided as a dict, we assume it's the first argument @@ -106,17 +108,14 @@ def image( ).perform_with_content() return resp - options = options or {} - path = build_path(base_path="/ai/translate/image", 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 @@ -159,6 +158,8 @@ async def image( blob: Union[TranslateImageParams, bytes], options: TranslateImageParams = None, ) -> Union[TranslateImageResponse, bytes]: + path = "/ai/translate/image" + options = options or {} if isinstance(blob, dict): resp = await AsyncRequest( config=self.config, @@ -168,17 +169,13 @@ async def image( ).perform_with_content() return resp - options = options or {} - path = build_path(base_path="/ai/translate/image", 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 From d61fe05b5f56cf1e536348188cbea02130e0bc6f Mon Sep 17 00:00:00 2001 From: Khurdhula-Harshavardhan Date: Fri, 12 Sep 2025 18:59:38 -0700 Subject: [PATCH 07/26] fix: multipart request for NSFW. --- jigsawstack/validate.py | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/jigsawstack/validate.py b/jigsawstack/validate.py index fc57c3c..5079385 100644 --- a/jigsawstack/validate.py +++ b/jigsawstack/validate.py @@ -99,6 +99,8 @@ def nsfw( blob: Union[NSFWParams, bytes], options: NSFWParams = None, ) -> NSFWResponse: + path = "/validate/nsfw" + options = options or {} if isinstance( blob, dict ): # If params is provided as a dict, we assume it's the first argument @@ -109,18 +111,14 @@ def nsfw( verb="post", ).perform_with_content() return resp - - options = options or {} - path = build_path(base_path="/validate/nsfw", 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 @@ -188,28 +186,26 @@ async def nsfw( blob: Union[NSFWParams, bytes], options: NSFWParams = None, ) -> NSFWResponse: + path = "/validate/nsfw" + options = options or {} if isinstance( blob, dict ): # If params is provided as a dict, we assume it's the first argument resp = await AsyncRequest( config=self.config, - path="/validate/nsfw", + path=path, params=cast(Dict[Any, Any], blob), verb="post", ).perform_with_content() return resp - - options = options or {} - path = build_path(base_path="/validate/nsfw", 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 From 306eeb56acc71c26a8592d9a30377bd0e73bc2b8 Mon Sep 17 00:00:00 2001 From: Khurdhula-Harshavardhan Date: Fri, 12 Sep 2025 19:04:20 -0700 Subject: [PATCH 08/26] fix: multipartform request. --- jigsawstack/embedding.py | 20 ++++++++------------ jigsawstack/embedding_v2.py | 18 ++++++------------ 2 files changed, 14 insertions(+), 24 deletions(-) diff --git a/jigsawstack/embedding.py b/jigsawstack/embedding.py index cd755f0..511f9d1 100644 --- a/jigsawstack/embedding.py +++ b/jigsawstack/embedding.py @@ -1,3 +1,4 @@ +from importlib.metadata import files from typing import Any, Dict, List, Literal, Union, cast, overload from typing_extensions import NotRequired, TypedDict @@ -55,6 +56,7 @@ def execute( options: EmbeddingParams = None, ) -> EmbeddingResponse: path = "/embedding" + options = options or {} if isinstance(blob, dict): resp = Request( config=self.config, @@ -64,17 +66,14 @@ 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 @@ -107,6 +106,7 @@ async def execute( options: EmbeddingParams = None, ) -> EmbeddingResponse: path = "/embedding" + options = options or {} if isinstance(blob, dict): resp = await AsyncRequest( config=self.config, @@ -116,17 +116,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 diff --git a/jigsawstack/embedding_v2.py b/jigsawstack/embedding_v2.py index fe62f69..6ce501b 100644 --- a/jigsawstack/embedding_v2.py +++ b/jigsawstack/embedding_v2.py @@ -53,6 +53,7 @@ def execute( options: EmbeddingV2Params = None, ) -> EmbeddingV2Response: path = "/embedding" + options = options or {} if isinstance(blob, dict): resp = Request( config=self.config, @@ -62,17 +63,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 @@ -107,6 +104,7 @@ async def execute( options: EmbeddingV2Params = None, ) -> EmbeddingV2Response: path = "/embedding" + options = options or {} if isinstance(blob, dict): resp = await AsyncRequest( config=self.config, @@ -116,17 +114,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 From b8bbf70dd7b97da4c9a30aee3ec748d2b4d3875f Mon Sep 17 00:00:00 2001 From: Khurdhula-Harshavardhan Date: Fri, 12 Sep 2025 19:05:04 -0700 Subject: [PATCH 09/26] chore: formatting. --- jigsawstack/async_request.py | 27 ++++------- jigsawstack/audio.py | 6 +-- jigsawstack/embedding.py | 1 - jigsawstack/request.py | 6 +-- jigsawstack/translate.py | 1 - jigsawstack/validate.py | 4 +- jigsawstack/vision.py | 4 +- tests/test_vocr.py | 91 +++++++++++++++--------------------- 8 files changed, 57 insertions(+), 83 deletions(-) diff --git a/jigsawstack/async_request.py b/jigsawstack/async_request.py index 1fbf44d..a1fc5bf 100644 --- a/jigsawstack/async_request.py +++ b/jigsawstack/async_request.py @@ -184,11 +184,11 @@ def __get_headers(self) -> Dict[str, str]: h["x-jigsaw-no-request-log"] = "true" _headers = h.copy() - - #don't override Content-Type if using multipart + + # 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 @@ -251,38 +251,29 @@ async def make_request( _form_data = None if verb.lower() in ["get", "delete"]: - #convert params for URL encoding if needed + # 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" + 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 + 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" - ) + _form_data.add_field("body", json.dumps(params), content_type="application/json") elif data: # for binary data without multipart _data = data diff --git a/jigsawstack/audio.py b/jigsawstack/audio.py index 043be8c..47c2cd8 100644 --- a/jigsawstack/audio.py +++ b/jigsawstack/audio.py @@ -80,8 +80,8 @@ def speech_to_text( ) -> Union[SpeechToTextResponse, SpeechToTextWebhookResponse]: options = options or {} path = "/ai/transcribe" - params= options or {} - if isinstance(blob, dict): + params = options or {} + if isinstance(blob, dict): # URL or file_store_key based request resp = Request( config=self.config, @@ -143,7 +143,7 @@ async def speech_to_text( verb="post", ).perform_with_content() return resp - + files = {"file": blob} resp = await AsyncRequest( config=self.config, diff --git a/jigsawstack/embedding.py b/jigsawstack/embedding.py index 511f9d1..edbef82 100644 --- a/jigsawstack/embedding.py +++ b/jigsawstack/embedding.py @@ -66,7 +66,6 @@ def execute( ).perform_with_content() return resp - files = {"file": blob} resp = Request( config=self.config, diff --git a/jigsawstack/request.py b/jigsawstack/request.py index 069d65a..0aa1a40 100644 --- a/jigsawstack/request.py +++ b/jigsawstack/request.py @@ -158,7 +158,7 @@ def __get_headers(self) -> Dict[Any, Any]: "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" @@ -167,11 +167,11 @@ def __get_headers(self) -> Dict[Any, Any]: 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 diff --git a/jigsawstack/translate.py b/jigsawstack/translate.py index 2967514..d8f9974 100644 --- a/jigsawstack/translate.py +++ b/jigsawstack/translate.py @@ -108,7 +108,6 @@ def image( ).perform_with_content() return resp - files = {"file": blob} resp = Request( config=self.config, diff --git a/jigsawstack/validate.py b/jigsawstack/validate.py index 5079385..6ee4040 100644 --- a/jigsawstack/validate.py +++ b/jigsawstack/validate.py @@ -111,7 +111,7 @@ def nsfw( verb="post", ).perform_with_content() return resp - + files = {"file": blob} resp = Request( config=self.config, @@ -198,7 +198,7 @@ async def nsfw( verb="post", ).perform_with_content() return resp - + files = {"file": blob} resp = await AsyncRequest( config=self.config, diff --git a/jigsawstack/vision.py b/jigsawstack/vision.py index ed1d2a9..a8bb3af 100644 --- a/jigsawstack/vision.py +++ b/jigsawstack/vision.py @@ -160,7 +160,7 @@ class OCRResponse(BaseResponse): tags: List[str] has_text: bool sections: List[object] - total_pages: Optional[int] + total_pages: Optional[int] page_range: Optional[ List[int] ] # Only available if page_range is set in the request parameters. @@ -205,7 +205,7 @@ def vocr( ).perform_with_content() return resp - files ={"file": blob} + files = {"file": blob} resp = Request( config=self.config, path=path, diff --git a/tests/test_vocr.py b/tests/test_vocr.py index df809d2..13c7a32 100644 --- a/tests/test_vocr.py +++ b/tests/test_vocr.py @@ -13,8 +13,12 @@ logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) -jigsaw = jigsawstack.JigsawStack(api_url="http://localhost:3000/api/", api_key=os.getenv("JIGSAWSTACK_API_KEY")) -async_jigsaw = jigsawstack.AsyncJigsawStack(api_url="http://localhost:3000/api/", api_key=os.getenv("JIGSAWSTACK_API_KEY")) +jigsaw = jigsawstack.JigsawStack( + api_url="http://localhost:3000/api/", api_key=os.getenv("JIGSAWSTACK_API_KEY") +) +async_jigsaw = jigsawstack.AsyncJigsawStack( + api_url="http://localhost:3000/api/", api_key=os.getenv("JIGSAWSTACK_API_KEY") +) IMAGE_URL = "https://jigsawstack.com/preview/vocr-example.jpg" @@ -46,7 +50,7 @@ "prompt": [ "What is the main heading?", "Extract any dates mentioned", - "What are the key points?" + "What are the key points?", ] }, }, @@ -57,25 +61,19 @@ "prompt": { "title": "Extract the main title", "content": "What is the main content?", - "metadata": "Extract any metadata or additional information" + "metadata": "Extract any metadata or additional information", } }, }, { "name": "url_with_string_prompt", - "params": { - "url": IMAGE_URL, - "prompt": "Summarize the text content" - }, + "params": {"url": IMAGE_URL, "prompt": "Summarize the text content"}, "blob": None, "options": None, }, { "name": "url_with_list_prompt", - "params": { - "url": IMAGE_URL, - "prompt": ["Extract headers", "Extract body text"] - }, + "params": {"url": IMAGE_URL, "prompt": ["Extract headers", "Extract body text"]}, "blob": None, "options": None, }, @@ -85,31 +83,20 @@ PDF_TEST_CASES = [ { "name": "pdf_with_page_range", - "params": { - "url": PDF_URL, - "page_range": [1, 3], - "prompt": "Extract text from these pages" - }, + "params": {"url": PDF_URL, "page_range": [1, 3], "prompt": "Extract text from these pages"}, "blob": None, "options": None, }, { "name": "pdf_single_page", - "params": { - "url": PDF_URL, - "page_range": [1, 1], - "prompt": "What is on the first page?" - }, + "params": {"url": PDF_URL, "page_range": [1, 1], "prompt": "What is on the first page?"}, "blob": None, "options": None, }, { "name": "pdf_blob_with_page_range", "blob": PDF_URL, - "options": { - "page_range": [1, 3], - "prompt": "what is this about?" - }, + "options": {"page_range": [1, 3], "prompt": "what is this about?"}, }, ] @@ -135,7 +122,7 @@ def test_vocr(self, test_case): result = jigsaw.vision.vocr(test_case["params"]) print(f"Test {test_case['name']}: Success={result.get('success')}") - + # Verify response structure assert result["success"] is True if "prompt" in (test_case.get("params") or {}): @@ -147,13 +134,11 @@ def test_vocr(self, test_case): assert isinstance(result["tags"], list) assert "sections" in result assert isinstance(result["sections"], list) - + except JigsawStackError as e: pytest.fail(f"Unexpected JigsawStackError in {test_case['name']}: {e}") - @pytest.mark.parametrize( - "test_case", pdf_test_cases, ids=[tc["name"] for tc in pdf_test_cases] - ) + @pytest.mark.parametrize("test_case", pdf_test_cases, ids=[tc["name"] for tc in pdf_test_cases]) def test_vocr_pdf(self, test_case): """Test synchronous VOCR with PDF inputs""" try: @@ -164,19 +149,21 @@ def test_vocr_pdf(self, test_case): else: # Use params directly result = jigsaw.vision.vocr(test_case["params"]) - + # Verify response structure assert result["success"] is True if "prompt" in (test_case.get("params") or {}): assert "context" in result assert "total_pages" in result - - if test_case.get("params", {}).get("page_range") or test_case.get("options", {}).get("page_range"): + + if test_case.get("params", {}).get("page_range") or test_case.get("options", {}).get( + "page_range" + ): assert "page_range" in result assert isinstance(result["page_range"], list) logger.info(f"Test {test_case['name']}: total_pages={result.get('total_pages')}") - + except JigsawStackError as e: pytest.fail(f"Unexpected JigsawStackError in {test_case['name']}: {e}") @@ -197,15 +184,13 @@ async def test_vocr_async(self, test_case): if test_case.get("blob"): # Download blob content blob_content = requests.get(test_case["blob"]).content - result = await async_jigsaw.vision.vocr( - blob_content, test_case.get("options", {}) - ) + result = await async_jigsaw.vision.vocr(blob_content, test_case.get("options", {})) else: # Use params directly result = await async_jigsaw.vision.vocr(test_case["params"]) print(f"Test {test_case['name']}: Success={result.get('success')}") - + # Verify response structure assert result["success"] is True if "prompt" in (test_case.get("params") or {}): @@ -217,16 +202,16 @@ async def test_vocr_async(self, test_case): assert isinstance(result["tags"], list) assert "sections" in result assert isinstance(result["sections"], list) - + # Log some details - logger.info(f"Test {test_case['name']}: has_text={result['has_text']}, tags={result['tags'][:3] if result['tags'] else []}") - + logger.info( + f"Test {test_case['name']}: has_text={result['has_text']}, tags={result['tags'][:3] if result['tags'] else []}" + ) + except JigsawStackError as e: pytest.fail(f"Unexpected JigsawStackError in {test_case['name']}: {e}") - @pytest.mark.parametrize( - "test_case", pdf_test_cases, ids=[tc["name"] for tc in pdf_test_cases] - ) + @pytest.mark.parametrize("test_case", pdf_test_cases, ids=[tc["name"] for tc in pdf_test_cases]) @pytest.mark.asyncio async def test_vocr_pdf_async(self, test_case): """Test asynchronous VOCR with PDF inputs""" @@ -234,27 +219,27 @@ async def test_vocr_pdf_async(self, test_case): if test_case.get("blob"): # Download blob content blob_content = requests.get(test_case["blob"]).content - result = await async_jigsaw.vision.vocr( - blob_content, test_case.get("options", {}) - ) + result = await async_jigsaw.vision.vocr(blob_content, test_case.get("options", {})) else: # Use params directly result = await async_jigsaw.vision.vocr(test_case["params"]) print(f"Test {test_case['name']}: Success={result.get('success')}") - + # Verify response structure assert result["success"] is True if "prompt" in (test_case.get("params") or {}): assert "context" in result assert "total_pages" in result # PDF specific - + # Check if page_range is in response when requested - if test_case.get("params", {}).get("page_range") or test_case.get("options", {}).get("page_range"): + if test_case.get("params", {}).get("page_range") or test_case.get("options", {}).get( + "page_range" + ): assert "page_range" in result assert isinstance(result["page_range"], list) logger.info(f"Test {test_case['name']}: total_pages={result.get('total_pages')}") - + except JigsawStackError as e: - pytest.fail(f"Unexpected JigsawStackError in {test_case['name']}: {e}") \ No newline at end of file + pytest.fail(f"Unexpected JigsawStackError in {test_case['name']}: {e}") From f5b73b25d4d75fe95cf1de2901f388ff48f105ce Mon Sep 17 00:00:00 2001 From: Khurdhula-Harshavardhan Date: Fri, 12 Sep 2025 19:05:31 -0700 Subject: [PATCH 10/26] fix: linting --- jigsawstack/async_request.py | 3 ++- jigsawstack/embedding.py | 2 -- jigsawstack/embedding_v2.py | 1 - jigsawstack/request.py | 1 - jigsawstack/translate.py | 1 - jigsawstack/vision.py | 1 - 6 files changed, 2 insertions(+), 7 deletions(-) diff --git a/jigsawstack/async_request.py b/jigsawstack/async_request.py index a1fc5bf..55fdfa1 100644 --- a/jigsawstack/async_request.py +++ b/jigsawstack/async_request.py @@ -1,6 +1,7 @@ import json -from typing import Any, AsyncGenerator, Dict, Generic, List, TypedDict, Union, cast from io import BytesIO +from typing import Any, AsyncGenerator, Dict, Generic, List, TypedDict, Union, cast + import aiohttp from typing_extensions import Literal, TypeVar diff --git a/jigsawstack/embedding.py b/jigsawstack/embedding.py index edbef82..203dc23 100644 --- a/jigsawstack/embedding.py +++ b/jigsawstack/embedding.py @@ -1,4 +1,3 @@ -from importlib.metadata import files from typing import Any, Dict, List, Literal, Union, cast, overload from typing_extensions import NotRequired, TypedDict @@ -6,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 diff --git a/jigsawstack/embedding_v2.py b/jigsawstack/embedding_v2.py index 6ce501b..b9514f9 100644 --- a/jigsawstack/embedding_v2.py +++ b/jigsawstack/embedding_v2.py @@ -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 diff --git a/jigsawstack/request.py b/jigsawstack/request.py index 0aa1a40..84ef8cf 100644 --- a/jigsawstack/request.py +++ b/jigsawstack/request.py @@ -1,4 +1,3 @@ -from importlib.resources import files import json from typing import Any, Dict, Generator, Generic, List, TypedDict, Union, cast diff --git a/jigsawstack/translate.py b/jigsawstack/translate.py index d8f9974..42b50dd 100644 --- a/jigsawstack/translate.py +++ b/jigsawstack/translate.py @@ -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 diff --git a/jigsawstack/vision.py b/jigsawstack/vision.py index a8bb3af..fe1d94a 100644 --- a/jigsawstack/vision.py +++ b/jigsawstack/vision.py @@ -1,5 +1,4 @@ from typing import Any, Dict, List, Optional, Union, cast, overload -from wsgiref import headers from typing_extensions import Literal, NotRequired, TypedDict From 085907bbd6bd7790251e433c8b660c7b91871204 Mon Sep 17 00:00:00 2001 From: Khurdhula-Harshavardhan Date: Fri, 12 Sep 2025 19:07:02 -0700 Subject: [PATCH 11/26] test: updating ci to include vocr tests. --- .github/workflows/ci.yml | 1 + tests/test_vocr.py | 8 ++------ 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b1f5b26..1eea9f3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -48,6 +48,7 @@ jobs: - test_web.py - test_deep_research.py - test_ai_scrape.py + - test_vocr.py steps: - uses: actions/checkout@v4 diff --git a/tests/test_vocr.py b/tests/test_vocr.py index 13c7a32..d233484 100644 --- a/tests/test_vocr.py +++ b/tests/test_vocr.py @@ -13,12 +13,8 @@ logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) -jigsaw = jigsawstack.JigsawStack( - api_url="http://localhost:3000/api/", api_key=os.getenv("JIGSAWSTACK_API_KEY") -) -async_jigsaw = jigsawstack.AsyncJigsawStack( - api_url="http://localhost:3000/api/", api_key=os.getenv("JIGSAWSTACK_API_KEY") -) +jigsaw = jigsawstack.JigsawStack(api_key=os.getenv("JIGSAWSTACK_API_KEY")) +async_jigsaw = jigsawstack.AsyncJigsawStack(api_key=os.getenv("JIGSAWSTACK_API_KEY")) IMAGE_URL = "https://jigsawstack.com/preview/vocr-example.jpg" From b5ec3b3fe71bdc8b7130141ecf88b5912f1440fe Mon Sep 17 00:00:00 2001 From: Win Cheng Date: Sun, 14 Sep 2025 22:21:00 -0700 Subject: [PATCH 12/26] rm unnecessary params --- jigsawstack/audio.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/jigsawstack/audio.py b/jigsawstack/audio.py index 47c2cd8..2f004c4 100644 --- a/jigsawstack/audio.py +++ b/jigsawstack/audio.py @@ -80,7 +80,6 @@ def speech_to_text( ) -> Union[SpeechToTextResponse, SpeechToTextWebhookResponse]: options = options or {} path = "/ai/transcribe" - params = options or {} if isinstance(blob, dict): # URL or file_store_key based request resp = Request( @@ -95,7 +94,7 @@ def speech_to_text( resp = Request( config=self.config, path=path, - params=params, + params=options, verb="post", files=files, ).perform_with_content() From 762ce6af5cb03e1bf0888a346396c167d5f257f2 Mon Sep 17 00:00:00 2001 From: Khurdhula-Harshavardhan Date: Mon, 15 Sep 2025 14:24:54 -0700 Subject: [PATCH 13/26] fix: drop unnecessary data param, as every request is not multipartform. --- jigsawstack/async_request.py | 68 ++++++++---------------------------- jigsawstack/audio.py | 3 +- jigsawstack/embedding.py | 10 +++--- jigsawstack/embedding_v2.py | 6 ++-- jigsawstack/request.py | 28 ++++++--------- jigsawstack/translate.py | 10 +++--- jigsawstack/validate.py | 2 -- jigsawstack/version.py | 2 +- jigsawstack/vision.py | 12 +++---- 9 files changed, 49 insertions(+), 92 deletions(-) diff --git a/jigsawstack/async_request.py b/jigsawstack/async_request.py index 55fdfa1..dc0c063 100644 --- a/jigsawstack/async_request.py +++ b/jigsawstack/async_request.py @@ -243,7 +243,6 @@ async def make_request( headers = self.__get_headers() params = self.params verb = self.verb - data = self.data files = self.files _params = None @@ -252,64 +251,27 @@ async def make_request( _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) + _form_data.add_field("file", BytesIO(files["file"]), filename="upload") 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 + _form_data.add_field( + "body", json.dumps(params), content_type="application/json" + ) + + headers.pop("Content-Type", None) + else: # pure JSON request _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=_params, - json=_json, - headers=headers, - ) - else: - return await session.request( - verb, - url, - params=_params, - data=_data, - headers=headers, - ) + return await session.request( + verb, + url, + params=_params, + json=_json, + data=_form_data or _data, + headers=headers, + ) def __get_session(self) -> aiohttp.ClientSession: """ diff --git a/jigsawstack/audio.py b/jigsawstack/audio.py index 2f004c4..7dab251 100644 --- a/jigsawstack/audio.py +++ b/jigsawstack/audio.py @@ -133,7 +133,6 @@ async def speech_to_text( ) -> Union[SpeechToTextResponse, SpeechToTextWebhookResponse]: options = options or {} path = "/ai/transcribe" - params = options or {} if isinstance(blob, dict): resp = await AsyncRequest( config=self.config, @@ -147,7 +146,7 @@ async def speech_to_text( resp = await AsyncRequest( config=self.config, path=path, - params=params, + params=options, verb="post", files=files, ).perform_with_content() diff --git a/jigsawstack/embedding.py b/jigsawstack/embedding.py index 203dc23..c091896 100644 --- a/jigsawstack/embedding.py +++ b/jigsawstack/embedding.py @@ -46,7 +46,9 @@ def __init__( @overload def execute(self, params: EmbeddingParams) -> EmbeddingResponse: ... @overload - def execute(self, blob: bytes, options: EmbeddingParams = None) -> EmbeddingResponse: ... + def execute( + self, blob: bytes, options: EmbeddingParams = None + ) -> EmbeddingResponse: ... def execute( self, @@ -69,7 +71,6 @@ def execute( config=self.config, path=path, params=options, - data=blob, files=files, verb="post", ).perform_with_content() @@ -95,7 +96,9 @@ def __init__( @overload async def execute(self, params: EmbeddingParams) -> EmbeddingResponse: ... @overload - async def execute(self, blob: bytes, options: EmbeddingParams = None) -> EmbeddingResponse: ... + async def execute( + self, blob: bytes, options: EmbeddingParams = None + ) -> EmbeddingResponse: ... async def execute( self, @@ -118,7 +121,6 @@ async def execute( config=self.config, path=path, params=options, - data=blob, files=files, verb="post", ).perform_with_content() diff --git a/jigsawstack/embedding_v2.py b/jigsawstack/embedding_v2.py index b9514f9..4447e8c 100644 --- a/jigsawstack/embedding_v2.py +++ b/jigsawstack/embedding_v2.py @@ -44,7 +44,9 @@ def __init__( @overload def execute(self, params: EmbeddingV2Params) -> EmbeddingV2Response: ... @overload - def execute(self, blob: bytes, options: EmbeddingV2Params = None) -> EmbeddingV2Response: ... + def execute( + self, blob: bytes, options: EmbeddingV2Params = None + ) -> EmbeddingV2Response: ... def execute( self, @@ -67,7 +69,6 @@ def execute( config=self.config, path=path, params=options, - data=blob, files=files, verb="post", ).perform_with_content() @@ -118,7 +119,6 @@ async def execute( config=self.config, path=path, params=options, - data=blob, files=files, verb="post", ).perform_with_content() diff --git a/jigsawstack/request.py b/jigsawstack/request.py index 84ef8cf..e824457 100644 --- a/jigsawstack/request.py +++ b/jigsawstack/request.py @@ -28,7 +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, # Change from 'file' to 'files' + files: Union[Dict[str, Any], None] = None, ): self.path = path self.params = params @@ -39,7 +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 # Change from 'file' to 'files' + self.files = files def perform(self) -> Union[T, None]: """Is the main function that makes the HTTP request @@ -93,7 +93,10 @@ def perform_file(self) -> Union[T, None]: # handle error in case there is a statusCode attr present # and status != 200 and response is a json. - if "application/json" not in resp.headers["content-type"] and resp.status_code != 200: + if ( + "application/json" not in resp.headers["content-type"] + and resp.status_code != 200 + ): raise_for_code_and_type( code=500, message="Failed to parse JigsawStack API response. Please try again.", @@ -253,7 +256,7 @@ def make_request(self, url: str) -> requests.Response: params = self.params verb = self.verb data = self.data - files = self.files # Change from 'file' to 'files' + files = self.files _requestParams = None _json = None @@ -262,23 +265,14 @@ def make_request(self, url: str) -> requests.Response: if verb.lower() in ["get", "delete"]: _requestParams = params - elif files: - # For multipart requests + elif files: # multipart request _files = files - # Add params as 'body' field in multipart form (JSON stringified) if params and isinstance(params, dict): - # Convert params to JSON string and add as 'body' field _data = {"body": json.dumps(params)} - elif data: - # For binary data without multipart - _data = data - # Pass params as query parameters for binary uploads - if params and isinstance(params, dict): - _requestParams = params - else: - # For JSON requests - _json = params + headers.pop("Content-Type", None) # let requests set it for multipart + else: # pure JSON request + _json = params try: return requests.request( verb, diff --git a/jigsawstack/translate.py b/jigsawstack/translate.py index 42b50dd..e96e37f 100644 --- a/jigsawstack/translate.py +++ b/jigsawstack/translate.py @@ -83,7 +83,9 @@ def text(self, params: TranslateParams) -> TranslateResponse: return resp @overload - def image(self, params: TranslateImageParams) -> Union[TranslateImageResponse, bytes]: ... + def image( + self, params: TranslateImageParams + ) -> Union[TranslateImageResponse, bytes]: ... @overload def image( self, blob: bytes, options: TranslateImageParams = None @@ -112,7 +114,6 @@ def image( config=self.config, path=path, params=options, - data=blob, files=files, verb="post", ).perform_with_content() @@ -145,7 +146,9 @@ async def text(self, params: TranslateParams) -> TranslateResponse: return resp @overload - async def image(self, params: TranslateImageParams) -> Union[TranslateImageResponse, bytes]: ... + async def image( + self, params: TranslateImageParams + ) -> Union[TranslateImageResponse, bytes]: ... @overload async def image( self, blob: bytes, options: TranslateImageParams = None @@ -172,7 +175,6 @@ async def image( config=self.config, path=path, params=options, - data=blob, files=files, verb="post", ).perform_with_content() diff --git a/jigsawstack/validate.py b/jigsawstack/validate.py index 6ee4040..774aef4 100644 --- a/jigsawstack/validate.py +++ b/jigsawstack/validate.py @@ -117,7 +117,6 @@ def nsfw( config=self.config, path=path, params=options, - data=blob, files=files, verb="post", ).perform_with_content() @@ -204,7 +203,6 @@ async def nsfw( config=self.config, path=path, params=options, - data=blob, files=files, verb="post", ).perform_with_content() diff --git a/jigsawstack/version.py b/jigsawstack/version.py index 44573a9..95b9715 100644 --- a/jigsawstack/version.py +++ b/jigsawstack/version.py @@ -1,4 +1,4 @@ -__version__ = "0.3.3" +__version__ = "0.3.4" def get_version() -> str: diff --git a/jigsawstack/vision.py b/jigsawstack/vision.py index fe1d94a..97e87cb 100644 --- a/jigsawstack/vision.py +++ b/jigsawstack/vision.py @@ -209,14 +209,15 @@ def vocr( config=self.config, path=path, params=options, - data=blob, files=files, verb="post", ).perform_with_content() return resp @overload - def object_detection(self, params: ObjectDetectionParams) -> ObjectDetectionResponse: ... + def object_detection( + self, params: ObjectDetectionParams + ) -> ObjectDetectionResponse: ... @overload def object_detection( self, blob: bytes, options: ObjectDetectionParams = None @@ -242,7 +243,6 @@ def object_detection( config=self.config, path=path, params=options, - data=blob, files=files, verb="post", ).perform_with_content() @@ -291,14 +291,15 @@ async def vocr( config=self.config, path=path, params=options, - data=blob, files=files, verb="post", ).perform_with_content() return resp @overload - async def object_detection(self, params: ObjectDetectionParams) -> ObjectDetectionResponse: ... + async def object_detection( + self, params: ObjectDetectionParams + ) -> ObjectDetectionResponse: ... @overload async def object_detection( self, blob: bytes, options: ObjectDetectionParams = None @@ -327,7 +328,6 @@ async def object_detection( config=self.config, path=path, params=options, - data=blob, files=files, verb="post", ).perform_with_content() From 2127ca15bdc1dcabbe02b1d25a41cc83dc8cd295 Mon Sep 17 00:00:00 2001 From: Khurdhula-Harshavardhan Date: Mon, 15 Sep 2025 14:26:32 -0700 Subject: [PATCH 14/26] chore: ruff formatting. --- jigsawstack/async_request.py | 4 +--- jigsawstack/embedding.py | 8 ++------ jigsawstack/embedding_v2.py | 4 +--- jigsawstack/request.py | 5 +---- jigsawstack/translate.py | 8 ++------ jigsawstack/vision.py | 8 ++------ 6 files changed, 9 insertions(+), 28 deletions(-) diff --git a/jigsawstack/async_request.py b/jigsawstack/async_request.py index dc0c063..a462ae6 100644 --- a/jigsawstack/async_request.py +++ b/jigsawstack/async_request.py @@ -256,9 +256,7 @@ async def make_request( _form_data = aiohttp.FormData() _form_data.add_field("file", BytesIO(files["file"]), filename="upload") if params and isinstance(params, dict): - _form_data.add_field( - "body", json.dumps(params), content_type="application/json" - ) + _form_data.add_field("body", json.dumps(params), content_type="application/json") headers.pop("Content-Type", None) else: # pure JSON request diff --git a/jigsawstack/embedding.py b/jigsawstack/embedding.py index c091896..9611537 100644 --- a/jigsawstack/embedding.py +++ b/jigsawstack/embedding.py @@ -46,9 +46,7 @@ def __init__( @overload def execute(self, params: EmbeddingParams) -> EmbeddingResponse: ... @overload - def execute( - self, blob: bytes, options: EmbeddingParams = None - ) -> EmbeddingResponse: ... + def execute(self, blob: bytes, options: EmbeddingParams = None) -> EmbeddingResponse: ... def execute( self, @@ -96,9 +94,7 @@ def __init__( @overload async def execute(self, params: EmbeddingParams) -> EmbeddingResponse: ... @overload - async def execute( - self, blob: bytes, options: EmbeddingParams = None - ) -> EmbeddingResponse: ... + async def execute(self, blob: bytes, options: EmbeddingParams = None) -> EmbeddingResponse: ... async def execute( self, diff --git a/jigsawstack/embedding_v2.py b/jigsawstack/embedding_v2.py index 4447e8c..0192f52 100644 --- a/jigsawstack/embedding_v2.py +++ b/jigsawstack/embedding_v2.py @@ -44,9 +44,7 @@ def __init__( @overload def execute(self, params: EmbeddingV2Params) -> EmbeddingV2Response: ... @overload - def execute( - self, blob: bytes, options: EmbeddingV2Params = None - ) -> EmbeddingV2Response: ... + def execute(self, blob: bytes, options: EmbeddingV2Params = None) -> EmbeddingV2Response: ... def execute( self, diff --git a/jigsawstack/request.py b/jigsawstack/request.py index e824457..038c540 100644 --- a/jigsawstack/request.py +++ b/jigsawstack/request.py @@ -93,10 +93,7 @@ def perform_file(self) -> Union[T, None]: # handle error in case there is a statusCode attr present # and status != 200 and response is a json. - if ( - "application/json" not in resp.headers["content-type"] - and resp.status_code != 200 - ): + if "application/json" not in resp.headers["content-type"] and resp.status_code != 200: raise_for_code_and_type( code=500, message="Failed to parse JigsawStack API response. Please try again.", diff --git a/jigsawstack/translate.py b/jigsawstack/translate.py index e96e37f..601c2a1 100644 --- a/jigsawstack/translate.py +++ b/jigsawstack/translate.py @@ -83,9 +83,7 @@ def text(self, params: TranslateParams) -> TranslateResponse: return resp @overload - def image( - self, params: TranslateImageParams - ) -> Union[TranslateImageResponse, bytes]: ... + def image(self, params: TranslateImageParams) -> Union[TranslateImageResponse, bytes]: ... @overload def image( self, blob: bytes, options: TranslateImageParams = None @@ -146,9 +144,7 @@ async def text(self, params: TranslateParams) -> TranslateResponse: return resp @overload - async def image( - self, params: TranslateImageParams - ) -> Union[TranslateImageResponse, bytes]: ... + async def image(self, params: TranslateImageParams) -> Union[TranslateImageResponse, bytes]: ... @overload async def image( self, blob: bytes, options: TranslateImageParams = None diff --git a/jigsawstack/vision.py b/jigsawstack/vision.py index 97e87cb..793841a 100644 --- a/jigsawstack/vision.py +++ b/jigsawstack/vision.py @@ -215,9 +215,7 @@ def vocr( return resp @overload - def object_detection( - self, params: ObjectDetectionParams - ) -> ObjectDetectionResponse: ... + def object_detection(self, params: ObjectDetectionParams) -> ObjectDetectionResponse: ... @overload def object_detection( self, blob: bytes, options: ObjectDetectionParams = None @@ -297,9 +295,7 @@ async def vocr( return resp @overload - async def object_detection( - self, params: ObjectDetectionParams - ) -> ObjectDetectionResponse: ... + async def object_detection(self, params: ObjectDetectionParams) -> ObjectDetectionResponse: ... @overload async def object_detection( self, blob: bytes, options: ObjectDetectionParams = None From 563f65e0bb50906b286ce456996394ae92c166b6 Mon Sep 17 00:00:00 2001 From: Khurdhula-Harshavardhan Date: Mon, 15 Sep 2025 14:26:47 -0700 Subject: [PATCH 15/26] feat: update version to 0.3.4 --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index bfb1aff..1aebb49 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ setup( name="jigsawstack", - version="0.3.3", + version="0.3.4", description="JigsawStack - The AI SDK for Python", long_description=open("README.md", encoding="utf8").read(), long_description_content_type="text/markdown", @@ -19,7 +19,7 @@ python_requires=">=3.7", keywords=["AI", "AI Tooling"], setup_requires=["pytest-runner"], - tests_require=["pytest"], + tests_require=["pytest", "pytest-asyncio"], test_suite="tests", classifiers=[ "Development Status :: 4 - Beta", From 777706d088d8d810bc419b529fae3128eae5611d Mon Sep 17 00:00:00 2001 From: Khurdhula-Harshavardhan Date: Mon, 15 Sep 2025 14:27:35 -0700 Subject: [PATCH 16/26] feat: ruff formatting. --- tests/test_audio.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/tests/test_audio.py b/tests/test_audio.py index 037f285..309b191 100644 --- a/tests/test_audio.py +++ b/tests/test_audio.py @@ -15,7 +15,6 @@ jigsaw = jigsawstack.JigsawStack(api_key=os.getenv("JIGSAWSTACK_API_KEY")) async_jigsaw = jigsawstack.AsyncJigsawStack(api_key=os.getenv("JIGSAWSTACK_API_KEY")) -# Sample audio URLs for testing AUDIO_URL = AUDIO_URL_LONG = "https://jigsawstack.com/preview/stt-example.wav" @@ -98,7 +97,10 @@ WEBHOOK_TEST_CASES = [ { "name": "with_webhook_url", - "params": {"url": AUDIO_URL, "webhook_url": "https://webhook.site/test-webhook"}, + "params": { + "url": AUDIO_URL, + "webhook_url": "https://webhook.site/test-webhook", + }, "blob": None, "options": None, }, @@ -106,7 +108,10 @@ "name": "with_blob_and_webhook", "params": None, "blob": AUDIO_URL, - "options": {"webhook_url": "https://webhook.site/test-webhook", "language": "en"}, + "options": { + "webhook_url": "https://webhook.site/test-webhook", + "language": "en", + }, }, ] From f97b5598d92828be9472a07c24dcde5f36ef458b Mon Sep 17 00:00:00 2001 From: Khurdhula-Harshavardhan Date: Mon, 15 Sep 2025 14:29:01 -0700 Subject: [PATCH 17/26] fix: drop unused param. --- jigsawstack/request.py | 1 - 1 file changed, 1 deletion(-) diff --git a/jigsawstack/request.py b/jigsawstack/request.py index 038c540..fddd5c3 100644 --- a/jigsawstack/request.py +++ b/jigsawstack/request.py @@ -252,7 +252,6 @@ def make_request(self, url: str) -> requests.Response: headers = self.__get_headers() params = self.params verb = self.verb - data = self.data files = self.files _requestParams = None From 50d8e5d2137d6d98df28119d3741c1317cafd90d Mon Sep 17 00:00:00 2001 From: Khurdhula-Harshavardhan Date: Mon, 15 Sep 2025 15:12:25 -0700 Subject: [PATCH 18/26] fix: updating properties for JigsawStack & AsyncJigsawStack, and embedding_v2 naming convetion. --- jigsawstack/__init__.py | 60 ++++++++++++++++++++++++++++------------- 1 file changed, 41 insertions(+), 19 deletions(-) diff --git a/jigsawstack/__init__.py b/jigsawstack/__init__.py index 091f775..5af7ca4 100644 --- a/jigsawstack/__init__.py +++ b/jigsawstack/__init__.py @@ -21,24 +21,29 @@ class JigsawStack: - audio: Audio - vision: Vision - image_generation: ImageGeneration - file: Store - web: Web - search: Search - classification: Classification - prompt_engine: PromptEngine api_key: str api_url: str headers: Dict[str, str] - # disable_request_logging: bool + audio: Audio + classification: Classification + embedding: Embedding + embedding_v2: EmbeddingV2 + store: Store + image_generation: ImageGeneration + prediction: Prediction + prompt_engine: PromptEngine + sentiment: Sentiment + summary: Summary + text_to_sql: SQL + translate: Translate + validate: Validate + vision: Vision + web: Web def __init__( self, api_key: Union[str, None] = None, api_url: Union[str, None] = None, - # disable_request_logging: Union[bool, None] = None, headers: Union[Dict[str, str], None] = None, ) -> None: if api_key is None: @@ -66,16 +71,19 @@ def __init__( api_url=api_url + "/v1", disable_request_logging=disable_request_logging, ) + self.web = Web( api_key=api_key, api_url=api_url + "/v1", disable_request_logging=disable_request_logging, ) + self.sentiment = Sentiment( api_key=api_key, api_url=api_url + "/v1", disable_request_logging=disable_request_logging, ).analyze + self.validate = Validate( api_key=api_key, api_url=api_url + "/v1", @@ -86,21 +94,25 @@ def __init__( api_url=api_url + "/v1", disable_request_logging=disable_request_logging, ).summarize + self.vision = Vision( api_key=api_key, api_url=api_url + "/v1", disable_request_logging=disable_request_logging, ) + self.prediction = Prediction( api_key=api_key, api_url=api_url + "/v1", disable_request_logging=disable_request_logging, ).predict + self.text_to_sql = SQL( api_key=api_key, api_url=api_url + "/v1", disable_request_logging=disable_request_logging, ).text_to_sql + self.store = Store( api_key=api_key, api_url=api_url + "/v1", @@ -118,7 +130,7 @@ def __init__( disable_request_logging=disable_request_logging, ).execute - self.embeddingV2 = EmbeddingV2( + self.embedding_v2 = EmbeddingV2( api_key=api_key, api_url=api_url + "/v2", disable_request_logging=disable_request_logging, @@ -144,16 +156,24 @@ def __init__( class AsyncJigsawStack: - validate: AsyncValidate - web: AsyncWeb + api_key: str + api_url: str + headers: Dict[str, str] audio: AsyncAudio - vision: AsyncVision + classification: AsyncClassification + embedding: AsyncEmbedding + embedding_v2: AsyncEmbeddingV2 image_generation: AsyncImageGeneration - store: AsyncStore + prediction: AsyncPrediction prompt_engine: AsyncPromptEngine - api_key: str - api_url: str - disable_request_logging: bool + sentiment: AsyncSentiment + store: AsyncStore + summary: AsyncSummary + text_to_sql: AsyncSQL + translate: AsyncTranslate + validate: AsyncValidate + vision: AsyncVision + web: AsyncWeb def __init__( self, @@ -176,6 +196,7 @@ def __init__( self.api_key = api_key self.api_url = api_url + disable_request_logging = self.headers.get("x-jigsaw-no-request-log") self.web = AsyncWeb( api_key=api_key, @@ -217,6 +238,7 @@ def __init__( api_url=api_url + "/v1", disable_request_logging=disable_request_logging, ).predict + self.text_to_sql = AsyncSQL( api_key=api_key, api_url=api_url + "/v1", @@ -241,7 +263,7 @@ def __init__( disable_request_logging=disable_request_logging, ).execute - self.embeddingV2 = AsyncEmbeddingV2( + self.embedding_v2 = AsyncEmbeddingV2( api_key=api_key, api_url=api_url + "/v2", disable_request_logging=disable_request_logging, From 8fa4631332bfd9edc979ba32cf98ed380387b32b Mon Sep 17 00:00:00 2001 From: Khurdhula-Harshavardhan Date: Mon, 15 Sep 2025 17:11:13 -0700 Subject: [PATCH 19/26] chore: drop redundant disable logging flag. --- jigsawstack/__init__.py | 228 +++++++++-------------------------- jigsawstack/async_request.py | 1 - tests/test_embedding.py | 8 +- 3 files changed, 59 insertions(+), 178 deletions(-) diff --git a/jigsawstack/__init__.py b/jigsawstack/__init__.py index 5af7ca4..537d6a1 100644 --- a/jigsawstack/__init__.py +++ b/jigsawstack/__init__.py @@ -62,97 +62,37 @@ def __init__( self.api_key = api_key self.api_url = api_url - self.headers = headers or {} - - disable_request_logging = self.headers.get("x-jigsaw-no-request-log") - - self.audio = Audio( - api_key=api_key, - api_url=api_url + "/v1", - disable_request_logging=disable_request_logging, - ) - - self.web = Web( - api_key=api_key, - api_url=api_url + "/v1", - disable_request_logging=disable_request_logging, - ) - - self.sentiment = Sentiment( - api_key=api_key, - api_url=api_url + "/v1", - disable_request_logging=disable_request_logging, - ).analyze - - self.validate = Validate( - api_key=api_key, - api_url=api_url + "/v1", - disable_request_logging=disable_request_logging, - ) - self.summary = Summary( - api_key=api_key, - api_url=api_url + "/v1", - disable_request_logging=disable_request_logging, - ).summarize - - self.vision = Vision( - api_key=api_key, - api_url=api_url + "/v1", - disable_request_logging=disable_request_logging, - ) - - self.prediction = Prediction( - api_key=api_key, - api_url=api_url + "/v1", - disable_request_logging=disable_request_logging, - ).predict - - self.text_to_sql = SQL( - api_key=api_key, - api_url=api_url + "/v1", - disable_request_logging=disable_request_logging, - ).text_to_sql - - self.store = Store( - api_key=api_key, - api_url=api_url + "/v1", - disable_request_logging=disable_request_logging, - ) - self.translate = Translate( - api_key=api_key, - api_url=api_url + "/v1", - disable_request_logging=disable_request_logging, - ) - - self.embedding = Embedding( - api_key=api_key, - api_url=api_url + "/v1", - disable_request_logging=disable_request_logging, - ).execute - - self.embedding_v2 = EmbeddingV2( - api_key=api_key, - api_url=api_url + "/v2", - disable_request_logging=disable_request_logging, - ).execute + self.headers = headers or {"Content-Type": "application/json"} + + self.audio = Audio(api_key=api_key, api_url=api_url + "/v1") + + self.web = Web(api_key=api_key, api_url=api_url + "/v1") + + self.sentiment = Sentiment(api_key=api_key, api_url=api_url + "/v1").analyze + + self.validate = Validate(api_key=api_key, api_url=api_url + "/v1") + self.summary = Summary(api_key=api_key, api_url=api_url + "/v1").summarize + + self.vision = Vision(api_key=api_key, api_url=api_url + "/v1") + + self.prediction = Prediction(api_key=api_key, api_url=api_url + "/v1").predict + + self.text_to_sql = SQL(api_key=api_key, api_url=api_url + "/v1").text_to_sql + + self.store = Store(api_key=api_key, api_url=api_url + "/v1") + self.translate = Translate(api_key=api_key, api_url=api_url + "/v1") + + self.embedding = Embedding(api_key=api_key, api_url=api_url + "/v1").execute + + self.embedding_v2 = EmbeddingV2(api_key=api_key, api_url=api_url + "/v2").execute self.image_generation = ImageGeneration( - api_key=api_key, - api_url=api_url + "/v1", - disable_request_logging=disable_request_logging, + api_key=api_key, api_url=api_url + "/v1" ).image_generation - self.classification = Classification( - api_key=api_key, - api_url=api_url + "/v1", - disable_request_logging=disable_request_logging, - ).classify + self.classification = Classification(api_key=api_key, api_url=api_url + "/v1").classify - self.prompt_engine = PromptEngine( - api_key=api_key, - api_url=api_url + "/v1", - disable_request_logging=disable_request_logging, - ) + self.prompt_engine = PromptEngine(api_key=api_key, api_url=api_url + "/v1") class AsyncJigsawStack: @@ -179,7 +119,7 @@ def __init__( self, api_key: Union[str, None] = None, api_url: Union[str, None] = None, - disable_request_logging: Union[bool, None] = None, + headers: Union[Dict[str, str], None] = None, ) -> None: if api_key is None: api_key = os.environ.get("JIGSAWSTACK_API_KEY") @@ -196,96 +136,38 @@ def __init__( self.api_key = api_key self.api_url = api_url - disable_request_logging = self.headers.get("x-jigsaw-no-request-log") - - self.web = AsyncWeb( - api_key=api_key, - api_url=api_url + "/v1", - disable_request_logging=disable_request_logging, - ) - - self.validate = AsyncValidate( - api_key=api_key, - api_url=api_url + "/v1", - disable_request_logging=disable_request_logging, - ) - self.audio = AsyncAudio( - api_key=api_key, - api_url=api_url + "/v1", - disable_request_logging=disable_request_logging, - ) - - self.vision = AsyncVision( - api_key=api_key, - api_url=api_url + "/v1", - disable_request_logging=disable_request_logging, - ) - - self.store = AsyncStore( - api_key=api_key, - api_url=api_url + "/v1", - disable_request_logging=disable_request_logging, - ) - - self.summary = AsyncSummary( - api_key=api_key, - api_url=api_url + "/v1", - disable_request_logging=disable_request_logging, - ).summarize - - self.prediction = AsyncPrediction( - api_key=api_key, - api_url=api_url + "/v1", - disable_request_logging=disable_request_logging, - ).predict - - self.text_to_sql = AsyncSQL( - api_key=api_key, - api_url=api_url + "/v1", - disable_request_logging=disable_request_logging, - ).text_to_sql - - self.sentiment = AsyncSentiment( - api_key=api_key, - api_url=api_url + "/v1", - disable_request_logging=disable_request_logging, - ).analyze - - self.translate = AsyncTranslate( - api_key=api_key, - api_url=api_url + "/v1", - disable_request_logging=disable_request_logging, - ) - - self.embedding = AsyncEmbedding( - api_key=api_key, - api_url=api_url + "/v1", - disable_request_logging=disable_request_logging, - ).execute - - self.embedding_v2 = AsyncEmbeddingV2( - api_key=api_key, - api_url=api_url + "/v2", - disable_request_logging=disable_request_logging, - ).execute + self.headers = headers or {"Content-Type": "application/json"} + + self.web = AsyncWeb(api_key=api_key, api_url=api_url + "/v1") + + self.validate = AsyncValidate(api_key=api_key, api_url=api_url + "/v1") + self.audio = AsyncAudio(api_key=api_key, api_url=api_url + "/v1") + + self.vision = AsyncVision(api_key=api_key, api_url=api_url + "/v1") + + self.store = AsyncStore(api_key=api_key, api_url=api_url + "/v1") + + self.summary = AsyncSummary(api_key=api_key, api_url=api_url + "/v1").summarize + + self.prediction = AsyncPrediction(api_key=api_key, api_url=api_url + "/v1").predict + + self.text_to_sql = AsyncSQL(api_key=api_key, api_url=api_url + "/v1").text_to_sql + + self.sentiment = AsyncSentiment(api_key=api_key, api_url=api_url + "/v1").analyze + + self.translate = AsyncTranslate(api_key=api_key, api_url=api_url + "/v1") + + self.embedding = AsyncEmbedding(api_key=api_key, api_url=api_url + "/v1").execute + + self.embedding_v2 = AsyncEmbeddingV2(api_key=api_key, api_url=api_url + "/v2").execute self.image_generation = AsyncImageGeneration( - api_key=api_key, - api_url=api_url + "/v1", - disable_request_logging=disable_request_logging, + api_key=api_key, api_url=api_url + "/v1" ).image_generation - self.classification = AsyncClassification( - api_key=api_key, - api_url=api_url + "/v1", - disable_request_logging=disable_request_logging, - ).classify - - self.prompt_engine = AsyncPromptEngine( - api_key=api_key, - api_url=api_url + "/v1", - disable_request_logging=disable_request_logging, - ) + self.classification = AsyncClassification(api_key=api_key, api_url=api_url + "/v1").classify + + self.prompt_engine = AsyncPromptEngine(api_key=api_key, api_url=api_url + "/v1") # Create a global instance of the Web class diff --git a/jigsawstack/async_request.py b/jigsawstack/async_request.py index a462ae6..0d44929 100644 --- a/jigsawstack/async_request.py +++ b/jigsawstack/async_request.py @@ -37,7 +37,6 @@ def __init__( self.api_key = config.get("api_key") self.data = data 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 diff --git a/tests/test_embedding.py b/tests/test_embedding.py index 7b6b368..c2bc59d 100644 --- a/tests/test_embedding.py +++ b/tests/test_embedding.py @@ -246,7 +246,7 @@ class TestEmbeddingV2Sync: def test_embedding_v2(self, test_case): """Test synchronous embedding v2 with various inputs""" try: - result = jigsaw.embeddingV2(test_case["params"]) + result = jigsaw.embedding_v2(test_case["params"]) assert result["success"] assert "embeddings" in result assert isinstance(result["embeddings"], list) @@ -271,7 +271,7 @@ def test_embedding_v2_blob(self, test_case): try: # Download blob content blob_content = requests.get(test_case["blob_url"]).content - result = jigsaw.embeddingV2(blob_content, test_case["options"]) + result = jigsaw.embedding_v2(blob_content, test_case["options"]) assert result["success"] assert "embeddings" in result assert isinstance(result["embeddings"], list) @@ -291,7 +291,7 @@ class TestEmbeddingV2Async: async def test_embedding_v2_async(self, test_case): """Test asynchronous embedding v2 with various inputs""" try: - result = await async_jigsaw.embeddingV2(test_case["params"]) + result = await async_jigsaw.embedding_v2(test_case["params"]) assert result["success"] assert "embeddings" in result assert isinstance(result["embeddings"], list) @@ -317,7 +317,7 @@ async def test_embedding_v2_blob_async(self, test_case): try: # Download blob content blob_content = requests.get(test_case["blob_url"]).content - result = await async_jigsaw.embeddingV2(blob_content, test_case["options"]) + result = await async_jigsaw.embedding_v2(blob_content, test_case["options"]) assert result["success"] assert "embeddings" in result assert isinstance(result["embeddings"], list) From 0c3aa6052792a32f33a12d563f3090d826854653 Mon Sep 17 00:00:00 2001 From: Khurdhula-Harshavardhan Date: Mon, 15 Sep 2025 17:33:37 -0700 Subject: [PATCH 20/26] fix: pass user defined headers as config. --- jigsawstack/async_request.py | 5 +--- jigsawstack/audio.py | 16 +++++------ jigsawstack/classification.py | 12 ++++----- jigsawstack/embedding.py | 12 ++++----- jigsawstack/embedding_v2.py | 12 ++++----- jigsawstack/image_generation.py | 12 ++++----- jigsawstack/prediction.py | 12 ++++----- jigsawstack/prompt_engine.py | 6 ++--- jigsawstack/request.py | 6 +---- jigsawstack/search.py | 12 ++++----- jigsawstack/sentiment.py | 12 ++++----- jigsawstack/sql.py | 12 ++++----- jigsawstack/store.py | 14 +++++----- jigsawstack/summary.py | 12 ++++----- jigsawstack/translate.py | 12 ++++----- jigsawstack/validate.py | 12 ++++----- jigsawstack/vision.py | 15 ++++++----- jigsawstack/web.py | 48 +++++++++------------------------ 18 files changed, 105 insertions(+), 137 deletions(-) diff --git a/jigsawstack/async_request.py b/jigsawstack/async_request.py index 0d44929..028e107 100644 --- a/jigsawstack/async_request.py +++ b/jigsawstack/async_request.py @@ -15,7 +15,7 @@ class AsyncRequestConfig(TypedDict): api_url: str api_key: str - disable_request_logging: Union[bool, None] = False + headers: Union[Dict[str, str], None] class AsyncRequest(Generic[T]): @@ -180,9 +180,6 @@ def __get_headers(self) -> Dict[str, str]: 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 diff --git a/jigsawstack/audio.py b/jigsawstack/audio.py index 7dab251..589f4d2 100644 --- a/jigsawstack/audio.py +++ b/jigsawstack/audio.py @@ -55,14 +55,10 @@ def __init__( self, api_key: str, api_url: str, - disable_request_logging: Union[bool, None] = False, + headers: Union[Dict[str, str], None] = None, ): - super().__init__(api_key, api_url, disable_request_logging) - self.config = RequestConfig( - api_url=api_url, - api_key=api_key, - disable_request_logging=disable_request_logging, - ) + super().__init__(api_key, api_url, headers) + self.config = RequestConfig(api_url=api_url, api_key=api_key, headers=headers) @overload def speech_to_text( @@ -108,13 +104,13 @@ def __init__( self, api_key: str, api_url: str, - disable_request_logging: Union[bool, None] = False, + headers: Union[Dict[str, str], None] = None, ): - super().__init__(api_key, api_url, disable_request_logging) + super().__init__(api_key, api_url, headers) self.config = AsyncRequestConfig( api_url=api_url, api_key=api_key, - disable_request_logging=disable_request_logging, + headers=headers, ) @overload diff --git a/jigsawstack/classification.py b/jigsawstack/classification.py index 45407e9..1d9770d 100644 --- a/jigsawstack/classification.py +++ b/jigsawstack/classification.py @@ -68,13 +68,13 @@ def __init__( self, api_key: str, api_url: str, - disable_request_logging: Union[bool, None] = False, + headers: Union[Dict[str, str], None] = None, ): - super().__init__(api_key, api_url, disable_request_logging) + super().__init__(api_key, api_url, headers) self.config = RequestConfig( api_url=api_url, api_key=api_key, - disable_request_logging=disable_request_logging, + headers=headers, ) def classify(self, params: ClassificationParams) -> ClassificationResponse: @@ -95,13 +95,13 @@ def __init__( self, api_key: str, api_url: str, - disable_request_logging: Union[bool, None] = False, + headers: Union[Dict[str, str], None] = None, ): - super().__init__(api_key, api_url, disable_request_logging) + super().__init__(api_key, api_url, headers) self.config = AsyncRequestConfig( api_url=api_url, api_key=api_key, - disable_request_logging=disable_request_logging, + headers=headers, ) async def classify(self, params: ClassificationParams) -> ClassificationResponse: diff --git a/jigsawstack/embedding.py b/jigsawstack/embedding.py index 9611537..71453ec 100644 --- a/jigsawstack/embedding.py +++ b/jigsawstack/embedding.py @@ -34,13 +34,13 @@ def __init__( self, api_key: str, api_url: str, - disable_request_logging: Union[bool, None] = False, + headers: Union[Dict[str, str], None] = None, ): - super().__init__(api_key, api_url, disable_request_logging) + super().__init__(api_key, api_url, headers) self.config = RequestConfig( api_url=api_url, api_key=api_key, - disable_request_logging=disable_request_logging, + headers=headers, ) @overload @@ -82,13 +82,13 @@ def __init__( self, api_key: str, api_url: str, - disable_request_logging: Union[bool, None] = False, + headers: Union[Dict[str, str], None] = None, ): - super().__init__(api_key, api_url, disable_request_logging) + super().__init__(api_key, api_url, headers) self.config = RequestConfig( api_url=api_url, api_key=api_key, - disable_request_logging=disable_request_logging, + headers=headers, ) @overload diff --git a/jigsawstack/embedding_v2.py b/jigsawstack/embedding_v2.py index 0192f52..8148df1 100644 --- a/jigsawstack/embedding_v2.py +++ b/jigsawstack/embedding_v2.py @@ -32,13 +32,13 @@ def __init__( self, api_key: str, api_url: str, - disable_request_logging: Union[bool, None] = False, + headers: Union[Dict[str, str], None] = None, ): - super().__init__(api_key, api_url, disable_request_logging) + super().__init__(api_key, api_url, headers) self.config = RequestConfig( api_url=api_url, api_key=api_key, - disable_request_logging=disable_request_logging, + headers=headers, ) @overload @@ -80,13 +80,13 @@ def __init__( self, api_key: str, api_url: str, - disable_request_logging: Union[bool, None] = False, + headers: Union[Dict[str, str], None] = None, ): - super().__init__(api_key, api_url, disable_request_logging) + super().__init__(api_key, api_url, headers) self.config = RequestConfig( api_url=api_url, api_key=api_key, - disable_request_logging=disable_request_logging, + headers=headers, ) @overload diff --git a/jigsawstack/image_generation.py b/jigsawstack/image_generation.py index 9584cf3..40d0e81 100644 --- a/jigsawstack/image_generation.py +++ b/jigsawstack/image_generation.py @@ -90,13 +90,13 @@ def __init__( self, api_key: str, api_url: str, - disable_request_logging: Union[bool, None] = False, + headers: Union[Dict[str, str], None] = None, ): - super().__init__(api_key, api_url, disable_request_logging=disable_request_logging) + super().__init__(api_key, api_url, headers) self.config = RequestConfig( api_url=api_url, api_key=api_key, - disable_request_logging=disable_request_logging, + headers=headers, ) def image_generation( @@ -119,13 +119,13 @@ def __init__( self, api_key: str, api_url: str, - disable_request_logging: Union[bool, None] = False, + headers: Union[Dict[str, str], None] = None, ): - super().__init__(api_key, api_url, disable_request_logging=disable_request_logging) + super().__init__(api_key, api_url, headers) self.config = RequestConfig( api_url=api_url, api_key=api_key, - disable_request_logging=disable_request_logging, + headers=headers, ) async def image_generation( diff --git a/jigsawstack/prediction.py b/jigsawstack/prediction.py index ec571a4..eeee9a6 100644 --- a/jigsawstack/prediction.py +++ b/jigsawstack/prediction.py @@ -49,13 +49,13 @@ def __init__( self, api_key: str, api_url: str, - disable_request_logging: Union[bool, None] = False, + headers: Union[Dict[str, str], None] = None, ): - super().__init__(api_key, api_url, disable_request_logging) + super().__init__(api_key, api_url, headers) self.config = RequestConfig( api_url=api_url, api_key=api_key, - disable_request_logging=disable_request_logging, + headers=headers, ) def predict(self, params: PredictionParams) -> PredictionResponse: @@ -76,13 +76,13 @@ def __init__( self, api_key: str, api_url: str, - disable_request_logging: Union[bool, None] = False, + headers: Union[Dict[str, str], None] = None, ): - super().__init__(api_key, api_url, disable_request_logging) + super().__init__(api_key, api_url, headers) self.config = RequestConfig( api_url=api_url, api_key=api_key, - disable_request_logging=disable_request_logging, + headers=headers, ) async def predict(self, params: PredictionParams) -> PredictionResponse: diff --git a/jigsawstack/prompt_engine.py b/jigsawstack/prompt_engine.py index 3af7fa3..1c2420b 100644 --- a/jigsawstack/prompt_engine.py +++ b/jigsawstack/prompt_engine.py @@ -98,13 +98,13 @@ def __init__( self, api_key: str, api_url: str, - disable_request_logging: Union[bool, None] = False, + headers: Union[Dict[str, str], None] = None, ): - super().__init__(api_key, api_url, disable_request_logging) + super().__init__(api_key, api_url, headers) self.config = RequestConfig( api_url=api_url, api_key=api_key, - disable_request_logging=disable_request_logging, + headers=headers, ) def create(self, params: PromptEngineCreateParams) -> PromptEngineCreateResponse: diff --git a/jigsawstack/request.py b/jigsawstack/request.py index fddd5c3..26fc710 100644 --- a/jigsawstack/request.py +++ b/jigsawstack/request.py @@ -14,7 +14,7 @@ class RequestConfig(TypedDict): api_url: str api_key: str - disable_request_logging: Union[bool, None] = False + headers: Union[Dict[str, str], None] # This class wraps the HTTP request creation logic @@ -37,7 +37,6 @@ def __init__( self.api_key = config.get("api_key") self.data = data self.headers = headers or {"Content-Type": "application/json"} - self.disable_request_logging = config.get("disable_request_logging") self.stream = stream self.files = files @@ -162,9 +161,6 @@ def __get_headers(self) -> Dict[Any, Any]: 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 diff --git a/jigsawstack/search.py b/jigsawstack/search.py index 21b0187..c1fe804 100644 --- a/jigsawstack/search.py +++ b/jigsawstack/search.py @@ -226,13 +226,13 @@ def __init__( self, api_key: str, api_url: str, - disable_request_logging: Union[bool, None] = False, + headers: Union[Dict[str, str], None] = None, ): - super().__init__(api_key, api_url, disable_request_logging) + super().__init__(api_key, api_url, headers) self.config = RequestConfig( api_url=api_url, api_key=api_key, - disable_request_logging=disable_request_logging, + headers=headers, ) def search(self, params: SearchParams) -> SearchResponse: @@ -288,13 +288,13 @@ def __init__( self, api_key: str, api_url: str, - disable_request_logging: Union[bool, None] = False, + headers: Union[Dict[str, str], None] = None, ): - super().__init__(api_key, api_url, disable_request_logging) + super().__init__(api_key, api_url, headers) self.config = AsyncRequestConfig( api_url=api_url, api_key=api_key, - disable_request_logging=disable_request_logging, + headers=headers, ) async def search(self, params: SearchParams) -> SearchResponse: diff --git a/jigsawstack/sentiment.py b/jigsawstack/sentiment.py index ef5e9df..db1031a 100644 --- a/jigsawstack/sentiment.py +++ b/jigsawstack/sentiment.py @@ -49,13 +49,13 @@ def __init__( self, api_key: str, api_url: str, - disable_request_logging: Union[bool, None] = False, + headers: Union[Dict[str, str], None] = None, ): - super().__init__(api_key, api_url, disable_request_logging) + super().__init__(api_key, api_url, headers) self.config = RequestConfig( api_url=api_url, api_key=api_key, - disable_request_logging=disable_request_logging, + headers=headers, ) def analyze(self, params: SentimentParams) -> SentimentResponse: @@ -76,13 +76,13 @@ def __init__( self, api_key: str, api_url: str, - disable_request_logging: Union[bool, None] = False, + headers: Union[Dict[str, str], None] = None, ): - super().__init__(api_key, api_url, disable_request_logging) + super().__init__(api_key, api_url, headers) self.config = RequestConfig( api_url=api_url, api_key=api_key, - disable_request_logging=disable_request_logging, + headers=headers, ) async def analyze(self, params: SentimentParams) -> SentimentResponse: diff --git a/jigsawstack/sql.py b/jigsawstack/sql.py index b895485..c74f2a7 100644 --- a/jigsawstack/sql.py +++ b/jigsawstack/sql.py @@ -44,13 +44,13 @@ def __init__( self, api_key: str, api_url: str, - disable_request_logging: Union[bool, None] = False, + headers: Union[Dict[str, str], None] = None, ): - super().__init__(api_key, api_url, disable_request_logging) + super().__init__(api_key, api_url, headers) self.config = RequestConfig( api_url=api_url, api_key=api_key, - disable_request_logging=disable_request_logging, + headers=headers, ) def text_to_sql(self, params: SQLParams) -> SQLResponse: @@ -71,13 +71,13 @@ def __init__( self, api_key: str, api_url: str, - disable_request_logging: Union[bool, None] = False, + headers: Union[Dict[str, str], None] = None, ): - super().__init__(api_key, api_url, disable_request_logging) + super().__init__(api_key, api_url, headers) self.config = RequestConfig( api_url=api_url, api_key=api_key, - disable_request_logging=disable_request_logging, + headers=headers, ) async def text_to_sql(self, params: SQLParams) -> SQLResponse: diff --git a/jigsawstack/store.py b/jigsawstack/store.py index 0693f49..4dd6918 100644 --- a/jigsawstack/store.py +++ b/jigsawstack/store.py @@ -1,4 +1,4 @@ -from typing import Any, Union +from typing import Any, Dict, Union from typing_extensions import NotRequired, TypedDict @@ -33,13 +33,13 @@ def __init__( self, api_key: str, api_url: str, - disable_request_logging: Union[bool, None] = False, + headers: Union[Dict[str, str], None] = None, ): - super().__init__(api_key, api_url, disable_request_logging) + super().__init__(api_key, api_url, headers) self.config = RequestConfig( api_url=api_url, api_key=api_key, - disable_request_logging=disable_request_logging, + headers=headers, ) def upload( @@ -91,13 +91,13 @@ def __init__( self, api_key: str, api_url: str, - disable_request_logging: Union[bool, None] = False, + headers: Union[Dict[str, str], None] = None, ): - super().__init__(api_key, api_url, disable_request_logging) + super().__init__(api_key, api_url, headers) self.config = AsyncRequestConfig( api_url=api_url, api_key=api_key, - disable_request_logging=disable_request_logging, + headers=headers, ) async def upload( diff --git a/jigsawstack/summary.py b/jigsawstack/summary.py index 0d19b39..8bbc0c3 100644 --- a/jigsawstack/summary.py +++ b/jigsawstack/summary.py @@ -54,13 +54,13 @@ def __init__( self, api_key: str, api_url: str, - disable_request_logging: Union[bool, None] = False, + headers: Union[Dict[str, str], None] = None, ): - super().__init__(api_key, api_url, disable_request_logging) + super().__init__(api_key, api_url, headers) self.config = RequestConfig( api_url=api_url, api_key=api_key, - disable_request_logging=disable_request_logging, + headers=headers, ) def summarize(self, params: SummaryParams) -> SummaryResponse: @@ -81,13 +81,13 @@ def __init__( self, api_key: str, api_url: str, - disable_request_logging: Union[bool, None] = False, + headers: Union[Dict[str, str], None] = None, ): - super().__init__(api_key, api_url, disable_request_logging) + super().__init__(api_key, api_url, headers) self.config = RequestConfig( api_url=api_url, api_key=api_key, - disable_request_logging=disable_request_logging, + headers=headers, ) async def summarize(self, params: SummaryParams) -> SummaryResponse: diff --git a/jigsawstack/translate.py b/jigsawstack/translate.py index 601c2a1..ebffcfb 100644 --- a/jigsawstack/translate.py +++ b/jigsawstack/translate.py @@ -64,13 +64,13 @@ def __init__( self, api_key: str, api_url: str, - disable_request_logging: Union[bool, None] = False, + headers: Union[Dict[str, str], None] = None, ): - super().__init__(api_key, api_url, disable_request_logging) + super().__init__(api_key, api_url, headers) self.config = RequestConfig( api_url=api_url, api_key=api_key, - disable_request_logging=disable_request_logging, + headers=headers, ) def text(self, params: TranslateParams) -> TranslateResponse: @@ -125,13 +125,13 @@ def __init__( self, api_key: str, api_url: str, - disable_request_logging: Union[bool, None] = False, + headers: Union[Dict[str, str], None] = None, ): - super().__init__(api_key, api_url, disable_request_logging) + super().__init__(api_key, api_url, headers) self.config = RequestConfig( api_url=api_url, api_key=api_key, - disable_request_logging=disable_request_logging, + headers=headers, ) async def text(self, params: TranslateParams) -> TranslateResponse: diff --git a/jigsawstack/validate.py b/jigsawstack/validate.py index 774aef4..37614b2 100644 --- a/jigsawstack/validate.py +++ b/jigsawstack/validate.py @@ -80,13 +80,13 @@ def __init__( self, api_key: str, api_url: str, - disable_request_logging: Union[bool, None] = False, + headers: Union[Dict[str, str], None] = None, ): - super().__init__(api_key, api_url, disable_request_logging) + super().__init__(api_key, api_url, headers) self.config = RequestConfig( api_url=api_url, api_key=api_key, - disable_request_logging=disable_request_logging, + headers=headers, ) @overload @@ -166,13 +166,13 @@ def __init__( self, api_key: str, api_url: str, - disable_request_logging: Union[bool, None] = False, + headers: Union[Dict[str, str], None] = None, ): - super().__init__(api_key, api_url, disable_request_logging) + super().__init__(api_key, api_url, headers) self.config = AsyncRequestConfig( api_url=api_url, api_key=api_key, - disable_request_logging=disable_request_logging, + headers=headers, ) @overload diff --git a/jigsawstack/vision.py b/jigsawstack/vision.py index 793841a..e44bb57 100644 --- a/jigsawstack/vision.py +++ b/jigsawstack/vision.py @@ -172,13 +172,13 @@ def __init__( self, api_key: str, api_url: str, - disable_request_logging: Union[bool, None] = False, + headers: Union[Dict[str, str], None] = None, ): - super().__init__(api_key, api_url, disable_request_logging) + super().__init__(api_key, api_url, headers) self.config = RequestConfig( api_url=api_url, api_key=api_key, - disable_request_logging=disable_request_logging, + headers=headers, ) @overload @@ -254,13 +254,13 @@ def __init__( self, api_key: str, api_url: str, - disable_request_logging: Union[bool, None] = False, + headers: Union[Dict[str, str], None] = None, ): - super().__init__(api_key, api_url, disable_request_logging) + super().__init__(api_key, api_url, headers) self.config = AsyncRequestConfig( api_url=api_url, api_key=api_key, - disable_request_logging=disable_request_logging, + headers=headers, ) @overload @@ -277,6 +277,7 @@ async def vocr( options = options or {} if isinstance(blob, dict): resp = await AsyncRequest( + headers=self.headers, config=self.config, path=path, params=cast(Dict[Any, Any], blob), @@ -286,6 +287,7 @@ async def vocr( files = {"file": blob} resp = await AsyncRequest( + headers=self.headers, config=self.config, path=path, params=options, @@ -312,6 +314,7 @@ async def object_detection( blob, dict ): # If params is provided as a dict, we assume it's the first argument resp = await AsyncRequest( + headers=self.headers, config=self.config, path=path, params=cast(Dict[Any, Any], blob), diff --git a/jigsawstack/web.py b/jigsawstack/web.py index 5d400c3..732e9c6 100644 --- a/jigsawstack/web.py +++ b/jigsawstack/web.py @@ -199,13 +199,13 @@ def __init__( self, api_key: str, api_url: str, - disable_request_logging: Union[bool, None] = False, + headers: Union[Dict[str, str], None] = None, ): - super().__init__(api_key, api_url, disable_request_logging) + super().__init__(api_key, api_url, headers) self.config = RequestConfig( api_url=api_url, api_key=api_key, - disable_request_logging=disable_request_logging, + headers=headers, ) def ai_scrape(self, params: AIScrapeParams) -> AIScrapeResponse: @@ -248,27 +248,15 @@ def html_to_any( return cast(HTMLToAnyURLResponse, resp) def search(self, params: SearchParams) -> SearchResponse: - s = Search( - self.api_key, - self.api_url, - disable_request_logging=self.config.get("disable_request_logging"), - ) + s = Search(self.api_key, self.api_url, self.headers) return s.search(params) def search_suggestions(self, params: SearchSuggestionsParams) -> SearchSuggestionsResponse: - s = Search( - self.api_key, - self.api_url, - disable_request_logging=self.config.get("disable_request_logging"), - ) + s = Search(self.api_key, self.api_url, self.headers) return s.suggestions(params) def deep_research(self, params: DeepResearchParams) -> DeepResearchResponse: - s = Search( - self.api_key, - self.api_url, - disable_request_logging=self.config.get("disable_request_logging"), - ) + s = Search(self.api_key, self.api_url, self.headers) return s.deep_research(params) @@ -282,13 +270,13 @@ def __init__( self, api_key: str, api_url: str, - disable_request_logging: Union[bool, None] = False, + headers: Union[Dict[str, str], None] = None, ): - super().__init__(api_key, api_url, disable_request_logging) + super().__init__(api_key, api_url, headers) self.config = AsyncRequestConfig( api_url=api_url, api_key=api_key, - disable_request_logging=disable_request_logging, + headers=headers, ) async def ai_scrape(self, params: AIScrapeParams) -> AIScrapeResponse: @@ -331,27 +319,15 @@ async def html_to_any( return cast(HTMLToAnyURLResponse, resp) async def search(self, params: SearchParams) -> SearchResponse: - s = AsyncSearch( - self.api_key, - self.api_url, - disable_request_logging=self.config.get("disable_request_logging"), - ) + s = AsyncSearch(self.api_key, self.api_url, self.headers) return await s.search(params) async def search_suggestions( self, params: SearchSuggestionsParams ) -> SearchSuggestionsResponse: - s = AsyncSearch( - self.api_key, - self.api_url, - disable_request_logging=self.config.get("disable_request_logging"), - ) + s = AsyncSearch(self.api_key, self.api_url, self.headers) return await s.suggestions(params) async def deep_research(self, params: DeepResearchParams) -> DeepResearchResponse: - s = AsyncSearch( - self.api_key, - self.api_url, - disable_request_logging=self.config.get("disable_request_logging"), - ) + s = AsyncSearch(self.api_key, self.api_url, self.headers) return await s.deep_research(params) From 2d7e43a986b0141e041cec632832cef51b686c25 Mon Sep 17 00:00:00 2001 From: Khurdhula-Harshavardhan Date: Mon, 15 Sep 2025 19:02:00 -0700 Subject: [PATCH 21/26] fix: pass logging param withing header, rename api_url as base_url and other fixes. --- jigsawstack/__init__.py | 90 +++++++++++++++++---------------- jigsawstack/_config.py | 12 ++--- jigsawstack/async_request.py | 13 +++-- jigsawstack/audio.py | 12 ++--- jigsawstack/classification.py | 12 ++--- jigsawstack/embedding.py | 12 ++--- jigsawstack/embedding_v2.py | 12 ++--- jigsawstack/image_generation.py | 12 ++--- jigsawstack/prediction.py | 12 ++--- jigsawstack/prompt_engine.py | 12 ++--- jigsawstack/request.py | 13 +++-- jigsawstack/search.py | 12 ++--- jigsawstack/sentiment.py | 12 ++--- jigsawstack/sql.py | 12 ++--- jigsawstack/store.py | 34 ++++++++----- jigsawstack/summary.py | 12 ++--- jigsawstack/translate.py | 12 ++--- jigsawstack/validate.py | 12 ++--- jigsawstack/vision.py | 15 +++--- jigsawstack/web.py | 24 ++++----- 20 files changed, 180 insertions(+), 177 deletions(-) diff --git a/jigsawstack/__init__.py b/jigsawstack/__init__.py index 537d6a1..6858a64 100644 --- a/jigsawstack/__init__.py +++ b/jigsawstack/__init__.py @@ -22,7 +22,7 @@ class JigsawStack: api_key: str - api_url: str + base_url: str headers: Dict[str, str] audio: Audio classification: Classification @@ -43,7 +43,7 @@ class JigsawStack: def __init__( self, api_key: Union[str, None] = None, - api_url: Union[str, None] = None, + base_url: Union[str, None] = None, headers: Union[Dict[str, str], None] = None, ) -> None: if api_key is None: @@ -54,50 +54,50 @@ def __init__( "The api_key client option must be set either by passing api_key to the client or by setting the JIGSAWSTACK_API_KEY environment variable" ) - if api_url is None: - api_url = os.environ.get("JIGSAWSTACK_API_URL") - if api_url is None: - api_url = "https://api.jigsawstack.com/" + if base_url is None: + base_url = os.environ.get("JIGSAWSTACK_base_url") + if base_url is None: + base_url = "https://api.jigsawstack.com/" self.api_key = api_key - self.api_url = api_url + self.base_url = base_url self.headers = headers or {"Content-Type": "application/json"} - self.audio = Audio(api_key=api_key, api_url=api_url + "/v1") + self.audio = Audio(api_key=api_key, base_url=base_url + "/v1") - self.web = Web(api_key=api_key, api_url=api_url + "/v1") + self.web = Web(api_key=api_key, base_url=base_url + "/v1") - self.sentiment = Sentiment(api_key=api_key, api_url=api_url + "/v1").analyze + self.sentiment = Sentiment(api_key=api_key, base_url=base_url + "/v1").analyze - self.validate = Validate(api_key=api_key, api_url=api_url + "/v1") - self.summary = Summary(api_key=api_key, api_url=api_url + "/v1").summarize + self.validate = Validate(api_key=api_key, base_url=base_url + "/v1") + self.summary = Summary(api_key=api_key, base_url=base_url + "/v1").summarize - self.vision = Vision(api_key=api_key, api_url=api_url + "/v1") + self.vision = Vision(api_key=api_key, base_url=base_url + "/v1") - self.prediction = Prediction(api_key=api_key, api_url=api_url + "/v1").predict + self.prediction = Prediction(api_key=api_key, base_url=base_url + "/v1").predict - self.text_to_sql = SQL(api_key=api_key, api_url=api_url + "/v1").text_to_sql + self.text_to_sql = SQL(api_key=api_key, base_url=base_url + "/v1").text_to_sql - self.store = Store(api_key=api_key, api_url=api_url + "/v1") - self.translate = Translate(api_key=api_key, api_url=api_url + "/v1") + self.store = Store(api_key=api_key, base_url=base_url + "/v1") + self.translate = Translate(api_key=api_key, base_url=base_url + "/v1") - self.embedding = Embedding(api_key=api_key, api_url=api_url + "/v1").execute + self.embedding = Embedding(api_key=api_key, base_url=base_url + "/v1").execute - self.embedding_v2 = EmbeddingV2(api_key=api_key, api_url=api_url + "/v2").execute + self.embedding_v2 = EmbeddingV2(api_key=api_key, base_url=base_url + "/v2").execute self.image_generation = ImageGeneration( - api_key=api_key, api_url=api_url + "/v1" + api_key=api_key, base_url=base_url + "/v1" ).image_generation - self.classification = Classification(api_key=api_key, api_url=api_url + "/v1").classify + self.classification = Classification(api_key=api_key, base_url=base_url + "/v1").classify - self.prompt_engine = PromptEngine(api_key=api_key, api_url=api_url + "/v1") + self.prompt_engine = PromptEngine(api_key=api_key, base_url=base_url + "/v1") class AsyncJigsawStack: api_key: str - api_url: str + base_url: str headers: Dict[str, str] audio: AsyncAudio classification: AsyncClassification @@ -118,7 +118,7 @@ class AsyncJigsawStack: def __init__( self, api_key: Union[str, None] = None, - api_url: Union[str, None] = None, + base_url: Union[str, None] = None, headers: Union[Dict[str, str], None] = None, ) -> None: if api_key is None: @@ -129,45 +129,47 @@ def __init__( "The api_key client option must be set either by passing api_key to the client or by setting the JIGSAWSTACK_API_KEY environment variable" ) - if api_url is None: - api_url = os.environ.get("JIGSAWSTACK_API_URL") - if api_url is None: - api_url = "https://api.jigsawstack.com/" + if base_url is None: + base_url = os.environ.get("JIGSAWSTACK_base_url") + if base_url is None: + base_url = "https://api.jigsawstack.com/" self.api_key = api_key - self.api_url = api_url + self.base_url = base_url self.headers = headers or {"Content-Type": "application/json"} - self.web = AsyncWeb(api_key=api_key, api_url=api_url + "/v1") + self.web = AsyncWeb(api_key=api_key, base_url=base_url + "/v1") - self.validate = AsyncValidate(api_key=api_key, api_url=api_url + "/v1") - self.audio = AsyncAudio(api_key=api_key, api_url=api_url + "/v1") + self.validate = AsyncValidate(api_key=api_key, base_url=base_url + "/v1") + self.audio = AsyncAudio(api_key=api_key, base_url=base_url + "/v1") - self.vision = AsyncVision(api_key=api_key, api_url=api_url + "/v1") + self.vision = AsyncVision(api_key=api_key, base_url=base_url + "/v1") - self.store = AsyncStore(api_key=api_key, api_url=api_url + "/v1") + self.store = AsyncStore(api_key=api_key, base_url=base_url + "/v1") - self.summary = AsyncSummary(api_key=api_key, api_url=api_url + "/v1").summarize + self.summary = AsyncSummary(api_key=api_key, base_url=base_url + "/v1").summarize - self.prediction = AsyncPrediction(api_key=api_key, api_url=api_url + "/v1").predict + self.prediction = AsyncPrediction(api_key=api_key, base_url=base_url + "/v1").predict - self.text_to_sql = AsyncSQL(api_key=api_key, api_url=api_url + "/v1").text_to_sql + self.text_to_sql = AsyncSQL(api_key=api_key, base_url=base_url + "/v1").text_to_sql - self.sentiment = AsyncSentiment(api_key=api_key, api_url=api_url + "/v1").analyze + self.sentiment = AsyncSentiment(api_key=api_key, base_url=base_url + "/v1").analyze - self.translate = AsyncTranslate(api_key=api_key, api_url=api_url + "/v1") + self.translate = AsyncTranslate(api_key=api_key, base_url=base_url + "/v1") - self.embedding = AsyncEmbedding(api_key=api_key, api_url=api_url + "/v1").execute + self.embedding = AsyncEmbedding(api_key=api_key, base_url=base_url + "/v1").execute - self.embedding_v2 = AsyncEmbeddingV2(api_key=api_key, api_url=api_url + "/v2").execute + self.embedding_v2 = AsyncEmbeddingV2(api_key=api_key, base_url=base_url + "/v2").execute self.image_generation = AsyncImageGeneration( - api_key=api_key, api_url=api_url + "/v1" + api_key=api_key, base_url=base_url + "/v1" ).image_generation - self.classification = AsyncClassification(api_key=api_key, api_url=api_url + "/v1").classify + self.classification = AsyncClassification( + api_key=api_key, base_url=base_url + "/v1" + ).classify - self.prompt_engine = AsyncPromptEngine(api_key=api_key, api_url=api_url + "/v1") + self.prompt_engine = AsyncPromptEngine(api_key=api_key, base_url=base_url + "/v1") # Create a global instance of the Web class diff --git a/jigsawstack/_config.py b/jigsawstack/_config.py index 6e15b54..3a007d8 100644 --- a/jigsawstack/_config.py +++ b/jigsawstack/_config.py @@ -1,17 +1,17 @@ -from typing import Union +from typing import Dict, Union class ClientConfig: base_url: str api_key: str - disable_request_logging: Union[bool, None] = None + headers: Union[Dict[str, str], None] def __init__( self, api_key: str, - api_url: str, - disable_request_logging: Union[bool, None] = None, + base_url: str, + headers: Union[Dict[str, str], None] = None, ): self.api_key = api_key - self.api_url = api_url - self.disable_request_logging = disable_request_logging + self.base_url = base_url + self.headers = headers diff --git a/jigsawstack/async_request.py b/jigsawstack/async_request.py index 028e107..d8f530d 100644 --- a/jigsawstack/async_request.py +++ b/jigsawstack/async_request.py @@ -13,7 +13,7 @@ class AsyncRequestConfig(TypedDict): - api_url: str + base_url: str api_key: str headers: Union[Dict[str, str], None] @@ -25,7 +25,6 @@ def __init__( path: str, params: Union[Dict[Any, Any], List[Dict[Any, Any]]], verb: RequestVerb, - 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 @@ -33,10 +32,10 @@ def __init__( self.path = path self.params = params self.verb = verb - self.api_url = config.get("api_url") + self.base_url = config.get("base_url") self.api_key = config.get("api_key") self.data = data - self.headers = headers or {"Content-Type": "application/json"} + self.headers = config.get("headers", None) or {"Content-Type": "application/json"} self.stream = stream self.files = files # Store files for multipart requests @@ -68,7 +67,7 @@ async def perform(self) -> Union[T, None]: Async method to make an HTTP request to the JigsawStack API. """ async with self.__get_session() as session: - resp = await self.make_request(session, url=f"{self.api_url}{self.path}") + resp = await self.make_request(session, url=f"{self.base_url}{self.path}") # For binary responses if resp.status == 200: @@ -109,7 +108,7 @@ async def perform(self) -> Union[T, None]: async def perform_file(self) -> Union[T, None]: async with self.__get_session() as session: - resp = await self.make_request(session, url=f"{self.api_url}{self.path}") + resp = await self.make_request(session, url=f"{self.base_url}{self.path}") if resp.status != 200: try: @@ -198,7 +197,7 @@ async def perform_streaming(self) -> AsyncGenerator[Union[T, str], None]: AsyncGenerator[Union[T, str], None]: A generator of response chunks """ async with self.__get_session() as session: - resp = await self.make_request(session, url=f"{self.api_url}{self.path}") + resp = await self.make_request(session, url=f"{self.base_url}{self.path}") # delete calls do not return a body if await resp.text() == "": diff --git a/jigsawstack/audio.py b/jigsawstack/audio.py index 589f4d2..575b839 100644 --- a/jigsawstack/audio.py +++ b/jigsawstack/audio.py @@ -54,11 +54,11 @@ class Audio(ClientConfig): def __init__( self, api_key: str, - api_url: str, + base_url: str, headers: Union[Dict[str, str], None] = None, ): - super().__init__(api_key, api_url, headers) - self.config = RequestConfig(api_url=api_url, api_key=api_key, headers=headers) + super().__init__(api_key, base_url, headers) + self.config = RequestConfig(base_url=base_url, api_key=api_key, headers=headers) @overload def speech_to_text( @@ -103,12 +103,12 @@ class AsyncAudio(ClientConfig): def __init__( self, api_key: str, - api_url: str, + base_url: str, headers: Union[Dict[str, str], None] = None, ): - super().__init__(api_key, api_url, headers) + super().__init__(api_key, base_url, headers) self.config = AsyncRequestConfig( - api_url=api_url, + base_url=base_url, api_key=api_key, headers=headers, ) diff --git a/jigsawstack/classification.py b/jigsawstack/classification.py index 1d9770d..134307c 100644 --- a/jigsawstack/classification.py +++ b/jigsawstack/classification.py @@ -67,12 +67,12 @@ class Classification(ClientConfig): def __init__( self, api_key: str, - api_url: str, + base_url: str, headers: Union[Dict[str, str], None] = None, ): - super().__init__(api_key, api_url, headers) + super().__init__(api_key, base_url, headers) self.config = RequestConfig( - api_url=api_url, + base_url=base_url, api_key=api_key, headers=headers, ) @@ -94,12 +94,12 @@ class AsyncClassification(ClientConfig): def __init__( self, api_key: str, - api_url: str, + base_url: str, headers: Union[Dict[str, str], None] = None, ): - super().__init__(api_key, api_url, headers) + super().__init__(api_key, base_url, headers) self.config = AsyncRequestConfig( - api_url=api_url, + base_url=base_url, api_key=api_key, headers=headers, ) diff --git a/jigsawstack/embedding.py b/jigsawstack/embedding.py index 71453ec..d914c4c 100644 --- a/jigsawstack/embedding.py +++ b/jigsawstack/embedding.py @@ -33,12 +33,12 @@ class Embedding(ClientConfig): def __init__( self, api_key: str, - api_url: str, + base_url: str, headers: Union[Dict[str, str], None] = None, ): - super().__init__(api_key, api_url, headers) + super().__init__(api_key, base_url, headers) self.config = RequestConfig( - api_url=api_url, + base_url=base_url, api_key=api_key, headers=headers, ) @@ -81,12 +81,12 @@ class AsyncEmbedding(ClientConfig): def __init__( self, api_key: str, - api_url: str, + base_url: str, headers: Union[Dict[str, str], None] = None, ): - super().__init__(api_key, api_url, headers) + super().__init__(api_key, base_url, headers) self.config = RequestConfig( - api_url=api_url, + base_url=base_url, api_key=api_key, headers=headers, ) diff --git a/jigsawstack/embedding_v2.py b/jigsawstack/embedding_v2.py index 8148df1..685cd52 100644 --- a/jigsawstack/embedding_v2.py +++ b/jigsawstack/embedding_v2.py @@ -31,12 +31,12 @@ class EmbeddingV2(ClientConfig): def __init__( self, api_key: str, - api_url: str, + base_url: str, headers: Union[Dict[str, str], None] = None, ): - super().__init__(api_key, api_url, headers) + super().__init__(api_key, base_url, headers) self.config = RequestConfig( - api_url=api_url, + base_url=base_url, api_key=api_key, headers=headers, ) @@ -79,12 +79,12 @@ class AsyncEmbeddingV2(ClientConfig): def __init__( self, api_key: str, - api_url: str, + base_url: str, headers: Union[Dict[str, str], None] = None, ): - super().__init__(api_key, api_url, headers) + super().__init__(api_key, base_url, headers) self.config = RequestConfig( - api_url=api_url, + base_url=base_url, api_key=api_key, headers=headers, ) diff --git a/jigsawstack/image_generation.py b/jigsawstack/image_generation.py index 40d0e81..08cf81c 100644 --- a/jigsawstack/image_generation.py +++ b/jigsawstack/image_generation.py @@ -89,12 +89,12 @@ class ImageGeneration(ClientConfig): def __init__( self, api_key: str, - api_url: str, + base_url: str, headers: Union[Dict[str, str], None] = None, ): - super().__init__(api_key, api_url, headers) + super().__init__(api_key, base_url, headers) self.config = RequestConfig( - api_url=api_url, + base_url=base_url, api_key=api_key, headers=headers, ) @@ -118,12 +118,12 @@ class AsyncImageGeneration(ClientConfig): def __init__( self, api_key: str, - api_url: str, + base_url: str, headers: Union[Dict[str, str], None] = None, ): - super().__init__(api_key, api_url, headers) + super().__init__(api_key, base_url, headers) self.config = RequestConfig( - api_url=api_url, + base_url=base_url, api_key=api_key, headers=headers, ) diff --git a/jigsawstack/prediction.py b/jigsawstack/prediction.py index eeee9a6..00bd3cf 100644 --- a/jigsawstack/prediction.py +++ b/jigsawstack/prediction.py @@ -48,12 +48,12 @@ class Prediction(ClientConfig): def __init__( self, api_key: str, - api_url: str, + base_url: str, headers: Union[Dict[str, str], None] = None, ): - super().__init__(api_key, api_url, headers) + super().__init__(api_key, base_url, headers) self.config = RequestConfig( - api_url=api_url, + base_url=base_url, api_key=api_key, headers=headers, ) @@ -75,12 +75,12 @@ class AsyncPrediction(ClientConfig): def __init__( self, api_key: str, - api_url: str, + base_url: str, headers: Union[Dict[str, str], None] = None, ): - super().__init__(api_key, api_url, headers) + super().__init__(api_key, base_url, headers) self.config = RequestConfig( - api_url=api_url, + base_url=base_url, api_key=api_key, headers=headers, ) diff --git a/jigsawstack/prompt_engine.py b/jigsawstack/prompt_engine.py index 1c2420b..59932b6 100644 --- a/jigsawstack/prompt_engine.py +++ b/jigsawstack/prompt_engine.py @@ -97,12 +97,12 @@ class PromptEngine(ClientConfig): def __init__( self, api_key: str, - api_url: str, + base_url: str, headers: Union[Dict[str, str], None] = None, ): - super().__init__(api_key, api_url, headers) + super().__init__(api_key, base_url, headers) self.config = RequestConfig( - api_url=api_url, + base_url=base_url, api_key=api_key, headers=headers, ) @@ -203,12 +203,12 @@ class AsyncPromptEngine(ClientConfig): def __init__( self, api_key: str, - api_url: str, + base_url: str, disable_request_logging: Union[bool, None] = False, ): - super().__init__(api_key, api_url, disable_request_logging) + super().__init__(api_key, base_url, disable_request_logging) self.config = RequestConfig( - api_url=api_url, + base_url=base_url, api_key=api_key, disable_request_logging=disable_request_logging, ) diff --git a/jigsawstack/request.py b/jigsawstack/request.py index 26fc710..38cbf01 100644 --- a/jigsawstack/request.py +++ b/jigsawstack/request.py @@ -12,7 +12,7 @@ class RequestConfig(TypedDict): - api_url: str + base_url: str api_key: str headers: Union[Dict[str, str], None] @@ -25,7 +25,6 @@ def __init__( path: str, params: Union[Dict[Any, Any], List[Dict[Any, Any]]], verb: RequestVerb, - headers: Dict[str, str] = None, data: Union[bytes, None] = None, stream: Union[bool, None] = False, files: Union[Dict[str, Any], None] = None, @@ -33,10 +32,10 @@ def __init__( self.path = path self.params = params self.verb = verb - self.api_url = config.get("api_url") + self.base_url = config.get("base_url") self.api_key = config.get("api_key") self.data = data - self.headers = headers or {"Content-Type": "application/json"} + self.headers = config.get("headers", None) or {"Content-Type": "application/json"} self.stream = stream self.files = files @@ -51,7 +50,7 @@ def perform(self) -> Union[T, None]: Raises: requests.HTTPError: If the request fails """ - resp = self.make_request(url=f"{self.api_url}{self.path}") + resp = self.make_request(url=f"{self.base_url}{self.path}") # for binary responses if resp.status_code == 200: @@ -84,7 +83,7 @@ def perform(self) -> Union[T, None]: return cast(T, resp) def perform_file(self) -> Union[T, None]: - resp = self.make_request(url=f"{self.api_url}{self.path}") + resp = self.make_request(url=f"{self.base_url}{self.path}") # delete calls do not return a body if resp.text == "" and resp.status_code == 200: @@ -182,7 +181,7 @@ def perform_streaming(self) -> Generator[Union[T, str], None, None]: Raises: requests.HTTPError: If the request fails """ - resp = self.make_request(url=f"{self.api_url}{self.path}") + resp = self.make_request(url=f"{self.base_url}{self.path}") # delete calls do not return a body if resp.text == "": diff --git a/jigsawstack/search.py b/jigsawstack/search.py index c1fe804..7898f8b 100644 --- a/jigsawstack/search.py +++ b/jigsawstack/search.py @@ -225,12 +225,12 @@ class Search(ClientConfig): def __init__( self, api_key: str, - api_url: str, + base_url: str, headers: Union[Dict[str, str], None] = None, ): - super().__init__(api_key, api_url, headers) + super().__init__(api_key, base_url, headers) self.config = RequestConfig( - api_url=api_url, + base_url=base_url, api_key=api_key, headers=headers, ) @@ -287,12 +287,12 @@ class AsyncSearch(ClientConfig): def __init__( self, api_key: str, - api_url: str, + base_url: str, headers: Union[Dict[str, str], None] = None, ): - super().__init__(api_key, api_url, headers) + super().__init__(api_key, base_url, headers) self.config = AsyncRequestConfig( - api_url=api_url, + base_url=base_url, api_key=api_key, headers=headers, ) diff --git a/jigsawstack/sentiment.py b/jigsawstack/sentiment.py index db1031a..7bc1acb 100644 --- a/jigsawstack/sentiment.py +++ b/jigsawstack/sentiment.py @@ -48,12 +48,12 @@ class Sentiment(ClientConfig): def __init__( self, api_key: str, - api_url: str, + base_url: str, headers: Union[Dict[str, str], None] = None, ): - super().__init__(api_key, api_url, headers) + super().__init__(api_key, base_url, headers) self.config = RequestConfig( - api_url=api_url, + base_url=base_url, api_key=api_key, headers=headers, ) @@ -75,12 +75,12 @@ class AsyncSentiment(ClientConfig): def __init__( self, api_key: str, - api_url: str, + base_url: str, headers: Union[Dict[str, str], None] = None, ): - super().__init__(api_key, api_url, headers) + super().__init__(api_key, base_url, headers) self.config = RequestConfig( - api_url=api_url, + base_url=base_url, api_key=api_key, headers=headers, ) diff --git a/jigsawstack/sql.py b/jigsawstack/sql.py index c74f2a7..4fa0ac8 100644 --- a/jigsawstack/sql.py +++ b/jigsawstack/sql.py @@ -43,12 +43,12 @@ class SQL(ClientConfig): def __init__( self, api_key: str, - api_url: str, + base_url: str, headers: Union[Dict[str, str], None] = None, ): - super().__init__(api_key, api_url, headers) + super().__init__(api_key, base_url, headers) self.config = RequestConfig( - api_url=api_url, + base_url=base_url, api_key=api_key, headers=headers, ) @@ -70,12 +70,12 @@ class AsyncSQL(ClientConfig): def __init__( self, api_key: str, - api_url: str, + base_url: str, headers: Union[Dict[str, str], None] = None, ): - super().__init__(api_key, api_url, headers) + super().__init__(api_key, base_url, headers) self.config = RequestConfig( - api_url=api_url, + base_url=base_url, api_key=api_key, headers=headers, ) diff --git a/jigsawstack/store.py b/jigsawstack/store.py index 4dd6918..89facea 100644 --- a/jigsawstack/store.py +++ b/jigsawstack/store.py @@ -32,12 +32,12 @@ class Store(ClientConfig): def __init__( self, api_key: str, - api_url: str, + base_url: str, headers: Union[Dict[str, str], None] = None, ): - super().__init__(api_key, api_url, headers) + super().__init__(api_key, base_url, headers) self.config = RequestConfig( - api_url=api_url, + base_url=base_url, api_key=api_key, headers=headers, ) @@ -51,14 +51,16 @@ def upload( path = build_path(base_path="/store/file", params=options) content_type = options.get("content_type", "application/octet-stream") - _headers = {"Content-Type": content_type} + config_with_headers = self.config.copy() + if config_with_headers.get("headers") is None: + config_with_headers["headers"] = {} + config_with_headers["headers"]["Content-Type"] = content_type resp = Request( - config=self.config, - params=options, # Empty params since we're using them in the URL + config=config_with_headers, + params={}, path=path, data=file, - headers=_headers, verb="post", ).perform_with_content() return resp @@ -90,12 +92,12 @@ class AsyncStore(ClientConfig): def __init__( self, api_key: str, - api_url: str, + base_url: str, headers: Union[Dict[str, str], None] = None, ): - super().__init__(api_key, api_url, headers) + super().__init__(api_key, base_url, headers) self.config = AsyncRequestConfig( - api_url=api_url, + base_url=base_url, api_key=api_key, headers=headers, ) @@ -108,13 +110,17 @@ async def upload( path = build_path(base_path="/store/file", params=options) content_type = options.get("content_type", "application/octet-stream") - _headers = {"Content-Type": content_type} + + config_with_headers = self.config.copy() + if config_with_headers.get("headers") is None: + config_with_headers["headers"] = {} + config_with_headers["headers"]["Content-Type"] = content_type + resp = await AsyncRequest( - config=self.config, - params=options, # Empty params since we're using them in the URL + config=config_with_headers, + params={}, path=path, data=file, - headers=_headers, verb="post", ).perform_with_content() return resp diff --git a/jigsawstack/summary.py b/jigsawstack/summary.py index 8bbc0c3..48fe578 100644 --- a/jigsawstack/summary.py +++ b/jigsawstack/summary.py @@ -53,12 +53,12 @@ class Summary(ClientConfig): def __init__( self, api_key: str, - api_url: str, + base_url: str, headers: Union[Dict[str, str], None] = None, ): - super().__init__(api_key, api_url, headers) + super().__init__(api_key, base_url, headers) self.config = RequestConfig( - api_url=api_url, + base_url=base_url, api_key=api_key, headers=headers, ) @@ -80,12 +80,12 @@ class AsyncSummary(ClientConfig): def __init__( self, api_key: str, - api_url: str, + base_url: str, headers: Union[Dict[str, str], None] = None, ): - super().__init__(api_key, api_url, headers) + super().__init__(api_key, base_url, headers) self.config = RequestConfig( - api_url=api_url, + base_url=base_url, api_key=api_key, headers=headers, ) diff --git a/jigsawstack/translate.py b/jigsawstack/translate.py index ebffcfb..b609540 100644 --- a/jigsawstack/translate.py +++ b/jigsawstack/translate.py @@ -63,12 +63,12 @@ class Translate(ClientConfig): def __init__( self, api_key: str, - api_url: str, + base_url: str, headers: Union[Dict[str, str], None] = None, ): - super().__init__(api_key, api_url, headers) + super().__init__(api_key, base_url, headers) self.config = RequestConfig( - api_url=api_url, + base_url=base_url, api_key=api_key, headers=headers, ) @@ -124,12 +124,12 @@ class AsyncTranslate(ClientConfig): def __init__( self, api_key: str, - api_url: str, + base_url: str, headers: Union[Dict[str, str], None] = None, ): - super().__init__(api_key, api_url, headers) + super().__init__(api_key, base_url, headers) self.config = RequestConfig( - api_url=api_url, + base_url=base_url, api_key=api_key, headers=headers, ) diff --git a/jigsawstack/validate.py b/jigsawstack/validate.py index 37614b2..d40cf55 100644 --- a/jigsawstack/validate.py +++ b/jigsawstack/validate.py @@ -79,12 +79,12 @@ class Validate(ClientConfig): def __init__( self, api_key: str, - api_url: str, + base_url: str, headers: Union[Dict[str, str], None] = None, ): - super().__init__(api_key, api_url, headers) + super().__init__(api_key, base_url, headers) self.config = RequestConfig( - api_url=api_url, + base_url=base_url, api_key=api_key, headers=headers, ) @@ -165,12 +165,12 @@ class AsyncValidate(ClientConfig): def __init__( self, api_key: str, - api_url: str, + base_url: str, headers: Union[Dict[str, str], None] = None, ): - super().__init__(api_key, api_url, headers) + super().__init__(api_key, base_url, headers) self.config = AsyncRequestConfig( - api_url=api_url, + base_url=base_url, api_key=api_key, headers=headers, ) diff --git a/jigsawstack/vision.py b/jigsawstack/vision.py index e44bb57..280b71d 100644 --- a/jigsawstack/vision.py +++ b/jigsawstack/vision.py @@ -171,12 +171,12 @@ class Vision(ClientConfig): def __init__( self, api_key: str, - api_url: str, + base_url: str, headers: Union[Dict[str, str], None] = None, ): - super().__init__(api_key, api_url, headers) + super().__init__(api_key, base_url, headers) self.config = RequestConfig( - api_url=api_url, + base_url=base_url, api_key=api_key, headers=headers, ) @@ -253,12 +253,12 @@ class AsyncVision(ClientConfig): def __init__( self, api_key: str, - api_url: str, + base_url: str, headers: Union[Dict[str, str], None] = None, ): - super().__init__(api_key, api_url, headers) + super().__init__(api_key, base_url, headers) self.config = AsyncRequestConfig( - api_url=api_url, + base_url=base_url, api_key=api_key, headers=headers, ) @@ -277,7 +277,6 @@ async def vocr( options = options or {} if isinstance(blob, dict): resp = await AsyncRequest( - headers=self.headers, config=self.config, path=path, params=cast(Dict[Any, Any], blob), @@ -287,7 +286,6 @@ async def vocr( files = {"file": blob} resp = await AsyncRequest( - headers=self.headers, config=self.config, path=path, params=options, @@ -314,7 +312,6 @@ async def object_detection( blob, dict ): # If params is provided as a dict, we assume it's the first argument resp = await AsyncRequest( - headers=self.headers, config=self.config, path=path, params=cast(Dict[Any, Any], blob), diff --git a/jigsawstack/web.py b/jigsawstack/web.py index 732e9c6..d432c25 100644 --- a/jigsawstack/web.py +++ b/jigsawstack/web.py @@ -198,12 +198,12 @@ class Web(ClientConfig): def __init__( self, api_key: str, - api_url: str, + base_url: str, headers: Union[Dict[str, str], None] = None, ): - super().__init__(api_key, api_url, headers) + super().__init__(api_key, base_url, headers) self.config = RequestConfig( - api_url=api_url, + base_url=base_url, api_key=api_key, headers=headers, ) @@ -248,15 +248,15 @@ def html_to_any( return cast(HTMLToAnyURLResponse, resp) def search(self, params: SearchParams) -> SearchResponse: - s = Search(self.api_key, self.api_url, self.headers) + s = Search(self.api_key, self.base_url, self.headers) return s.search(params) def search_suggestions(self, params: SearchSuggestionsParams) -> SearchSuggestionsResponse: - s = Search(self.api_key, self.api_url, self.headers) + s = Search(self.api_key, self.base_url, self.headers) return s.suggestions(params) def deep_research(self, params: DeepResearchParams) -> DeepResearchResponse: - s = Search(self.api_key, self.api_url, self.headers) + s = Search(self.api_key, self.base_url, self.headers) return s.deep_research(params) @@ -269,12 +269,12 @@ class AsyncWeb(ClientConfig): def __init__( self, api_key: str, - api_url: str, + base_url: str, headers: Union[Dict[str, str], None] = None, ): - super().__init__(api_key, api_url, headers) + super().__init__(api_key, base_url, headers) self.config = AsyncRequestConfig( - api_url=api_url, + base_url=base_url, api_key=api_key, headers=headers, ) @@ -319,15 +319,15 @@ async def html_to_any( return cast(HTMLToAnyURLResponse, resp) async def search(self, params: SearchParams) -> SearchResponse: - s = AsyncSearch(self.api_key, self.api_url, self.headers) + s = AsyncSearch(self.api_key, self.base_url, self.headers) return await s.search(params) async def search_suggestions( self, params: SearchSuggestionsParams ) -> SearchSuggestionsResponse: - s = AsyncSearch(self.api_key, self.api_url, self.headers) + s = AsyncSearch(self.api_key, self.base_url, self.headers) return await s.suggestions(params) async def deep_research(self, params: DeepResearchParams) -> DeepResearchResponse: - s = AsyncSearch(self.api_key, self.api_url, self.headers) + s = AsyncSearch(self.api_key, self.base_url, self.headers) return await s.deep_research(params) From 160c8598c5c2c671a59c5dcc85f14683a0a5dc83 Mon Sep 17 00:00:00 2001 From: Khurdhula-Harshavardhan Date: Mon, 15 Sep 2025 20:35:31 -0700 Subject: [PATCH 22/26] fix: env variable to still be API_URL --- jigsawstack/__init__.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/jigsawstack/__init__.py b/jigsawstack/__init__.py index 6858a64..590e08e 100644 --- a/jigsawstack/__init__.py +++ b/jigsawstack/__init__.py @@ -55,7 +55,7 @@ def __init__( ) if base_url is None: - base_url = os.environ.get("JIGSAWSTACK_base_url") + base_url = os.environ.get("JIGSAWSTACK_API_URL") if base_url is None: base_url = "https://api.jigsawstack.com/" @@ -80,6 +80,7 @@ def __init__( self.text_to_sql = SQL(api_key=api_key, base_url=base_url + "/v1").text_to_sql self.store = Store(api_key=api_key, base_url=base_url + "/v1") + self.translate = Translate(api_key=api_key, base_url=base_url + "/v1") self.embedding = Embedding(api_key=api_key, base_url=base_url + "/v1").execute @@ -130,7 +131,7 @@ def __init__( ) if base_url is None: - base_url = os.environ.get("JIGSAWSTACK_base_url") + base_url = os.environ.get("JIGSAWSTACK_API_URL") if base_url is None: base_url = "https://api.jigsawstack.com/" @@ -141,6 +142,7 @@ def __init__( self.web = AsyncWeb(api_key=api_key, base_url=base_url + "/v1") self.validate = AsyncValidate(api_key=api_key, base_url=base_url + "/v1") + self.audio = AsyncAudio(api_key=api_key, base_url=base_url + "/v1") self.vision = AsyncVision(api_key=api_key, base_url=base_url + "/v1") From d81794de9e63b453643ab954cfac799e77d03f04 Mon Sep 17 00:00:00 2001 From: Khurdhula-Harshavardhan Date: Mon, 15 Sep 2025 20:36:17 -0700 Subject: [PATCH 23/26] feat: formatting with ruff --- jigsawstack/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jigsawstack/__init__.py b/jigsawstack/__init__.py index 590e08e..98ecc26 100644 --- a/jigsawstack/__init__.py +++ b/jigsawstack/__init__.py @@ -142,7 +142,7 @@ def __init__( self.web = AsyncWeb(api_key=api_key, base_url=base_url + "/v1") self.validate = AsyncValidate(api_key=api_key, base_url=base_url + "/v1") - + self.audio = AsyncAudio(api_key=api_key, base_url=base_url + "/v1") self.vision = AsyncVision(api_key=api_key, base_url=base_url + "/v1") From ad9ab563e91719203800422db42721640d731105 Mon Sep 17 00:00:00 2001 From: Khurdhula-Harshavardhan Date: Mon, 15 Sep 2025 20:46:47 -0700 Subject: [PATCH 24/26] feat: pass headers to endpoint. --- jigsawstack/__init__.py | 58 ++++++++++++++++++++--------------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/jigsawstack/__init__.py b/jigsawstack/__init__.py index 98ecc26..1613098 100644 --- a/jigsawstack/__init__.py +++ b/jigsawstack/__init__.py @@ -64,36 +64,36 @@ def __init__( self.headers = headers or {"Content-Type": "application/json"} - self.audio = Audio(api_key=api_key, base_url=base_url + "/v1") + self.audio = Audio(api_key=api_key, base_url=base_url + "/v1", headers=headers) - self.web = Web(api_key=api_key, base_url=base_url + "/v1") + self.web = Web(api_key=api_key, base_url=base_url + "/v1", headers=headers) - self.sentiment = Sentiment(api_key=api_key, base_url=base_url + "/v1").analyze + self.sentiment = Sentiment(api_key=api_key, base_url=base_url + "/v1", headers=headers).analyze - self.validate = Validate(api_key=api_key, base_url=base_url + "/v1") - self.summary = Summary(api_key=api_key, base_url=base_url + "/v1").summarize + self.validate = Validate(api_key=api_key, base_url=base_url + "/v1", headers=headers) + self.summary = Summary(api_key=api_key, base_url=base_url + "/v1", headers=headers).summarize - self.vision = Vision(api_key=api_key, base_url=base_url + "/v1") + self.vision = Vision(api_key=api_key, base_url=base_url + "/v1", headers=headers) - self.prediction = Prediction(api_key=api_key, base_url=base_url + "/v1").predict + self.prediction = Prediction(api_key=api_key, base_url=base_url + "/v1", headers=headers).predict - self.text_to_sql = SQL(api_key=api_key, base_url=base_url + "/v1").text_to_sql + self.text_to_sql = SQL(api_key=api_key, base_url=base_url + "/v1", headers=headers).text_to_sql - self.store = Store(api_key=api_key, base_url=base_url + "/v1") + self.store = Store(api_key=api_key, base_url=base_url + "/v1", headers=headers) - self.translate = Translate(api_key=api_key, base_url=base_url + "/v1") + self.translate = Translate(api_key=api_key, base_url=base_url + "/v1", headers=headers) - self.embedding = Embedding(api_key=api_key, base_url=base_url + "/v1").execute + self.embedding = Embedding(api_key=api_key, base_url=base_url + "/v1", headers=headers).execute - self.embedding_v2 = EmbeddingV2(api_key=api_key, base_url=base_url + "/v2").execute + self.embedding_v2 = EmbeddingV2(api_key=api_key, base_url=base_url + "/v2", headers=headers).execute self.image_generation = ImageGeneration( - api_key=api_key, base_url=base_url + "/v1" + api_key=api_key, base_url=base_url + "/v1", headers=headers ).image_generation - self.classification = Classification(api_key=api_key, base_url=base_url + "/v1").classify + self.classification = Classification(api_key=api_key, base_url=base_url + "/v1", headers=headers).classify - self.prompt_engine = PromptEngine(api_key=api_key, base_url=base_url + "/v1") + self.prompt_engine = PromptEngine(api_key=api_key, base_url=base_url + "/v1", headers=headers) class AsyncJigsawStack: @@ -139,39 +139,39 @@ def __init__( self.base_url = base_url self.headers = headers or {"Content-Type": "application/json"} - self.web = AsyncWeb(api_key=api_key, base_url=base_url + "/v1") + self.web = AsyncWeb(api_key=api_key, base_url=base_url + "/v1", headers=headers) - self.validate = AsyncValidate(api_key=api_key, base_url=base_url + "/v1") + self.validate = AsyncValidate(api_key=api_key, base_url=base_url + "/v1", headers=headers) - self.audio = AsyncAudio(api_key=api_key, base_url=base_url + "/v1") + self.audio = AsyncAudio(api_key=api_key, base_url=base_url + "/v1", headers=headers) - self.vision = AsyncVision(api_key=api_key, base_url=base_url + "/v1") + self.vision = AsyncVision(api_key=api_key, base_url=base_url + "/v1", headers=headers) - self.store = AsyncStore(api_key=api_key, base_url=base_url + "/v1") + self.store = AsyncStore(api_key=api_key, base_url=base_url + "/v1", headers=headers) - self.summary = AsyncSummary(api_key=api_key, base_url=base_url + "/v1").summarize + self.summary = AsyncSummary(api_key=api_key, base_url=base_url + "/v1", headers=headers).summarize self.prediction = AsyncPrediction(api_key=api_key, base_url=base_url + "/v1").predict - self.text_to_sql = AsyncSQL(api_key=api_key, base_url=base_url + "/v1").text_to_sql + self.text_to_sql = AsyncSQL(api_key=api_key, base_url=base_url + "/v1", headers=headers).text_to_sql - self.sentiment = AsyncSentiment(api_key=api_key, base_url=base_url + "/v1").analyze + self.sentiment = AsyncSentiment(api_key=api_key, base_url=base_url + "/v1", headers=headers).analyze - self.translate = AsyncTranslate(api_key=api_key, base_url=base_url + "/v1") + self.translate = AsyncTranslate(api_key=api_key, base_url=base_url + "/v1", headers=headers) - self.embedding = AsyncEmbedding(api_key=api_key, base_url=base_url + "/v1").execute + self.embedding = AsyncEmbedding(api_key=api_key, base_url=base_url + "/v1", headers=headers).execute - self.embedding_v2 = AsyncEmbeddingV2(api_key=api_key, base_url=base_url + "/v2").execute + self.embedding_v2 = AsyncEmbeddingV2(api_key=api_key, base_url=base_url + "/v2", headers=headers).execute self.image_generation = AsyncImageGeneration( - api_key=api_key, base_url=base_url + "/v1" + api_key=api_key, base_url=base_url + "/v1", headers=headers ).image_generation self.classification = AsyncClassification( - api_key=api_key, base_url=base_url + "/v1" + api_key=api_key, base_url=base_url + "/v1", headers=headers ).classify - self.prompt_engine = AsyncPromptEngine(api_key=api_key, base_url=base_url + "/v1") + self.prompt_engine = AsyncPromptEngine(api_key=api_key, base_url=base_url + "/v1", headers=headers) # Create a global instance of the Web class From 6bef4e1c6b05b471bc3ddacad45064905ab5c8d6 Mon Sep 17 00:00:00 2001 From: Khurdhula-Harshavardhan Date: Mon, 15 Sep 2025 20:47:01 -0700 Subject: [PATCH 25/26] feat: pass headers to endpoint. --- jigsawstack/__init__.py | 56 ++++++++++++++++++++++++++++++----------- 1 file changed, 42 insertions(+), 14 deletions(-) diff --git a/jigsawstack/__init__.py b/jigsawstack/__init__.py index 1613098..9218810 100644 --- a/jigsawstack/__init__.py +++ b/jigsawstack/__init__.py @@ -68,32 +68,48 @@ def __init__( self.web = Web(api_key=api_key, base_url=base_url + "/v1", headers=headers) - self.sentiment = Sentiment(api_key=api_key, base_url=base_url + "/v1", headers=headers).analyze + self.sentiment = Sentiment( + api_key=api_key, base_url=base_url + "/v1", headers=headers + ).analyze self.validate = Validate(api_key=api_key, base_url=base_url + "/v1", headers=headers) - self.summary = Summary(api_key=api_key, base_url=base_url + "/v1", headers=headers).summarize + self.summary = Summary( + api_key=api_key, base_url=base_url + "/v1", headers=headers + ).summarize self.vision = Vision(api_key=api_key, base_url=base_url + "/v1", headers=headers) - self.prediction = Prediction(api_key=api_key, base_url=base_url + "/v1", headers=headers).predict + self.prediction = Prediction( + api_key=api_key, base_url=base_url + "/v1", headers=headers + ).predict - self.text_to_sql = SQL(api_key=api_key, base_url=base_url + "/v1", headers=headers).text_to_sql + self.text_to_sql = SQL( + api_key=api_key, base_url=base_url + "/v1", headers=headers + ).text_to_sql self.store = Store(api_key=api_key, base_url=base_url + "/v1", headers=headers) self.translate = Translate(api_key=api_key, base_url=base_url + "/v1", headers=headers) - self.embedding = Embedding(api_key=api_key, base_url=base_url + "/v1", headers=headers).execute + self.embedding = Embedding( + api_key=api_key, base_url=base_url + "/v1", headers=headers + ).execute - self.embedding_v2 = EmbeddingV2(api_key=api_key, base_url=base_url + "/v2", headers=headers).execute + self.embedding_v2 = EmbeddingV2( + api_key=api_key, base_url=base_url + "/v2", headers=headers + ).execute self.image_generation = ImageGeneration( api_key=api_key, base_url=base_url + "/v1", headers=headers ).image_generation - self.classification = Classification(api_key=api_key, base_url=base_url + "/v1", headers=headers).classify + self.classification = Classification( + api_key=api_key, base_url=base_url + "/v1", headers=headers + ).classify - self.prompt_engine = PromptEngine(api_key=api_key, base_url=base_url + "/v1", headers=headers) + self.prompt_engine = PromptEngine( + api_key=api_key, base_url=base_url + "/v1", headers=headers + ) class AsyncJigsawStack: @@ -149,19 +165,29 @@ def __init__( self.store = AsyncStore(api_key=api_key, base_url=base_url + "/v1", headers=headers) - self.summary = AsyncSummary(api_key=api_key, base_url=base_url + "/v1", headers=headers).summarize + self.summary = AsyncSummary( + api_key=api_key, base_url=base_url + "/v1", headers=headers + ).summarize self.prediction = AsyncPrediction(api_key=api_key, base_url=base_url + "/v1").predict - self.text_to_sql = AsyncSQL(api_key=api_key, base_url=base_url + "/v1", headers=headers).text_to_sql + self.text_to_sql = AsyncSQL( + api_key=api_key, base_url=base_url + "/v1", headers=headers + ).text_to_sql - self.sentiment = AsyncSentiment(api_key=api_key, base_url=base_url + "/v1", headers=headers).analyze + self.sentiment = AsyncSentiment( + api_key=api_key, base_url=base_url + "/v1", headers=headers + ).analyze self.translate = AsyncTranslate(api_key=api_key, base_url=base_url + "/v1", headers=headers) - self.embedding = AsyncEmbedding(api_key=api_key, base_url=base_url + "/v1", headers=headers).execute + self.embedding = AsyncEmbedding( + api_key=api_key, base_url=base_url + "/v1", headers=headers + ).execute - self.embedding_v2 = AsyncEmbeddingV2(api_key=api_key, base_url=base_url + "/v2", headers=headers).execute + self.embedding_v2 = AsyncEmbeddingV2( + api_key=api_key, base_url=base_url + "/v2", headers=headers + ).execute self.image_generation = AsyncImageGeneration( api_key=api_key, base_url=base_url + "/v1", headers=headers @@ -171,7 +197,9 @@ def __init__( api_key=api_key, base_url=base_url + "/v1", headers=headers ).classify - self.prompt_engine = AsyncPromptEngine(api_key=api_key, base_url=base_url + "/v1", headers=headers) + self.prompt_engine = AsyncPromptEngine( + api_key=api_key, base_url=base_url + "/v1", headers=headers + ) # Create a global instance of the Web class From 5b29bef622fd18fd933eccb90eed6f36e40be679 Mon Sep 17 00:00:00 2001 From: Khurdhula-Harshavardhan Date: Mon, 15 Sep 2025 20:57:50 -0700 Subject: [PATCH 26/26] fix: AsyncPromptEngine must accept headers. --- jigsawstack/prompt_engine.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/jigsawstack/prompt_engine.py b/jigsawstack/prompt_engine.py index 59932b6..c264db9 100644 --- a/jigsawstack/prompt_engine.py +++ b/jigsawstack/prompt_engine.py @@ -204,13 +204,13 @@ def __init__( self, api_key: str, base_url: str, - disable_request_logging: Union[bool, None] = False, + headers: Union[Dict[str, str], None] = None, ): - super().__init__(api_key, base_url, disable_request_logging) + super().__init__(api_key, base_url, headers) self.config = RequestConfig( base_url=base_url, api_key=api_key, - disable_request_logging=disable_request_logging, + headers=headers, ) async def create(self, params: PromptEngineCreateParams) -> PromptEngineCreateResponse: