From d6951dfa7e22dce31eb24df5f85705bbf5fe0a3b Mon Sep 17 00:00:00 2001 From: admin Date: Tue, 4 Mar 2025 13:15:29 -0800 Subject: [PATCH 1/6] Remove KV store functionality --- jigsawstack/store.py | 30 +++--------------------------- setup.py | 2 +- tests/test_store.py | 44 ++++++++++++++++++++++++++++---------------- 3 files changed, 32 insertions(+), 44 deletions(-) diff --git a/jigsawstack/store.py b/jigsawstack/store.py index 51a0713..b42be4b 100644 --- a/jigsawstack/store.py +++ b/jigsawstack/store.py @@ -3,33 +3,14 @@ from .request import Request, RequestConfig from .async_request import AsyncRequest, AsyncRequestConfig from ._config import ClientConfig -from typing import Any, Dict, List, cast -from typing_extensions import NotRequired, TypedDict - - -class FileDeleteResponse(TypedDict): - success: bool -class KVGetParams(TypedDict): - key: str -class KVGetResponse(TypedDict): +class FileDeleteResponse(TypedDict): success: bool - value: str -class KVAddParams(TypedDict): - key: str - value: str - encrypt: NotRequired[bool] - byo_secret: NotRequired[str] - - -class KVAddResponse(TypedDict): - success: bool - class FileUploadParams(TypedDict): overwrite: bool @@ -37,7 +18,7 @@ class FileUploadParams(TypedDict): content_type: NotRequired[str] -class KV(ClientConfig): + config: RequestConfig @@ -83,7 +64,6 @@ def delete(self, key: str) -> KVGetResponse: class Store(ClientConfig): config: RequestConfig - kv: KV def __init__( self, @@ -98,8 +78,6 @@ def __init__( disable_request_logging=disable_request_logging, ) - self.kv = KV(api_key, api_url, disable_request_logging) - def upload(self, file: bytes, options=FileUploadParams) -> Any: overwrite = options.get("overwrite") filename = options.get("filename") @@ -141,7 +119,6 @@ def delete(self, key: str) -> FileDeleteResponse: return resp -class AsyncKV(ClientConfig): config: AsyncRequestConfig @@ -188,7 +165,6 @@ async def delete(self, key: str) -> KVGetResponse: class AsyncStore(ClientConfig): config: AsyncRequestConfig - kv: AsyncKV def __init__( self, @@ -202,7 +178,7 @@ def __init__( api_key=api_key, disable_request_logging=disable_request_logging, ) - self.kv = AsyncKV(api_key, api_url, disable_request_logging) + async def upload(self, file: bytes, options=FileUploadParams) -> Any: overwrite = options.get("overwrite") diff --git a/setup.py b/setup.py index 1219799..3d8d2c0 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ setup( name="jigsawstack", - version="0.1.29", + version="0.1.30", description="JigsawStack Python SDK", long_description=open("README.md", encoding="utf8").read(), long_description_content_type="text/markdown", diff --git a/tests/test_store.py b/tests/test_store.py index c4862d9..bf03d3b 100644 --- a/tests/test_store.py +++ b/tests/test_store.py @@ -11,29 +11,41 @@ @pytest.mark.skip(reason="Skipping TestWebAPI class for now") -def test_async_kv_response(): - async def _test(): - client = AsyncJigsawStack() - try: - result = await client.store.kv.add( - {"key": "hello", "value": "world", "encrypt": False} - ) - logger.info(result) - assert result["success"] == True - except JigsawStackError as e: - pytest.fail(f"Unexpected JigsawStackError: {e}") +class TestAsyncFileOperations: + """ + Test class for async file operations. + Add your file operation tests here. + """ + + def test_async_file_upload(self): + # Template for future file upload tests + pass + + def test_async_file_retrieval(self): + # Template for future file retrieval tests + pass + + def test_async_file_deletion(self): + # Template for future file deletion tests + pass - asyncio.run(_test()) - -def test_async_retriev_kv_response(): +# Example file upload test +# Uncomment and modify as needed +""" +def test_async_file_upload_example(): async def _test(): client = AsyncJigsawStack() try: - result = await client.store.kv.get("hello") + file_content = b"test file content" + result = await client.store.upload( + file_content, + {"filename": "test.txt", "overwrite": True} + ) logger.info(result) - assert result["success"] == True + assert result["success"] == True except JigsawStackError as e: pytest.fail(f"Unexpected JigsawStackError: {e}") asyncio.run(_test()) +""" \ No newline at end of file From d3ba25591b8e1f686b11e2433319adf616338a88 Mon Sep 17 00:00:00 2001 From: Khurdhula-Harshavardhan Date: Wed, 5 Mar 2025 10:04:02 -0800 Subject: [PATCH 2/6] Introducing image_generation to Jigsawstack-python: 1. image_generation.py: - Defined params and response types for ImageGeneration. - Defined a new class/module for ImageGeneration & AsyncImageGeneration. 2. request.py: - Since the response is bytes as type, and content-type is "image/png" within its headers, we introduce a new content-type before raising a parsing error, otherwise. 3. jigsawstack: - Added ImageGeneration and AsyncImageGeneration to jigsawstack. --- jigsawstack/__init__.py | 14 +++++ jigsawstack/image_generation.py | 103 ++++++++++++++++++++++++++++++++ jigsawstack/request.py | 8 ++- 3 files changed, 122 insertions(+), 3 deletions(-) create mode 100644 jigsawstack/image_generation.py diff --git a/jigsawstack/__init__.py b/jigsawstack/__init__.py index 7931505..d75a6f0 100644 --- a/jigsawstack/__init__.py +++ b/jigsawstack/__init__.py @@ -15,11 +15,13 @@ from .prompt_engine import PromptEngine, AsyncPromptEngine from .embedding import Embedding, AsyncEmbedding from .exceptions import JigsawStackError +from .image_generation import ImageGeneration, AsyncImageGeneration class JigsawStack: audio: Audio vision: Vision + image_generation: ImageGeneration file: Store web: Web search: Search @@ -116,6 +118,11 @@ def __init__( api_url=api_url, disable_request_logging=disable_request_logging, ).execute + self.image_generation = ImageGeneration( + api_key=api_key, + api_url=api_url, + disable_request_logging=disable_request_logging, + ).image_generation class AsyncJigsawStack: @@ -124,6 +131,7 @@ class AsyncJigsawStack: web: AsyncWeb audio: AsyncAudio vision: AsyncVision + image_generation: AsyncImageGeneration store: AsyncStore prompt_engine: AsyncPromptEngine api_key: str @@ -227,6 +235,12 @@ def __init__( disable_request_logging=disable_request_logging, ).execute + self.image_generation = AsyncImageGeneration( + api_key=api_key, + api_url=api_url, + disable_request_logging=disable_request_logging, + ).image_generation + # Create a global instance of the Web class __all__ = ["JigsawStack", "Search", "JigsawStackError", "AsyncJigsawStack"] diff --git a/jigsawstack/image_generation.py b/jigsawstack/image_generation.py new file mode 100644 index 0000000..07695cb --- /dev/null +++ b/jigsawstack/image_generation.py @@ -0,0 +1,103 @@ +from typing import Any, Dict, List, Union, cast +from typing_extensions import NotRequired, TypedDict, Literal +from .request import Request, RequestConfig +from .async_request import AsyncRequest + +from typing import List, Union +from ._config import ClientConfig + +class ImageGenerationparams(TypedDict): + prompt: str + """" + The text to generate the image from." + """ + aspect_ratio: Literal["1:1", "16:9", "21:9", "3:2", "2:3", "4:5", "5:4", "3:4", "4:3", "9:16", "9:21"] + """ + The aspect ratio of the image. The default is 1:1. + """ + width: NotRequired[int] + """" + The width of the image. The default is 512." + """ + height: NotRequired[int] + """ + The height of the image. The default is 512." + """ + steps: NotRequired[int] + """" + The number of steps to generate the image."" + """ + advance_config: NotRequired[Dict[str, Union[int, str]]] + """ + The advance configuration for the image generation. The default is None. + You can pass the following: + - `seed`: The seed for the image generation. The default is None. + - `guidance`: The guidance for the image generation. The default is None. + - `negative_prompt`: The negative prompt for the image generation. The default is None. + """ + +class ImageGenerationResponse(TypedDict): + success: bool + """ + Indicates whether the image generation was successful. + """ + image: bytes + """ + The generated image as a blob. + """ + +class ImageGeneration(ClientConfig): + config: RequestConfig + + def __init__( + self, + api_key: str, + api_url: str, + disable_request_logging: Union[bool, None] = False, + ): + super().__init__(api_key, api_url, disable_request_logging=disable_request_logging) + self.config = RequestConfig( + api_url=api_url, + api_key=api_key, + disable_request_logging=disable_request_logging, + ) + + def image_generation(self, params: ImageGenerationparams) -> ImageGenerationResponse: + path = "/ai/image_generation" + resp = Request( + config=self.config, + path=path, + params=cast(Dict[Any, Any], params), # type: ignore + verb="post", + ).perform() + return resp + +class AsyncImageGeneration(ClientConfig): + config: RequestConfig + + def __init__( + self, + api_key: str, + api_url: str, + disable_request_logging: Union[bool, None] = False, + ): + super().__init__(api_key, api_url, disable_request_logging=disable_request_logging) + self.config = RequestConfig( + api_url=api_url, + api_key=api_key, + disable_request_logging=disable_request_logging, + ) + + async def image_generation(self, params: ImageGenerationparams) -> ImageGenerationResponse: + path = "/ai/image_generation" + resp = await AsyncRequest( + config=self.config, + path=path, + params=cast(Dict[Any, Any], params), # type: ignore + verb="post", + ).perform() + return resp + + + + \ No newline at end of file diff --git a/jigsawstack/request.py b/jigsawstack/request.py index 101e937..1a6b464 100644 --- a/jigsawstack/request.py +++ b/jigsawstack/request.py @@ -56,7 +56,9 @@ def perform(self) -> Union[T, None]: # this is a safety net, if we get here it means the JigsawStack API is having issues # and most likely the gateway is returning htmls - if "application/json" not in resp.headers["content-type"] and "audio/wav" not in resp.headers["content-type"]: + if "application/json" not in resp.headers["content-type"] \ + and "audio/wav" not in resp.headers["content-type"] \ + and "image/png" not in resp.headers["content-type"]: raise_for_code_and_type( code=500, message="Failed to parse JigsawStack API response. Please try again.", @@ -72,9 +74,9 @@ def perform(self) -> Union[T, None]: err=error.get("error"), ) - if "audio/wav" in resp.headers["content-type"]: + if "audio/wav" or "image/png" in resp.headers["content-type"]: return cast(T, resp) # we return the response object, instead of the json - + return cast(T, resp.json()) def perform_file(self) -> Union[T, None]: From 8a3679c5128af5a81171156807db0ddb7ba5a8d0 Mon Sep 17 00:00:00 2001 From: Khurdhula-Harshavardhan Date: Wed, 5 Mar 2025 11:03:54 -0800 Subject: [PATCH 3/6] Updating class prototype. --- jigsawstack/image_generation.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/jigsawstack/image_generation.py b/jigsawstack/image_generation.py index 07695cb..8d4c3f0 100644 --- a/jigsawstack/image_generation.py +++ b/jigsawstack/image_generation.py @@ -1,17 +1,17 @@ from typing import Any, Dict, List, Union, cast -from typing_extensions import NotRequired, TypedDict, Literal +from typing_extensions import NotRequired, TypedDict, Literal, Required from .request import Request, RequestConfig from .async_request import AsyncRequest from typing import List, Union from ._config import ClientConfig -class ImageGenerationparams(TypedDict): - prompt: str +class ImageGenerationParams(TypedDict): + prompt: Required[str] """" The text to generate the image from." """ - aspect_ratio: Literal["1:1", "16:9", "21:9", "3:2", "2:3", "4:5", "5:4", "3:4", "4:3", "9:16", "9:21"] + aspect_ratio: NotRequired[Literal["1:1", "16:9", "21:9", "3:2", "2:3", "4:5", "5:4", "3:4", "4:3", "9:16", "9:21"]] """ The aspect ratio of the image. The default is 1:1. """ @@ -62,7 +62,7 @@ def __init__( disable_request_logging=disable_request_logging, ) - def image_generation(self, params: ImageGenerationparams) -> ImageGenerationResponse: + def image_generation(self, params: ImageGenerationParams) -> ImageGenerationResponse: path = "/ai/image_generation" resp = Request( config=self.config, @@ -88,7 +88,7 @@ def __init__( disable_request_logging=disable_request_logging, ) - async def image_generation(self, params: ImageGenerationparams) -> ImageGenerationResponse: + async def image_generation(self, params: ImageGenerationParams) -> ImageGenerationResponse: path = "/ai/image_generation" resp = await AsyncRequest( config=self.config, From a234136cd7a8bc76b1f4f7196e36cc23302f1884 Mon Sep 17 00:00:00 2001 From: admin Date: Wed, 5 Mar 2025 18:28:03 -0800 Subject: [PATCH 4/6] update file path --- jigsawstack/store.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/jigsawstack/store.py b/jigsawstack/store.py index b42be4b..aab7505 100644 --- a/jigsawstack/store.py +++ b/jigsawstack/store.py @@ -99,7 +99,7 @@ def upload(self, file: bytes, options=FileUploadParams) -> Any: return resp def get(self, key: str) -> Any: - path = f"/store/file/{key}" + path = f"/store/file/read/{key}" resp = Request( config=self.config, path=path, @@ -109,7 +109,7 @@ def get(self, key: str) -> Any: return resp def delete(self, key: str) -> FileDeleteResponse: - path = f"/store/file/{key}" + path = f"/store/file/read/{key}" resp = Request( config=self.config, path=path, @@ -201,7 +201,7 @@ async def upload(self, file: bytes, options=FileUploadParams) -> Any: return resp async def get(self, key: str) -> Any: - path = f"/store/file/{key}" + path = f"/store/file/read/{key}" resp = await AsyncRequest( config=self.config, path=path, @@ -211,7 +211,7 @@ async def get(self, key: str) -> Any: return resp async def delete(self, key: str) -> FileDeleteResponse: - path = f"/store/file/{key}" + path = f"/store/file/read/{key}" resp = AsyncRequest( config=self.config, path=path, From f0cdbfed0287e0f334b876eb6a6cbb30d1822a95 Mon Sep 17 00:00:00 2001 From: Yoeven D Khemlani Date: Thu, 6 Mar 2025 10:50:42 +0800 Subject: [PATCH 5/6] Create biome.json --- biome.json | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 biome.json diff --git a/biome.json b/biome.json new file mode 100644 index 0000000..5ad6df5 --- /dev/null +++ b/biome.json @@ -0,0 +1,37 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", + "files": { + "ignoreUnknown": false, + "ignore": [] + }, + "formatter": { + "enabled": true, + "useEditorconfig": true, + "formatWithErrors": false, + "indentStyle": "space", + "indentWidth": 2, + "lineEnding": "lf", + "lineWidth": 150, + "attributePosition": "auto", + "bracketSpacing": true + }, + "organizeImports": { + "enabled": true + }, + "linter": { + "enabled": false + }, + "javascript": { + "formatter": { + "jsxQuoteStyle": "double", + "quoteProperties": "asNeeded", + "trailingCommas": "es5", + "semicolons": "always", + "arrowParentheses": "always", + "bracketSameLine": false, + "quoteStyle": "double", + "attributePosition": "auto", + "bracketSpacing": true + } + } +} From 38c556269375ffef96463745eb6d8728bcf65b22 Mon Sep 17 00:00:00 2001 From: Khurdhula-Harshavardhan Date: Thu, 6 Mar 2025 16:24:40 -0800 Subject: [PATCH 6/6] jigsawstack-python updates: 1. Merged KV removal PR. 2. Fixed KV removal bug. 3. Updated validate.nsfw to accept valid params. 4. Tested all endpoints, to match jigsawstack & documentation. --- jigsawstack/store.py | 90 +---------------------------------------- jigsawstack/validate.py | 8 ++-- 2 files changed, 5 insertions(+), 93 deletions(-) diff --git a/jigsawstack/store.py b/jigsawstack/store.py index aab7505..16ff78c 100644 --- a/jigsawstack/store.py +++ b/jigsawstack/store.py @@ -17,50 +17,6 @@ class FileUploadParams(TypedDict): filename: str content_type: NotRequired[str] - - - - config: RequestConfig - - def __init__( - self, - api_key: str, - api_url: str, - disable_request_logging: Union[bool, None] = False, - ): - 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, - ) - - def add(self, params: KVAddParams) -> KVAddResponse: - path = "/store/kv" - resp = Request( - config=self.config, - path=path, - params=cast(Dict[Any, Any], params), - verb="post", - ).perform_with_content() - return resp - - def get(self, key: str) -> KVGetResponse: - path = f"/store/kv/{key}" - resp = Request(config=self.config, path=path, verb="get").perform_with_content() - return resp - - def delete(self, key: str) -> KVGetResponse: - path = f"/store/kv/{key}" - resp = Request( - config=self.config, - path=path, - params=cast(Dict[Any, Any], params={}), - verb="delete", - ).perform_with_content() - return resp - - class Store(ClientConfig): config: RequestConfig @@ -113,51 +69,7 @@ def delete(self, key: str) -> FileDeleteResponse: resp = Request( config=self.config, path=path, - params=cast(Dict[Any, Any], params={}), - verb="delete", - ).perform_with_content() - return resp - - - - config: AsyncRequestConfig - - def __init__( - self, - api_key: str, - api_url: str, - disable_request_logging: Union[bool, None] = False, - ): - super().__init__(api_key, api_url, disable_request_logging) - self.config = AsyncRequestConfig( - api_url=api_url, - api_key=api_key, - disable_request_logging=disable_request_logging, - ) - - async def add(self, params: KVAddParams) -> KVAddResponse: - path = "/store/kv" - resp = await AsyncRequest( - config=self.config, - path=path, - params=cast(Dict[Any, Any], params), - verb="post", - ).perform_with_content() - return resp - - async def get(self, key: str) -> KVGetResponse: - path = f"/store/kv/{key}" - resp = await AsyncRequest( - config=self.config, path=path, verb="get", params={} - ).perform_with_content() - return resp - - async def delete(self, key: str) -> KVGetResponse: - path = f"/store/kv/{key}" - resp = await AsyncRequest( - config=self.config, - path=path, - params=cast(Dict[Any, Any], params={}), + params=key, verb="delete", ).perform_with_content() return resp diff --git a/jigsawstack/validate.py b/jigsawstack/validate.py index 80dab07..daafa06 100644 --- a/jigsawstack/validate.py +++ b/jigsawstack/validate.py @@ -96,14 +96,14 @@ def email(self, params: EmailValidationParams) -> EmailValidationResponse: ).perform_with_content() return resp - def nsfw(self, url: str) -> NSFWResponse: + def nsfw(self, params: NSFWParams) -> NSFWResponse: path = f"/validate/nsfw" resp = Request( config=self.config, path=path, params=cast( Dict[Any, Any], - params={"url": url}, + params ), verb="post", ).perform_with_content() @@ -174,14 +174,14 @@ async def email(self, params: EmailValidationParams) -> EmailValidationResponse: ).perform_with_content() return resp - async def nsfw(self, url: str) -> NSFWResponse: + async def nsfw(self, params: NSFWParams) -> NSFWResponse: path = f"/validate/nsfw" resp = await AsyncRequest( config=self.config, path=path, params=cast( Dict[Any, Any], - params={"url": url}, + params, ), verb="post", ).perform_with_content()